从 JWT 令牌获取 UserInfo 信息并在 Spring Boot OAuth2 自动配置上仅使用 JWK 验证
Posted
技术标签:
【中文标题】从 JWT 令牌获取 UserInfo 信息并在 Spring Boot OAuth2 自动配置上仅使用 JWK 验证【英文标题】:Getting UserInfo information from a JWT token and using only JWK validation on Spring Boot OAuth2 autoconfiguration 【发布时间】:2019-12-15 09:54:39 【问题描述】:我正在创建一个 RESTful 服务,该服务使用 OAuth2 机制和外部 Keycloak 用户身份验证服务器 (UAA) 对所有传入请求进行身份验证。
该服务充当资源服务器,使用 @EnableResourceServer
并具有以下配置:
@Configuration
@EnableResourceServer
@Order(0)
public class SecurityConfig extends WebSecurityConfigurerAdapter
private final ResourceServerTokenServices resourceServerTokenServices;
@Autowired
public SecurityConfig(ResourceServerTokenServices resourceServerTokenServices)
this.resourceServerTokenServices = resourceServerTokenServices;
@Override
protected void configure(HttpSecurity http) throws Exception
http.authorizeRequests()
.antMatchers("/actuator/health").permitAll()
.anyRequest().authenticated().and().addFilterAfter(oAuth2AuthenticationProcessingFilter(), AbstractPreAuthenticatedProcessingFilter.class);
private OAuth2AuthenticationProcessingFilter oAuth2AuthenticationProcessingFilter()
OAuth2AuthenticationProcessingFilter oAuth2AuthenticationProcessingFilter = new OAuth2AuthenticationProcessingFilter();
oAuth2AuthenticationProcessingFilter.setAuthenticationManager(oauthAuthenticationManager());
oAuth2AuthenticationProcessingFilter.setStateless(false);
return oAuth2AuthenticationProcessingFilter;
private AuthenticationManager oauthAuthenticationManager()
OAuth2AuthenticationManager oAuth2AuthenticationManager = new OAuth2AuthenticationManager();
oAuth2AuthenticationManager.setResourceId("country-microservice");
oAuth2AuthenticationManager.setTokenServices(resourceServerTokenServices);
oAuth2AuthenticationManager.setClientDetailsService(null);
return oAuth2AuthenticationManager;
我还使用以下依赖项来包含 Spring Security OAuth2:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
</dependencies>
用户在 UAA 上进行身份验证以获得 JWT 令牌,他们必须使用该令牌来调用我正在创建的服务。 JWT 令牌本身包含用户信息:
...
"realm_access":
"roles": [
"user"
]
,
"scope": "profile email",
"email_verified": true,
"name": "Test Derp",
"preferred_username": "user1",
"given_name": "Test",
"family_name": "Derp",
"email": "test@test.com"
为避免向 UAA 发出另一个请求,该服务使用 JWK 来验证传入的令牌。我正在使用 Keycloak 的证书端点设置 security.oauth2.resource.jwk.key-set-uri
属性:
security.oauth2.resource.jwk.key-set-uri=http://localhost:9080/auth/realms/dev/protocol/openid-connect/certs
问题在于 Spring 没有获取在 JWT 令牌上找到的用户信息并将其填充到 Authentication
对象中。
我有以下控制器来返回主体信息:
@RestController
@RequestMapping(path = "/user", produces = MediaType.APPLICATION_JSON_VALUE)
public class UserController
@GetMapping
public Object getUser(Authentication authentication)
if (authentication != null)
return authentication.getPrincipal();
return null;
Authentication
对象在 getUser
函数中以 null 传递(使用 JWK 验证)。
我尝试使用以下配置自定义JWKTokenStore
和JWTAccessTokenConverter
,但没有成功:
@Configuration
public class JwkStoreConfig
private final ResourceServerProperties resource;
@Autowired
public JwkStoreConfig(ResourceServerProperties resource)
this.resource = resource;
@Bean
@Primary
public JwtAccessTokenConverter accessTokenConverter()
return new JwtAccessTokenConverter();
@Bean
public DefaultTokenServices jwkTokenServices(TokenStore jwkTokenStore)
DefaultTokenServices services = new DefaultTokenServices();
services.setTokenStore(jwkTokenStore);
return services;
@Bean
public TokenStore jwkTokenStore(JwtAccessTokenConverter jwtAccessTokenConverter)
JwkTokenStore jwkTokenStore = new JwkTokenStore(this.resource.getJwk().getKeySetUri(), jwtAccessTokenConverter);
return jwkTokenStore;
到目前为止,唯一有效的解决方案是忘记使用 JWK 并更改服务以使用 Keycloak 的 UserInfo 来验证传入令牌,使用 security.oauth2.resource.user-info-uri
属性并删除 JWK URI 属性:
security.oauth2.resource.user-info-uri=http://localhost:9080/auth/realms/dev/protocol/openid-connect/userinfo
使用此属性集,Authentication
对象将与用户信息一起传递给控制器,但这会使服务在每次需要验证传入令牌时都请求 UAA。
有什么想法吗?
谢谢。 问候。
【问题讨论】:
【参考方案1】:这方面的文档是 here,虽然对我来说并不完全清楚,至少在我的设置中它不能按预期工作。
我的理解是这两个属性应该一起使用:
security.oauth2.resource.jwt.key-uri: http://localhost:9191/auth/realms/master
security.oauth2.resource.jwk.key-set-uri: http://localhost:9191/auth/realms/master/protocol/openid-connect/certs
不过,这对我来说失败了,日志消息有点不合时宜:
Authentication request failed: error="invalid_token", error_description="Cannot convert access token to JSON"
稍微调试一下 Spring Security 代码,看起来如果由于某种原因它无法在内部解析/创建用于验证签名的公钥,则会出现上述错误。
这对我有用...
如果您将属性 security.oauth2.resource.jwt.key-value
设置为实际的公钥,那么它会验证并从 JWT 解压缩用户信息,而无需回调(或配置)用户信息端点。不利的一面是,必须将公钥复制到代码中,并在更改时随时更新。
请注意,此属性的值必须在开头包含 -----BEGIN PUBLIC KEY-----\n
和 \n-----END PUBLIC KEY-----
。
例如
security.oauth2.resource.jwt.key-value: "-----BEGIN PUBLIC KEY-----\nMIIB......\n-----END PUBLIC KEY-----"
我没有尝试过,但我很确定这也适用于 YAML 中的实际换行符:
security.oauth2.resource.jwt.key-value: |
-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----
我认为第一组属性不适用于我的设置的原因是因为从 key-uri 返回的密钥没有这些开始和结束段以及 Spring Security期望他们在那里。
这可能不会直接回答您的问题,但希望仍然有帮助。 Spring 安全日志记录非常稀少,我发现这些属性对于它们改变事物行为的程度非常复杂,没有太多有用的日志记录信息来指示正在发生的事情,即使在 TRACE 级别也是如此。
【讨论】:
以上是关于从 JWT 令牌获取 UserInfo 信息并在 Spring Boot OAuth2 自动配置上仅使用 JWK 验证的主要内容,如果未能解决你的问题,请参考以下文章
如何在spring security oauth2授权服务器中通过jwt令牌获取用户信息?
无法访问 Azure 上的 OpenId UserInfo 端点(AADSTS90010:JWT 令牌不能与 UserInfo 端点一起使用)
如何在 Angular 中从服务器获取带有 jwt 令牌的响应标头