总结:
使用 SpringBoot 2.7.7 测试 Spring Security OAuth2 资源服务器 JWT,它使用 Spring Security 5.7.6 在启动时查询授权服务器。
启动期望
当使用此属性和这些依赖项时,资源服务器 将自动配置自身以验证 JWT 编码的承载 代币。
它通过确定性的启动过程实现这一点:
- 查询 jwks_url 属性的提供者配置或授权服务器元数据端点
- 查询 jwks_url 端点以获取支持的算法
- 配置验证策略以查询jwks_url以获取找到的算法的有效公钥
- 配置验证策略以验证针对 idp.example.com 的每个 JWT iss 声明。
此过程的结果是授权服务器必须是 启动并接收请求以便资源服务器成功 启动。
如果资源服务器查询时授权服务器宕机了 (给定适当的超时),然后启动将失败。
我在
application.properties
中定义了属性,并且在 pom.xml
中定义了依赖项,如上所述。
但是,我尝试这个的非常小的示例不起作用(例如,资源服务器在启动时似乎根本没有查询授权服务器,因此资源服务器启动成功。
我原以为我的非常小的应用程序会根据文档在启动时失败,但事实并非如此!甚至关闭了授权服务器,SpringBoot资源服务器应用程序仍然启动。
这就是我所做的:
1) pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.7</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.example</groupId>
<artifactId>oauth2-resource-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>oauth2-resource-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.5</spring-cloud.version>
<bootstrap.version>5.2.3</bootstrap.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency-->
<!--dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>${bootstrap.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2。应用程序.属性
server.port=8081
spring.thymeleaf.cache=false
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://openam.localtest.me:8080/openam/oauth2/realms/subrealm/
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://openam.localtest.me:8080/openam/oauth2/realms/subrealm/connect/jwk_uri
3.配置类别:
package org.example.oauth2resourceserver;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class OAuth2ResourceServerSecurityConfiguration {
@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri}")
String jwkSetUri;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.antMatchers(HttpMethod.GET, "/**").hasAuthority("SCOPE_message:read")
.antMatchers(HttpMethod.POST, "/**").hasAuthority("SCOPE_message:write")
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
@Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withJwkSetUri(this.jwkSetUri).build();
}
}
4。控制器
package org.example.oauth2resourceserver;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class ExampleMVCController {
@GetMapping("/")
public String main(Model model) {
return "welcome";
}
@GetMapping("/welcome")
public String welcome(Model model) {
return "welcome";
}
}
5。应用
package org.example.oauth2resourceserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Oauth2ResourceServerApplication {
public static void main(String[] args) {
SpringApplication.run(Oauth2ResourceServerApplication.class, args);
}
}
授权服务器关闭后,运行资源服务器时控制台上的输出为:
2022-12-28 03:32:38.148 INFO 1377958 --- [ main] o.e.o.Oauth2ResourceServerApplication : Starting Oauth2ResourceServerApplication using Java 11.0.15 on xxxxx-Inspiron-15-7510 with PID 1377958 (/home/xxxxx/projects/oauth2-resource-server/target/classes started by jsalvo in /home/xxxxx/projects/oauth2-resource-server)
2022-12-28 03:32:38.150 INFO 1377958 --- [ main] o.e.o.Oauth2ResourceServerApplication : No active profile set, falling back to 1 default profile: "default"
2022-12-28 03:32:38.561 INFO 1377958 --- [ main] o.s.cloud.context.scope.GenericScope : BeanFactory id=12f14043-de4d-3b01-a8aa-ef038a41274e
2022-12-28 03:32:38.733 INFO 1377958 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8081 (http)
2022-12-28 03:32:38.739 INFO 1377958 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2022-12-28 03:32:38.739 INFO 1377958 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.70]
2022-12-28 03:32:38.819 INFO 1377958 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2022-12-28 03:32:38.820 INFO 1377958 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 634 ms
2022-12-28 03:32:38.931 INFO 1377958 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@748ac6f3, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@68f6e55d, org.springframework.security.web.context.SecurityContextPersistenceFilter@2bfaba70, org.springframework.security.web.header.HeaderWriterFilter@5584d9c6, org.springframework.security.web.csrf.CsrfFilter@3bf54172, org.springframework.security.web.authentication.logout.LogoutFilter@58af5076, org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter@650c405c, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@9301672, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@2577a95d, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@6fff46bf, org.springframework.security.web.session.SessionManagementFilter@17e9bc9e, org.springframework.security.web.access.ExceptionTranslationFilter@4da39ca9, org.springframework.security.web.access.intercept.AuthorizationFilter@2954f6ab]
2022-12-28 03:32:39.140 INFO 1377958 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8081 (http) with context path ''
2022-12-28 03:32:39.152 INFO 1377958 --- [ main] o.e.o.Oauth2ResourceServerApplication : Started Oauth2ResourceServerApplication in 1.27 seconds (JVM running for 1.512)
更新2022年12月28日
根据评论的建议,我已经完全删除了上面的OAuth2ResourceServerSecurityConfiguration。还是没有运气。
查看 OAuth2ResourceServerJwtConfiguration.JwtDecoderConfiguration 的源代码:
看来您必须仅指定以下属性之一,而不能同时指定两者:
spring.security.oauth2.resourceserver.jwt.issuer-uri
spring.security.oauth2.resourceserver.jwt.jwk-set-uri
方法在:
JwtDecoderConfiguration.jwtDecoderByIssuerUri() 方法 有一个
@IssuerUriCondition
注释,它规定了必须仅定义 spring.security.oauth2.resourceserver.jwt.issuer-uri
属性的条件。
所以我注释掉了另一个属性(从
spring.security.oauth2.resourceserver.jwt.jwk-set-uri
注释掉了 application.properties
),但是当我通过 IntelliJ 调试到该方法时,它在下面的第 139 行处步进,但它不会在 lambda 内的第 141 行处步进/停止,这是实际调用授权服务器的代码的位置,即使我尝试从第 139 行单步执行 (F7):
总之我还是很茫然。大家有什么想法吗?
如果你看一下这里的Spring文档:再进一步,它特别提到了这一点;
如果资源服务器必须能够独立于 授权服务器,那么也可以提供 jwk-set-uri:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://idp.example.com
jwk-set-uri: https://idp.example.com/.well-known/jwks.json
如文档所述;
因此,资源服务器将 NOT ping 授权服务器 启动时
这似乎是你通过配置所做的;
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://openam.localtest.me:8080/openam/oauth2/realms/subrealm/connect/jwk_uri
我想如果你删除该行☝🏾,那么你就会得到你正在寻找的默认行为 - 资源服务器依赖于授权服务器的可用性来启动