10年 Java程序员,硬核人生!勇往直前,永不退缩!

欢迎围观我的git:https://github.com/R1310328554/spring_security_learn 寻找志同道合的有志于研究技术的朋友,关注本人微信公众号: 觉醒的码农,或Q群 165874185

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

oauth2 client访问oauth2 server 的user info 端点; 返回401 invalid_user_info_response

日志是: invalid_user_info_response] An error occurred while attempting to retrieve the UserInfo Resource: 401 null]
onAuthenticationFailure request = [org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterRequest@50eeb3fd], response = [org.springframework.security.web.header.HeaderWriterFilter$HeaderWriterResponse@5e294790], exception = [org.springframework.security.oauth2.core.OAuth2AuthenticationException: [invalid_user_info_response] An error occurred while attempting to retrieve the UserInfo Resource: 401 null]

通过抓包,发现,Cannot convert access token to JSON

具体是

请求:
GET http://192.168.1.103:8081/auth/user/me HTTP/1.1
Accept: application/json
Authorization: Bearer 036685d2-ed5a-48c7-a5c7-a135a9e49468
User-Agent: Java/1.8.0_231
Host: 192.168.1.103:8081
Connection: keep-alive

响应:
HTTP/1.1 401
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: *
Access-Control-Max-Age: 3600
Access-Control-Allow-Headers: x-requested-with, authorization
Pragma: no-cache
WWW-Authenticate: Bearer realm="oauth2-resource", error="invalid_token", error_description="Cannot convert access token to JSON"
Cache-Control: no-store
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8
Date: Wed, 13 Jul 2022 08:02:17 GMT
Content-Length: 83

{"error":"invalid_token","error_description":"Cannot convert access token to JSON"}

测试发现就是,如果把 自己注册的TokenStore 去掉就好了,为什么呢?

@Bean  
public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
    return new JwtTokenStore(jwtAccessTokenConverter);
}

调试半天,终于发现:

@Configuration
public class ResourceServerConfiguration extends WebSecurityConfigurerAdapter implements Ordered {

    private int order = 3;

    @Autowired(required = false)
    private TokenStore tokenStore;

    @Autowired(required = false)
    private AuthenticationEventPublisher eventPublisher;

    @Autowired(required = false)
    private Map<String, ResourceServerTokenServices> tokenServices;

    @Autowired
    private ApplicationContext context;

    private List<ResourceServerConfigurer> configurers = Collections.emptyList();

    @Autowired(required = false)
    private AuthorizationServerEndpointsConfiguration endpoints;
    
    ...
}

原来是 ResourceServerConfiguration里面有@Autowired,把我创建的JwtTokenStore注册进去了,但是呢

调试发现其实有多个 DefaultTokenServices实例, 有的是使用InMemoryTokenStore(测试发现是 AuthorizationServer), ResourceServer使用的是JwtTokenStore

这样就出问题了,AuthorizationServer 返回的是普通的access_token:

然后oauth2 client 使用它访问oauth2 server 的user info 端点, 然后ResourceServer使用JwtTokenStore 来解析它,尝试从其中获取 用户信息,结果就自然解析不了,报错了!

注意:
AuthorizationServerEndpointsConfigurer、 ResourceServerSecurityConfigurer 各有各自的 tokenStore,两者最好是相同。

解决方法是,在配置自定义的AuthorizationServerConfigurerAdapter的时候,给endpoints的tokenStore也设置为相同jwtTokenStore, 这样就不会有转换问题了

public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .accessTokenConverter(jwtAccessTokenConverter)
                .tokenStore(jwtTokenStore) 
        ;
        super.configure(endpoints);
    }
    ...

更正

测试发现,仅仅配置.tokenStore(jwtTokenStore) 是不行的,其实只需要配置 accessTokenConverter 为jwtAccessTokenConverter即可;也就是:

public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .accessTokenConverter(jwtAccessTokenConverter) 
        ;
        super.configure(endpoints);
    }
    ...

why,因为accessTokenConverter 才是真正复杂发放 accessToken 的地方, 而且注意AuthorizationServerEndpointsConfigurer的tokenStore()
tokenStore() 其实会尝试根据accessTokenConverter生成tokenStore, 如果JwtAccessTokenConverter则生成JwtTokenStore

	private TokenStore tokenStore() {
		if (tokenStore == null) {
			if (accessTokenConverter() instanceof JwtAccessTokenConverter) {
				this.tokenStore = new JwtTokenStore((JwtAccessTokenConverter) accessTokenConverter());
			}
			else {
				this.tokenStore = new InMemoryTokenStore();
			}
		}
		return this.tokenStore;
	}

然后就可以看到oauth/token端点返回正确的jwt格式的token了!

请求:
POST http://192.168.1.103:8081/auth/oauth/token HTTP/1.1
Accept: application/json;charset=UTF-8
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Authorization: Basic UjJkcHhRM3ZQcnRmZ0Y3MjpmRHc3TXBrazVjekhOdVNSdG1oR21BR0w0MkNheFFCOQ==
User-Agent: Java/1.8.0_231
Host: 192.168.1.103:8081
Connection: keep-alive
Content-Length: 98

grant_type=authorization_code&code=3Xfg5J&redirect_uri=http%3A%2F%2F192.168.1.103%3A8082%2FdoLogin

响应: 
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NTc3NDI5OTcsInVzZXJfbmFtZSI6ImEiLCJhdXRob3JpdGllcyI6WyJST0xFX1VTRVIiXSwianRpIjoiODNjNzNkYWEtMWM2OC00NTBmLWI0MDEtZWU3YjRhZWQ5ZDMyIiwiY2xpZW50X2lkIjoiUjJkcHhRM3ZQcnRmZ0Y3MiIsInNjb3BlIjpbInVzZXJfaW5mbyJdfQ.mq1tHm4DMOgIgIvQd1JcoKhqDPR79cxOLV9g3sKnv8U","token_type":"bearer","expires_in":43152,"scope":"user_info","jti":"83c73daa-1c68-450f-b401-ee7b4aed9d32"}
posted on 2022-07-15 17:22  CanntBelieve  阅读(1277)  评论(0编辑  收藏  举报