Spring安全性在并发环境中获得错误的主体

时间:2018-04-04 09:43:11

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

在我的微服务中,我在端口19000处有ResourceServerAuthServer

ResourceServer中,这是application.yml

的一部分
security:
  oauth2:
    resource:
      id: gateway
      user-info-uri: http://localhost:19000/user
      prefer-token-info: false

/ user端点很简单,就像这样

@RestController
@RequestMapping("/")
public class UserController {

    @GetMapping(value = "/user")
    public Principal getUser(Principal user) {
        return user;
    }
}

我将在ResourceServer中获取UserDetail,使用此代码

void me(Principal principal) {
    String name = principal.getName();    
}

一开始,name始终是正确的名称。但是如果userA和userB几乎同时用他们的令牌访问界面,那么事情就出错了。有时,除了userB'名称,我将获得userA的名称。

我查看了spring安全源代码,在UserInfoTokenServices.java中,我发现这段代码可能会导致错误。当许多查询进入时,它们的多线程操作相同的this.restTemplate,以及accessToken和existingToken的逻辑,当它们相等时,但是其他线程可能会在调用this.restTemplate之前更改restTemplate.getForEntity }

private Map<String, Object> getMap(String path, String accessToken) {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Getting user info from: " + path);
        }
        try {
            OAuth2RestOperations restTemplate = this.restTemplate;
            if (restTemplate == null) {
                BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();
                resource.setClientId(this.clientId);
                restTemplate = new OAuth2RestTemplate(resource);
            }
            OAuth2AccessToken existingToken = restTemplate.getOAuth2ClientContext()
                    .getAccessToken();
            if (existingToken == null || !accessToken.equals(existingToken.getValue())) {
                DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(
                        accessToken);
                token.setTokenType(this.tokenType);
                restTemplate.getOAuth2ClientContext().setAccessToken(token);
            }
            return restTemplate.getForEntity(path, Map.class).getBody();
        }
        catch (Exception ex) {
            this.logger.warn("Could not fetch user details: " + ex.getClass() + ", "
                    + ex.getMessage());
            return Collections.<String, Object>singletonMap("error",
                    "Could not fetch user details");
        }
    }
}

我认为这会导致错误的校长信息。

事实上。当我使用这段代码时

Principal principal = SecurityContextHolder.getContext().getAuthentication();
String name = principal.getName();

name突然出错,然后再次正确。

你们有没有对这种情况感到困惑?

怎么办我可以随时获得正确的用户名。

感谢您的关注。

2 个答案:

答案 0 :(得分:0)

服务器启动时

步骤1如果没有AuthorizationServerEndpointsConfiguration.class的bean,将转到第2步

步骤2:如果没有ResourceServerTokenServices.class的bean ,运行以下代码:

@Bean
@ConditionalOnMissingBean(ResourceServerTokenServices.class)
public UserInfoTokenServices userInfoTokenServices() {
    UserInfoTokenServices services = new UserInfoTokenServices(
            this.sso.getUserInfoUri(), this.sso.getClientId());
    services.setRestTemplate(this.restTemplate);
    services.setTokenType(this.sso.getTokenType());
    if (this.authoritiesExtractor != null) {
        services.setAuthoritiesExtractor(this.authoritiesExtractor);
    }
    if (this.principalExtractor != null) {
        services.setPrincipalExtractor(this.principalExtractor);
    }
    return services;
}

所以ResourceServerTokenServices是单例,所以它是restTemplate

当程序运行到下面的代码中时,多线程将并发运行restTemplate。会出现一些错误。

 private Map<String, Object> getMap(String path, String accessToken) {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Getting user info from: " + path);
        }
        try {
            OAuth2RestOperations restTemplate = this.restTemplate;
            if (restTemplate == null) {
                BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();
                resource.setClientId(this.clientId);
                restTemplate = new OAuth2RestTemplate(resource);
            }
            OAuth2AccessToken existingToken = restTemplate.getOAuth2ClientContext()
                    .getAccessToken();
            if (existingToken == null || !accessToken.equals(existingToken.getValue())) {
                DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(
                        accessToken);
                token.setTokenType(this.tokenType);
                restTemplate.getOAuth2ClientContext().setAccessToken(token);
            }
            return restTemplate.getForEntity(path, Map.class).getBody();
        }
        catch (Exception ex) {
            this.logger.warn("Could not fetch user details: " + ex.getClass() + ", "
                    + ex.getMessage());
            return Collections.<String, Object>singletonMap("error",
                    "Could not fetch user details");
        }
    } 

正确的方法是:如果ResourceServer没有AuthorizationServerEndpointsConfiguration,那么最好提供ResourceServerTokenServices.class的实现。这将得到更好的控制。

答案 1 :(得分:0)

我遇到了同样的问题:并发请求混和了主体。

今天从本文中获得了建议:https://www.baeldung.com/spring-security-oauth2-authentication-with-reddit

1)添加了@EnableOAuth2Client批注,

2)添加了OAuth2ClientContext clientContext来重新放置铭牌。

我最终的RestTemplate bean看起来是这样的:

    @Bean
    @LoadBalanced
    public OAuth2RestOperations restTemplate(UserInfoTokenServices remoteTokenServices,
                                             OAuth2ClientContext clientContext) {
        ClientCredentialsResourceDetails resourceDetails = new ClientCredentialsResourceDetails();
        resourceDetails.setClientId(securityProperties.getServiceClientId());
        resourceDetails.setClientSecret(securityProperties.getServiceClientSecret());
        OAuth2RestOperations restTemplate = new OAuth2RestTemplate(resourceDetails, clientContext);
        remoteTokenServices.setRestTemplate(restTemplate);
        return restTemplate;
    }

我的测试表明错误已经消失并且主体没有发生混搭。