簡體   English   中英

Spring Security在並發環境中獲得錯誤的負責人

[英]Spring security get Wrong Principal in Concurrency Environment

在我的微服務中,我在端口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代碼,在UserInfoTokenServices.java ,我發現此代碼可能會導致錯誤。 當有許多查詢進入時,它們相等時,多線程將以this.restTemplate和accessToken和existingToken的邏輯進行相同的操作,但其他線程可能會在調用restTemplate.getForEntity之前更改this.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");
        }
    }
}

我認為這將導致錯誤的主體信息。

而且事實上。 當我使用此代碼

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

name會突然出錯,然后下次再正確一次。

你們曾經對這種情況感到困惑嗎?

該怎么辦,我可以一直獲取正確的用戶名。

感謝您的關注。

服務器啟動時,如果沒有AuthorizationServerEndpointsConfiguration.class的bean,則執行步驟1,然后轉到步驟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的實現。 這樣可以更好地控制。

我遇到了同樣的問題:並發請求混和了主體。

今天應用了本文的建議: 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;
    }

我的測試表明錯誤已消失,並且主體中沒有發生混搭。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM