• spring security oauth2授权服务刷新令牌报错UserDetailsService is required


    刷新令牌流程

    1. 调用刷新令牌端点
    • 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);
    }
    
    1. 根据grant_type=refresh_token,获取RefreshTokenGranter,并调用TokenGranter.grant
    2. 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);
    	}	
    }
    
    1. 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;
    }
    

    默认配置情况

    1. 默认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;
    }
    
    1. 默认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)));
    	}
    }
    
    1. 默认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种方案选一即可

    1. AuthorizationServer增加配置UserDetailsService
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    	@Autowired
        UserDetailsService userDetailsService;
    	...
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        	...
            endpoints.userDetailsService(userDetailsService);
            ...
        }
        ...
    }
    
    1. 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);
        }
        ...
    }
    
    1. AuthorizationServer增加配置自定义TokenService
  • 相关阅读:
    innodb的存储结构
    使用zabbix邮件发送报表
    如何使用yum下载rpm包
    redis cluster节点管理测试
    redis迁移工具-redis-migrate-tool使用测试
    redis客户端连接异常
    redis sentinel基本命令与参数
    [转]redis-cli的一些有趣也很有用的功能
    [转]为什么使用 Redis及其产品定位
    Redis多机常用架构-cluster
  • 原文地址:https://www.cnblogs.com/luguojun/p/14294690.html
Copyright © 2020-2023  润新知