弹簧安全性oauth2使用异步请求

时间:2016-03-30 20:14:18

标签: asynchronous spring-security spring-boot spring-security-oauth2 oauth2

据我所知,SpringSecurityFilter链每次请求@Async请求运行两次,因为它在入站请求线程上运行,传递给在不同线程上运行的异步代码,然后在尝试写入时对于响应线程,SpringSecurityFilter链再次运行。

这导致access_token到期附近出现问题,因为我正在使用RemoteTokenServices,并且会发生原因是对原始请求进行了身份验证,服务活动大约需要一秒钟,然后再次调用RemoteTokenServices,此时access_token已过期,所以请求返回401。

这里推荐的解决方案是什么?我无法阻止SecurityFilterChain在响应线程上第二次运行。我做错了什么,或者这是预期的行为?我看到SecurityContext正确传递到@Async线程,但它在响应线程中为null。

有没有办法确保SecurityFilterChain每个请求只运行一次?或者是每个请求接受多个过滤器调用并以某种方式通过缓存处理它的解决方案?

我使用spring-boot 1.3.3.RELEASE和spring-security-oauth2 2.0.9.RELEASE。

日志:

INFO [..nio-exec-1] [Caching...] loadAuthentication: 0bc97f92-9ebb-411f-9e8e-e7dc137aeffe
DEBUG [..nio-exec-1] [Caching...] Entering CachingRemoteTokenService auth: null
DEBUG [..nio-exec-1] [Audit...] AuditEvent [timestamp=Wed Mar 30 12:27:45 PDT 2016, principal=testClient, type=AUTHENTICATION_SUCCESS, data={details=remoteAddress=127.0.0.1, tokenType=BearertokenValue=<TOKEN>}]
INFO [..nio-exec-1] [Controller] Callable testing request received
DEBUG [MvcAsync1] [TaskService] taskBegin
DEBUG [MvcAsync1] [TaskService] Entering TaskService auth: org.springframework.security.oauth2.provider.OAuth2Authentication@47c78d1a: Principal: testClient; Credentials: [PROTECTED]; Authenticated: true; Details: remoteAddress=127.0.0.1, tokenType=BearertokenValue=<TOKEN>; Granted Authorities: ROLE_CLIENT
DEBUG [MvcAsync1] [TaskService] end of task
INFO [..nio-exec-2] [Caching...] loadAuthentication: 0bc97f92-9ebb-411f-9e8e-e7dc137aeffe
DEBUG [..nio-exec-2] [Caching...] Entering CachingRemoteTokenService auth: null
DEBUG [..nio-exec-2] [RemoteTokenServices] check_token returned error: invalid_token
DEBUG [..nio-exec-2] [Audit...] AuditEvent [timestamp=Wed Mar 30 12:27:47 PDT 2016, principal=access-token, type=AUTHENTICATION_FAILURE, data={type=org.springframework.security.authentication.BadCredentialsException, message=0bc97f92-9ebb-411f-9e8e-e7dc137aeffe}]

相关代码:

控制器:

@RequestMapping(value = "/callable",
method = RequestMethod.GET, 
produces = { MediaType.APPLICATION_JSON_VALUE })
public @ApiResponseObject Callable<ApiResponse> runCallable(HttpServletRequest httpServletRequest)
        throws InterruptedException {
    log.info(String.format("Callable testing request received"));
    Callable<ApiResponse> rv = taskService::execute;
    return rv;
}

异步服务:

    @Override
public ApiResponse execute() {
    log.debug("taskBegin");
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    log.debug("Entering TaskService auth: " + auth);
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    ApiResponse rv = new ApiResponse();
    rv.setStatus(HttpStatus.OK.value());
    log.debug("end of task");
    return rv;
}

RemoteTokenServices实现(注意缓存已注释掉):

    public class CachingRemoteTokenService extends RemoteTokenServices {

    private static Log log = LogFactory.getLog(CachingRemoteTokenService.class);

    @Override
    //@Cacheable(cacheNames="tokens", key="#root.methodName + #accessToken")
    public OAuth2Authentication loadAuthentication(String accessToken)
            throws org.springframework.security.core.AuthenticationException,
                   InvalidTokenException {
        log.info("loadAuthentication: " + accessToken);
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        log.debug("Entering CachingRemoteTokenService auth: " + auth);
        return super.loadAuthentication(accessToken);
    }

    @Override
    //@Cacheable(cacheNames="tokens", key="#root.methodName + #accessToken")
    public OAuth2AccessToken readAccessToken(String accessToken) {
        log.info("readAccessToken: " + accessToken);
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        log.debug("Entering CachingRemoteTokenService auth: " + auth);
        return super.readAccessToken(accessToken);
    }
}

最后我的安全配置:

 @Configuration
public class Oauth2ResourceConfig {

    private static Log log = LogFactory.getLog(Oauth2ResourceConfig.class);

    @Value("${client.secret}") 
    private String clientSecret;

    @Value("${check.token.endpoint}") 
    private String checkTokenEndpoint;

    @Bean
    @Lazy
    public ResourceServerTokenServices tokenService() {
        CachingRemoteTokenService tokenServices = new CachingRemoteTokenService();
        tokenServices.setClientId("test-service");
        tokenServices.setClientSecret(clientSecret);
        tokenServices.setCheckTokenEndpointUrl(checkTokenEndpoint);

        return tokenServices;
    }

    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfig extends ResourceServerConfigurerAdapter {

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                .antMatchers("/health-check").permitAll()
                .antMatchers("/**").access("#oauth2.isClient() and #oauth2.hasScope('trust')");

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.resourceId("test-service");
        }
    }
}

1 个答案:

答案 0 :(得分:0)

在这里得到答案:https://github.com/spring-projects/spring-security-oauth/issues/736

显然修复是配置security.filter-dispatcher-types=REQUEST, ERROR