在我的微服务中,我在端口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
中,我发现这段代码可能会导致错误。当许多查询进入时,它们的多线程操作相同的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
突然出错,然后再次正确。
你们有没有对这种情况感到困惑?
怎么办我可以随时获得正确的用户名。
感谢您的关注。
答案 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;
}
我的测试表明错误已经消失并且主体没有发生混搭。