spring security oauth2授权服务刷新令牌报错UserDetailsService is required
刷新令牌流程
- 调用刷新令牌端点
- org.springframework.security.oauth2.provider.endpoint.TokenEndpoint
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
...
if (isRefreshTokenRequest(parameters)) {
// A refresh token has its own default scopes, so we should ignore any added by the factory here.
tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));
}
// 获取新令牌
OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
if (token == null) {
throw new UnsupportedGrantTypeException("Unsupported grant type");
}
return getResponse(token);
}
- 根据grant_type=refresh_token,获取RefreshTokenGranter,并调用TokenGranter.grant
- RefreshTokenGranter
- org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter
public class RefreshTokenGranter extends AbstractTokenGranter {
...
@Override
protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
String refreshToken = tokenRequest.getRequestParameters().get("refresh_token");
return getTokenServices().refreshAccessToken(refreshToken, tokenRequest);
}
}
- DefaultTokenServices 通过refresh_token获取认证信息,并创建预认证token,以预认证形式尝试获取userdetails
- org.springframework.security.oauth2.provider.token.DefaultTokenServices
@Transactional(noRollbackFor={InvalidTokenException.class, InvalidGrantException.class})
public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest)
throws AuthenticationException {
if (!supportRefreshToken) {
throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);
}
OAuth2RefreshToken refreshToken = tokenStore.readRefreshToken(refreshTokenValue);
if (refreshToken == null) {
throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);
}
OAuth2Authentication authentication = tokenStore.readAuthenticationForRefreshToken(refreshToken);
if (this.authenticationManager != null && !authentication.isClientOnly()) {
// The client has already been authenticated, but the user authentication might be old now, so give it a
// chance to re-authenticate.
// 此处创建预认证token,并通过authenticationManager认证此token获取userdetails
Authentication user = new PreAuthenticatedAuthenticationToken(authentication.getUserAuthentication(), "", authentication.getAuthorities());
user = authenticationManager.authenticate(user);
Object details = authentication.getDetails();
authentication = new OAuth2Authentication(authentication.getOAuth2Request(), user);
authentication.setDetails(details);
}
String clientId = authentication.getOAuth2Request().getClientId();
if (clientId == null || !clientId.equals(tokenRequest.getClientId())) {
throw new InvalidGrantException("Wrong client for this refresh token: " + refreshTokenValue);
}
// clear out any access tokens already associated with the refresh
// token.
tokenStore.removeAccessTokenUsingRefreshToken(refreshToken);
if (isExpired(refreshToken)) {
tokenStore.removeRefreshToken(refreshToken);
throw new InvalidTokenException("Invalid refresh token (expired): " + refreshToken);
}
authentication = createRefreshedAuthentication(authentication, tokenRequest);
if (!reuseRefreshToken) {
tokenStore.removeRefreshToken(refreshToken);
refreshToken = createRefreshToken(authentication);
}
OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
tokenStore.storeAccessToken(accessToken, authentication);
if (!reuseRefreshToken) {
tokenStore.storeRefreshToken(accessToken.getRefreshToken(), authentication);
}
return accessToken;
}
5.PreAuthenticatedAuthenticationProvider 获取userdetails
- org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
...
UserDetails ud = preAuthenticatedUserDetailsService
.loadUserDetails((PreAuthenticatedAuthenticationToken) authentication);
userDetailsChecker.check(ud);
PreAuthenticatedAuthenticationToken result = new PreAuthenticatedAuthenticationToken(
ud, authentication.getCredentials(), ud.getAuthorities());
result.setDetails(authentication.getDetails());
return result;
}
默认配置情况
- 默认tokenServices配置
- org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer
private AuthorizationServerTokenServices tokenServices() {
if (tokenServices != null) {
return tokenServices;
}
this.tokenServices = createDefaultTokenServices();
return tokenServices;
}
public AuthorizationServerTokenServices getDefaultAuthorizationServerTokenServices() {
if (defaultTokenServices != null) {
return defaultTokenServices;
}
this.defaultTokenServices = createDefaultTokenServices();
return this.defaultTokenServices;
}
private DefaultTokenServices createDefaultTokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setReuseRefreshToken(reuseRefreshToken);
tokenServices.setClientDetailsService(clientDetailsService());
tokenServices.setTokenEnhancer(tokenEnhancer());
addUserDetailsService(tokenServices, this.userDetailsService);
return tokenServices;
}
- 默认PreAuthenticatedAuthenticationProvider配置
- org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer
private ClientDetailsService clientDetailsService() {
if (clientDetailsService == null) {
this.clientDetailsService = new InMemoryClientDetailsService();
}
if (this.defaultTokenServices != null) {
addUserDetailsService(defaultTokenServices, userDetailsService);
}
return this.clientDetailsService;
}
private void addUserDetailsService(DefaultTokenServices tokenServices, UserDetailsService userDetailsService) {
if (userDetailsService != null) {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>(
userDetailsService));
tokenServices
.setAuthenticationManager(new ProviderManager(Arrays.<AuthenticationProvider> asList(provider)));
}
}
- 默认UserDetailsService配置
- org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerSecurityConfiguration
@Override
protected void configure(HttpSecurity http) throws Exception {
...
// 如果没有配置UserDetailsService
if (!endpoints.getEndpointsConfigurer().isUserDetailsServiceOverride()) {
UserDetailsService userDetailsService = http.getSharedObject(UserDetailsService.class);
endpoints.getEndpointsConfigurer().userDetailsService(userDetailsService);
}
...
}
解决方案
3种方案选一即可
- AuthorizationServer增加配置UserDetailsService
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
UserDetailsService userDetailsService;
...
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
...
endpoints.userDetailsService(userDetailsService);
...
}
...
}
- WebSecurity将UserDetailsService注入AuthorizationServerEndpointsConfigurer
此方案需要AuthorizationServerConfig 的Order高于WebSecurityConfig,否则无法注入
@Autowired
private AuthorizationServerEndpointsConfiguration endpoints;
@Override
protected void configure(HttpSecurity http) throws Exception {
if (!endpoints.getEndpointsConfigurer().isUserDetailsServiceOverride()) {
UserDetailsService userDetailsService = http.getSharedObject(UserDetailsService.class);
endpoints.getEndpointsConfigurer().userDetailsService(userDetailsService);
}
...
}
- AuthorizationServer增加配置自定义TokenService