Spring Boot OAuth2 资源服务器:库如何在没有公钥的情况下验证 JWT 令牌?
Posted
技术标签:
【中文标题】Spring Boot OAuth2 资源服务器:库如何在没有公钥的情况下验证 JWT 令牌?【英文标题】:Spring Boot OAuth2 Resource Server: How library can verify JWT token without public key? 【发布时间】:2021-01-18 05:51:09 【问题描述】:我有以下配置最少的 Spring Boot 应用程序
application.properties
server.port=8081
spring.security.oauth2.resourceserver.jwt.issuer-uri = http://localhost:8080/auth/realms/master
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.nurgasemetey</groupId>
<artifactId>springboot-keycloak</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-keycloak</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
控制器
@RestController
@RequestMapping("/users")
public class UsersController
@GetMapping("/status/check")
public String status(@AuthenticationPrincipal Jwt principal)
return "working";
似乎 Spring Boot Oauth2 不使用公钥,正如我在代码中看到的那样:
OAuth2ResourceServerProperties
/**
* JSON Web Key URI to use to verify the JWT token.
*/
private String jwkSetUri;
/**
* JSON Web Algorithm used for verifying the digital signatures.
*/
private String jwsAlgorithm = "RS256";
/**
* URI that can either be an OpenID Connect discovery endpoint or an OAuth 2.0
* Authorization Server Metadata endpoint defined by RFC 8414.
*/
private String issuerUri;
/**
* Location of the file containing the public key used to verify a JWT.
*/
private Resource publicKeyLocation;
但是我没有给publicKeyLocation
,但是app不用公钥也可以验证。
在底层它使用JwtIssuerValidator
和JwtTimestampValidator
验证器。
另一方面,express-jwt
,需要公钥才能离线验证
const express = require('express');
const jwt = require('express-jwt');
const app = express();
const secret = 'secret';
const fs = require('fs');
var publicKey = fs.readFileSync('public.pub');
app.get('/protected', jwt( secret: publicKey, algorithms: ['RS256'] ), (req, res) =>
res.send('protected');
)
app.listen(3000, () => console.log('server started'));
Spring Boot Oauth 如何在没有公钥的情况下进行验证?
【问题讨论】:
http://localhost:8080/auth/realms/master
提供什么?大多数令牌颁发者应该公开一个 /.well-known/openid-configuration
端点,Spring Boot 将尝试自动查找该端点。这个端点告诉 Spring Boot 如何找到 JWKS 端点,这是可以找到公钥的地方。
是的,它公开了公钥。感谢您的澄清
【参考方案1】:
自我回答。
首先,http://localhost:8080/auth/realms/master
似乎公开了公钥。正如Generate JWT Token in Keycloak and get public key to verify the JWT token on a third party platform - Stack Overflow 和@Thomas Kåsene 对这个问题的评论中所说的那样
其次,我挖了spring boot oauth2的代码,偶然发现了这段代码在
ReactiveOAuth2ResourceServerJwkConfiguration
@Bean
@Conditional(IssuerUriCondition.class)
ReactiveJwtDecoder jwtDecoderByIssuerUri()
return ReactiveJwtDecoders.fromIssuerLocation(this.properties.getIssuerUri());
JwtDecoderProviderConfigurationUtils
private static Map<String, Object> getConfiguration(String issuer, URI... uris)
String errorMessage = "Unable to resolve the Configuration with the provided Issuer of " +
"\"" + issuer + "\"";
for (URI uri : uris)
try
RequestEntity<Void> request = RequestEntity.get(uri).build();
ResponseEntity<Map<String, Object>> response = rest.exchange(request, typeReference);
Map<String, Object> configuration = response.getBody();
if (configuration.get("jwks_uri") == null)
throw new IllegalArgumentException("The public JWK set URI must not be null");
return configuration;
catch (IllegalArgumentException e)
throw e;
catch (RuntimeException e)
if (!(e instanceof HttpClientErrorException &&
((HttpClientErrorException) e).getStatusCode().is4xxClientError()))
throw new IllegalArgumentException(errorMessage, e);
// else try another endpoint
throw new IllegalArgumentException(errorMessage);
这似乎从application.properties
中给出的issuer-uri
获取公钥。获取后,它会使用获取的公钥验证 jwt 令牌。
测试
关闭您的 jwt 提供程序,在我的情况下为 keycloak 并运行 spring boot 应用程序,然后它给出
Caused by: java.lang.IllegalArgumentException: Unable to resolve the Configuration with the provided Issuer of "http://localhost:8080/auth/realms/master"
【讨论】:
也感谢您的提问和回答。虽然我很想知道微服务是一次还是每次请求都获取公钥? Spring Boot 在启动时获取一次公钥。 令牌验证发生在 Spring Boot 中,公钥从 KeyCloak 中获取。因此,在验证令牌时,它不会调用 KeyCloak。假设如果从 KeyCloak 中删除用户,即使令牌验证过程也会成功,因为它没有调用 KeyClock 端点来验证令牌,它只是在 Spring 引导中发生。如何处理这种情况?有没有办法在 Spring Boot 中针对 Auth Server(即 KeyCloak)进行令牌验证? @ARods 我明白你的意思。解决它的一种方法是令牌的过期时间很短。就是这样,已经解决了。如果不适合你,可能需要集成 Keycloak SDK。以上是关于Spring Boot OAuth2 资源服务器:库如何在没有公钥的情况下验证 JWT 令牌?的主要内容,如果未能解决你的问题,请参考以下文章
Spring boot + oauth2:访问此资源需要完全身份验证
spring boot(1.3)基于oauth2资源服务器:不能使用@PreAuthorize方法安全
spring boot oauth2 ResourceServerConfigurerAdapter 不保护资源
Spring Boot 2 OAuth2 资源服务器不访问授权服务器进行访问令牌验证