【问题标题】:Shared SecurityContextHolder across multiple WebSecurityConfigurerAdapters跨多个 WebSecurityConfigurerAdapter 共享 SecurityContextHolder
【发布时间】:2019-10-26 06:41:36
【问题描述】:

上下文:我正在编写一个 Web 应用程序,旨在为两个不同的 API 提供服务,每个 API 都有自己的身份验证过滤器。两个过滤器都处理和验证 JWT 令牌,但是令牌本身包含不同的有效负载并且来自不同的身份验证源。此外,即使令牌不同且来源不同,但它们共享相同的登录凭据,并使用相同的密钥进行保护。

即:您可以使用相同的凭据访问这些身份验证 URL 中的任何一个,然后返回具有完全不同负载的不同 JWT 令牌。

/auth/auth1/login /auth/auth2/登录

问题: 我遇到的问题是,如果我对其中一个进行身份验证,我就可以访问另一个。即使不提供令牌。

意思是如果我去 /requests/something,使用来自身份验证提供程序 1 的承载令牌进行身份验证,然后转到 /ims/oneroster/v1p1/somethingElse(不从身份验证提供程序 2 传递不同的令牌)我能够访问数据,即使我没有使用与该路径关联的过滤器进行身份验证。

目前,我知道确保每个过滤器正确检查用户令牌是否有效的唯一方法是将 SecurityContextHolder.clearContext(); 放在每个过滤器的顶部过滤器 doFilterInternal 方法。但是,我很确定我不应该这样做。

谁能看到我下面的问题,或者提出一些建议?

SecurityConfig.class

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
    //https://docs.spring.io/spring-security/site/docs/current/reference/html/jc.html#multiple-httpsecurity

    @Configuration @Order(1)
    public static class XPressWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
        private final CacheService cacheService;

        public XPressWebSecurityConfigurationAdapter(CacheService cacheService) {this.cacheService = cacheService;}

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.antMatcher("/requests/**")
                    .authorizeRequests().anyRequest().authenticated()
                    .and()
                        .addFilter(new JWTAuthorizationFilter(authenticationManagerBean(), cacheService))
                            //.exceptionHandling().authenticationEntryPoint(new JWTAuthenticationEntryPoint())
            ;
        }
    }

    @Configuration @Order(2)
    public static class OneRosterWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
        private final CacheService cacheService;

        public OneRosterWebSecurityConfigurationAdapter(CacheService cacheService) {this.cacheService = cacheService;}

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.antMatcher("/ims/oneroster/v1p1/**")
                    .authorizeRequests().anyRequest().authenticated()
                    .and().addFilter(new OneRosterAuthorizationFilter(authenticationManagerBean(), cacheService))
            ;
        }
    }
}

OneRosterAuthorizationFilter.class

public class OneRosterAuthorizationFilter extends BasicAuthenticationFilter {
    private final CacheService cacheService;

    public OneRosterAuthorizationFilter(AuthenticationManager authManager, CacheService cacheService) {
        super(authManager);
        this.cacheService = cacheService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
        logger.debug("GOING TO ONEROSTER FILTER");

        AuthRequest authRequest = new AuthRequest(req);
        if(authRequest.isAuthEnabled()) {
            if(authRequest.isHeader() || (authRequest.isParameter() && authRequest.isAllowTokenParameter())) {
                UsernamePasswordAuthenticationToken authentication = getAuthentication(req, authRequest);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }
        chain.doFilter(req, res);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest req, AuthRequest authRequest) {
        if(StringUtils.isBlank(authRequest.getToken())) {
            return null;  //Token was blank... 403 Forbidden
        }

        DecodedToken decodedToken = TokenDecoder.decodeToken(authRequest.getToken());

        Application application = null;
        if(decodedToken != null) {
            application = new Application(decodedToken.getAppId(), decodedToken.getToken(), cacheService);
        }

        try {
                if(!System.getenv("provider_id").equalsIgnoreCase(decodedToken.getProviderId())) {
                    throw new JWTVerificationException("Provider Ids Don't Match....");
                }

                if(application != null && StringUtils.isNotBlank(application.getApp().getProviderSecret())) {
                JWT.require(Algorithm.HMAC256(application.getApp().getProviderSecret().getBytes()))
                        .withIssuer(PropertiesLoader.getInstance().getProperty("security.auth.jwt.issuer"))
                        .build().verify(authRequest.getToken());
                return new UsernamePasswordAuthenticationToken(application, decodedToken.getToken(), getACLs(application));
            }
        }
        catch (JWTVerificationException exception) {
            //https://medium.com/fullstackblog/spring-security-jwt-token-expired-custom-response-b85437914b81
            req.setAttribute("JWTVerificationException", exception.getMessage());
            return null;
        }
        return null; //DecodedToken or Application was null... 403 Forbidden
    }


    private Collection<GrantedAuthority> getACLs(Application application) {
        Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        application.getPermissions().forEach(pathPermission -> {
            if(pathPermission.getGet()) {
                grantedAuthorities.add(new SimpleGrantedAuthority("get:" + pathPermission.getPath()));
            }
            if(pathPermission.getPost()) {
                grantedAuthorities.add(new SimpleGrantedAuthority("post:" + pathPermission.getPath()));
            }
            if(pathPermission.getPut()) {
                grantedAuthorities.add(new SimpleGrantedAuthority("put:" + pathPermission.getPath()));
            }
            if(pathPermission.getDelete()) {
                grantedAuthorities.add(new SimpleGrantedAuthority("delete:" + pathPermission.getPath()));
            }
        });
        return grantedAuthorities;
    }
}

JWTAuthorizationFilter.class

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
    private final CacheService cacheService;

