【问题标题】:Spring Zuul API Gateway with Spring Session / Redis Authenticate and Route in same RequestSpring Zuul API Gateway 与 Spring Session / Redis 在同一请求中进行身份验证和路由
【发布时间】:2016-04-17 13:01:18
【问题描述】:

这几天我真的在苦苦思索如何做到这一点,终于决定认输求救!!!

我遵循了 Dave Syer 博士关于 Angular 和 Spring Security 的教程,特别是将 Zuul 代理作为 api 网关并使用 Spring Session 和 Redis (https://github.com/spring-guides/tut-spring-security-and-angular-js/tree/master/double#_sso_with_oauth2_angular_js_and_spring_security_part_v)

我遇到的问题是我通过网关从具有以下标头的外部应用程序调用资源休息服务:

String plainCreds = "user:password";
byte[] plainCredsBytes = plainCreds.getBytes();
byte[] base64CredsBytes = Base64.getEncoder().encode(plainCredsBytes);
String base64Creds = new String(base64CredsBytes);

HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + base64Creds);

被认证,然后被zuul路由,然后资源通过redis访问认证会话。

问题是会话似乎只在请求响应后才提交到网关中的 redis。所以发生的事情是,当我使用标头调用资源服务时,我可以看到网关和正在创建的会话中发生的成功身份验证,但是由于会话在其之后不在 redis 中,我在资源中得到了 403通过zuul路由。

但是,如果我收到错误消息,请获取会话 ID 并将其添加到标头并重试它,因为现在我的经过身份验证的会话在路由后可用于资源项目。

请有人指点我如何通过网关让我的电话在同一个请求中进行身份验证和路由?

谢谢 贾斯汀

【问题讨论】:

  • 你解决了吗?我也有同样的问题。

标签: spring-security spring-cloud spring-session


【解决方案1】:

我在不同页面上关注了 Justin Taylor 的帖子,所以这是他的解决方案。在这里有源代码的解决方案让我很有意义:

  1. 让 Spring Session 热切提交 - 从 spring-session v1.0 开始,有注释属性@EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE) 可以立即将会话数据保存到 Redis 中。文档here
  2. 用于将会话添加到当前请求标头的简单 Zuul 过滤器:
@Component
public class SessionSavingZuulPreFilter extends ZuulFilter {

    @Autowired
    private SessionRepository repository;

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public Object run() {
        RequestContext context = RequestContext.getCurrentContext();

        HttpSession httpSession = context.getRequest().getSession();
        Session session = repository.getSession(httpSession.getId());

        context.addZuulRequestHeader("Cookie", "SESSION=" + httpSession.getId());

        log.info("ZuulPreFilter session proxy: {}", session.getId());

        return null;
    }

}

再一次 - 这不是我的解决方案 - 凭证交给贾斯汀·泰勒。

【讨论】:

  • 很酷,它很有帮助!我希望将来与 zuul 和 spring session 有更好的集成,这将满足这一点,因为它是具有无状态 api 的边缘架构/网关的一个非常标准的用例。
  • 对于那些偶然发现这一点的人。这个过滤器基本上是开箱即用的。对于“其他方法”,将类型设置为“pre”并将顺序设置为零。不知道为什么省略这些,因为它们本质上是复制和粘贴此代码的一行。
  • @Ceekay 感谢您的评论。我修复了代码。这段代码只是一个例子。例如。它不处理多个 Cookie 值(它使用 SESSION= 覆盖所有现有值)。
  • 好像不能处理并发用户?
【解决方案2】:

对于这里的延迟回复,我感到非常抱歉,南非的一大优点是我们的电信很棒,呵呵,我有一段时间没有在家上网了,我的源代码在我的家用电脑上。

是的,史蒂夫走在了正确的轨道上。这里有两个问题需要解决:

  1. Spring session 仅在响应初始传入请求时将经过身份验证的 session 提交到 redis。因此,第一步是遵循 steve 提供的链接,以确保 Spring 会话在会话更改时提交到 redis。

  2. Zuul 不会在初始路由上传播这个新认证的会话。因此,您需要做的是使用 zuul 预过滤器(周围有很多示例)来获取经过身份验证的会话 ID,然后将其添加到 zuul 请求到网关后面的资源。您将在 zuul 请求中看到设置会话 id 的 setter 方法。

如果您不这样做,您将需要进行两次调用,一次进行身份验证并获取一个有效的会话 ID,该会话 ID 将来自 Spring 会话的 redis,然后使用您经过身份验证的会话 ID 进行后续调用。

我确实为此奋斗了一段时间,但当我让它工作时,它就在现场。我将此解决方案扩展为不仅适用于 http basic,还添加到 jwt 令牌实现中。

希望这会有所帮助,只要我在家中连接,我就可以发布源代码。

祝你好运! 贾斯汀

【讨论】:

    【解决方案3】:

    我的 APIGateway (Zuul) 由 Apache Httpd 代理并受 Mellon module (SAML 2.0) 保护。在身份提供者上成功验证后,mellon 模块将一些标头正确地注入到 SAML 响应中,但第一个请求失败并显示 403 状态代码。

    我也在使用 SpringSecurity,为了解决这个问题,我在安全过滤器链上添加了一个简单的过滤器,以确保正确创建 SecurityContext:

    @Component
    public class MellonFilter extends OncePerRequestFilter {
    
        private final Logger log = LoggerFactory.getLogger(MellonFilter.class);
    
    
        @Override
        protected void doFilterInternal(HttpServletRequest req, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
    
           String mellonId=req.getHeader("mellon-nameid");
    
            if(mellonId==null||mellonId.isEmpty())
                ;//do filterchain
            else {
    
                UserWithRoles userWithRoles = new UserWithRoles();
                userWithRoles.setUsername(mellonId);
                SilUserDetails details = new SilUserDetails(userWithRoles);
    
                SilAuthenticationPrincipal silPrincipal = null;
                Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
    
                authorities.add(new SimpleGrantedAuthority("Some roles");
    
                silPrincipal = new SilAuthenticationPrincipal(details, true, authorities);
                SecurityContextHolder.clearContext();
                SecurityContextHolder.getContext().setAuthentication(silPrincipal);
            }
            filterChain.doFilter(req,httpServletResponse);
        }
    
        @Override
        protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
            if(SecurityContextHolder.getContext().getAuthentication()!=null&&SecurityContextHolder.getContext().getAuthentication() instanceof SilAuthenticationPrincipal)
                return true;
            return false;
    
        }
    }
    

    然后我需要一个 ZuulFilter 来保存会话(在 Redis 上)并传播实际的会话 ID:

    public class ZuulSessionCookieFilter extends ZuulFilter {
    
        private final Logger log = LoggerFactory.getLogger(ZuulSessionCookieFilter.class);
    
        @Autowired
        private SessionRepository repository;
    
        @Override
        public String filterType() {
            return FilterConstants.PRE_TYPE;
        }
    
        @Override
        public int filterOrder() {
            return 0;
        }
    
        @Override
        public boolean shouldFilter() {
    
            return true;
        }
    
        @Override
        public Object run() throws ZuulException {
    
            RequestContext context = RequestContext.getCurrentContext();
    
            HttpSession httpSession = context.getRequest().getSession();
            httpSession.setAttribute(
                    HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
                    SecurityContextHolder.getContext()
            );
            Session session = repository.findById(httpSession.getId());
            context.addZuulRequestHeader("cookie", "SESSION=" + base64Encode(httpSession.getId()));
            log.debug("ZuulPreFilter session proxy: {} and {}", session.getId(),httpSession.getId());
    
            return null;
        }
    
        private static String base64Encode(String value) {
            byte[] encodedCookieBytes = Base64.getEncoder().encode(value.getBytes());
            return new String(encodedCookieBytes);
        }
    }
    

    希望这个解决方案对大家有所帮助。

    【讨论】:

      猜你喜欢
      • 2018-01-12
      • 2020-02-06
      • 1970-01-01
      • 2019-08-30
      • 2019-05-20
      • 1970-01-01
      • 1970-01-01
      • 2019-12-22
      • 2016-07-30
      相关资源
      最近更新 更多