[英]Spring security get Wrong Principal in Concurrency Environment
在我的微服務中,我在端口19000處有一個ResourceServer
和AuthServer
。
在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.