    public JWTAuthorizationFilter(AuthenticationManager authManager, CacheService cacheService) {
        super(authManager);
        this.cacheService = cacheService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
        logger.debug("GOING TO JWT FILTER");

        AuthRequest authRequest = new AuthRequest(req);
        if(authRequest.isAuthEnabled()) {
            if(authRequest.isHeader() || (authRequest.isParameter() && authRequest.isAllowTokenParameter())) {
                UsernamePasswordAuthenticationToken authentication = getAuthentication(req, authRequest);
                if(authentication != null) {
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        chain.doFilter(req, res);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest req, AuthRequest authRequest) {
        if(StringUtils.isBlank(authRequest.getToken())) {
            return null;  //Token was blank... 403 Forbidden
        }

        DecodedToken decodedToken = TokenDecoder.decodeToken(authRequest.getToken());

        Application application = null;
        if(decodedToken != null) {
            application = new Application(decodedToken.getApplication_id(), decodedToken.getToken(), cacheService);
        }

        try {
            if(application != null && StringUtils.isNotBlank(application.getApp().getProviderSecret())) {
                JWT.require(Algorithm.HMAC256(application.getApp().getProviderSecret().getBytes()))
                        .withIssuer(PropertiesLoader.getInstance().getProperty("security.auth.jwt.issuer"))
                        .build().verify(authRequest.getToken());
                return new UsernamePasswordAuthenticationToken(application, decodedToken.getToken(), getACLs(application));
            }
        }
        catch (JWTVerificationException exception) {
            //https://medium.com/fullstackblog/spring-security-jwt-token-expired-custom-response-b85437914b81
            req.setAttribute("JWTVerificationException", exception.getMessage());
            return null;
        }
        return null; //DecodedToken or Application was null... 403 Forbidden
    }


    private Collection<GrantedAuthority> getACLs(Application application) {
        Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        application.getPermissions().forEach(pathPermission -> {
            if(pathPermission.getGet()) {
                grantedAuthorities.add(new SimpleGrantedAuthority("get:" + pathPermission.getPath()));
            }
            if(pathPermission.getPost()) {
                grantedAuthorities.add(new SimpleGrantedAuthority("post:" + pathPermission.getPath()));
            }
            if(pathPermission.getPut()) {
                grantedAuthorities.add(new SimpleGrantedAuthority("put:" + pathPermission.getPath()));
            }
            if(pathPermission.getDelete()) {
                grantedAuthorities.add(new SimpleGrantedAuthority("delete:" + pathPermission.getPath()));
            }
        });
        return grantedAuthorities;
    }
}
Note: Application is the class that implements UserDetails

【问题讨论】:

  • 您需要 HTTP 会话吗?如果你的服务是无状态的,你就不会有这种问题。
  • 目前我认为我不需要会话。但是假设我在未来做这件事会有多困难呢?另外,如果我可以让它无状态,我将如何解决当前问题?谢谢。
  • 要使其无状态,您必须更改会话策略,请参阅SessionCreationPolicy。您还必须确保您的应用程序不会自行打开(您的代码)。
  • 使用会话的最佳方式是将您的应用程序拆分为两个应用程序(在同一个容器或两个不同的容器中运行)。
  • 谢谢@dur。我想我现在会尝试采用无状态方法,如果结果证明我需要会话,我会将它们分开。

标签: spring spring-boot spring-security


【解决方案1】:

在阅读了 dur 的评论后,这是我的问题的解决方案。我需要做的就是添加:

.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

到每个 HttpSecurity 对象。

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
    //https://docs.spring.io/spring-security/site/docs/current/reference/html/jc.html#multiple-httpsecurity

    @Configuration @Order(1)
    public static class XPressWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
        private final CacheService cacheService;

        public XPressWebSecurityConfigurationAdapter(CacheService cacheService) {this.cacheService = cacheService;}

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            //https://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/
            http.antMatcher("/requests/**")
                    .authorizeRequests().anyRequest().authenticated()
                    .and().addFilter(new JWTAuthorizationFilter(authenticationManagerBean(), cacheService))
                        .exceptionHandling().authenticationEntryPoint(new JWTAuthenticationEntryPoint())
                    .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        }
    }

    @Configuration @Order(2)
    public static class OneRosterWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
        private final CacheService cacheService;

        public OneRosterWebSecurityConfigurationAdapter(CacheService cacheService) {this.cacheService = cacheService;}

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.antMatcher("/ims/oneroster/v1p1/**")
                    .authorizeRequests().anyRequest().authenticated()
                    .and().addFilter(new OneRosterAuthorizationFilter(authenticationManagerBean(), cacheService))
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        }
    }
}

【讨论】:

    猜你喜欢
    • 2016-04-26
    • 1970-01-01
    • 2014-01-18
    • 2016-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多