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

Spring Zuul API Gateway with Spring Session / Redis Authenticate and Route in same Request

这几天真是苦思冥想怎么办,终于决定认输求助,拜托!!!

我遵循了 Dave Syer 博士关于 Angular 和 Spring 安全性的教程,特别是 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)

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

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.

访问经过身份验证的 session 的资源

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

但是,如果我遇到错误,请获取 session id 并将其添加到 header 并重试,因为现在我的已验证 session 可用于资源项目路由后。

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

谢谢 贾斯汀

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

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

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

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

如果您不这样做,您将需要进行两次调用,一次是进行身份验证并获取一个有效的会话 ID,该会话 ID 位于 spring 会话的 redis 中,然后是随后的调用经过身份验证的会话 ID。

我确实为此苦苦挣扎了一段时间,但是当我让它工作时,它就出现了。我扩展了这个解决方案,使其不仅适用于 http basic,而且添加到 jwt 令牌实现中。

希望这对您有所帮助,只要我在家连上网络,我就可以 post 来源。

祝你好运! 贾斯汀

我关注了 Justin Taylor 在不同页面上的帖子,所以这是他的解决方案。在此处提供源代码解决方案对我来说很有意义:

  1. 使 Spring Session 热切提交 - 因为 spring-session v1.0 有注释 属性 @EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE) 将 session 数据保存到Redis 立即。文档 here.
  2. 用于将 session 添加到当前请求的 header:
  3. 的简单 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;
    }

}

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

我的 APIGateway (Zuul) 由 Apache Httpd 代理并受 Mellon module (SAML 2.0) 保护。在身份提供者上成功验证后,mellon 模块正确地注入一些 headers 读入 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 来保存 session(在 Redis 上)并传播实际的 session 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);
    }
}

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