具有Spring Session / Redis的Spring Zuul API网关在同一请求中进行身份验证和路由

时间:2016-01-12 18:57:53

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

过去几天我一直在寻找关于如何做到这一点的最高点和最低点,并最终决定承认失败并请求帮助,请!!!

我跟随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并将其添加到标头中,然后重试它,因为现在我的身份验证会话在路由后可用于资源项目。

请有人指出我如何通过网关接听我的电话进行身份验证并在同一请求中路由吗?

由于 贾斯汀

3 个答案:

答案 0 :(得分:9)

我跟随Justin Taylor在不同页面上的帖子,所以这是他的解决方案。在这里找到包含源代码的解决方案是有道理的:

  1. 急切地提交Spring会话 - 从spring-session v1.0开始,有一个注释属性@EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE),可以立即将会话数据保存到Redis中。文档here
  2. 简单的Zuul过滤器,用于将会话添加到当前请求的标题中:
  3. @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;
        }
    
    }
    

    再一次 - 这不是我的解决方案 - 凭据去Justin Taylor。

答案 1 :(得分:3)

我很抱歉这里的延迟回应,关于南非的一个伟大的事情是我们伟大的电信嘿嘿,我家里没有互联网一段时间,我的源代码是在我的家用电脑上。< / p>

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

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

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

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

    我曾经和它做了一段时间的战斗,但是当我开始工作时,它就是现场。我将此解决方案扩展到不仅适用于http basic,而是添加到jwt令牌实现中。

    希望这有帮助,只要我在家连接,我就可以发布消息来源。

    祝你好运! 贾斯汀

答案 2 :(得分:1)

我的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);
    }
}

我希望此解决方案对每个人都有用。