Spring 安全 OAuth2 JWT 资源服务器(旧版)有 NULL 主体
Spring Security OAuth 2 JWKS Resource Server (Legacy) has NULL Principal
我需要设置一个遗留的 Spring-Boot 1.5 项目来充当 OAuth2 资源服务器,使用公开的 JWKS 在每个请求中验证所有传入的 JWT。
我有 followed/checked 的链接和文章是:
- https://projects.spring.io/spring-security-oauth/docs/oauth2.html
- https://www.baeldung.com/spring-security-oauth2-jws-jwk
- Spring Security OAuth with JWK Example
我的 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>1.5.22.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>project</groupId>
<artifactId>project-resource</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Project Resource</name>
<description>Project Resource</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
<!--https://projects.spring.io/spring-security-oauth/docs/oauth2.html-->
<!--https://projects.spring.io/spring-security-oauth/docs/oauth2.html#resource-server-configuration-->
<!--https://github.com/spring-projects/spring-security-oauth/tree/master/tests/annotation/resource-->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.5.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
我将其配置为充当资源服务器并使用 application.yml
:
中的 JWKS uri
security:
oauth2:
resource:
jwk:
key-set-uri: http://sso.server/.well-known/jwks.json
主要的class就这么简单:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
@SpringBootApplication
@EnableResourceServer
public class ProjectResourceApplication {
public static void main(String[] args) {
SpringApplication.run(ProjectResourceApplication.class, args);
}
}
为了使用来自另一个域的 API,我还必须提供此 CorsFilter
,
因为简单的 httpSecurity.cors() 不起作用。
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CORSFilter extends OncePerRequestFilter {
@Value("${custom.cors.allowOrigin:*}")
private String allowOrigin;
@Value("${custom.cors.allowMethods:GET, POST, PUT, DELETE, OPTIONS}")
private String allowMethods;
@Value("${custom.cors.allowHeaders:Content-Type,Authorization}")
private String allowHeaders;
@Value("${custom.cors.allowCredentials:true}")
private String allowCredentials;
@Value("${custom.cors.maxAge:3600}")
private String maxAge;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
response.addHeader("Access-Control-Allow-Origin", allowOrigin);
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
response.addHeader("Access-Control-Allow-Methods", allowMethods);
response.addHeader("Access-Control-Allow-Headers", allowHeaders);
response.addHeader("Access-Control-Allow-Credentials", allowCredentials);
response.addHeader("Access-Control-Max-Age", maxAge);
response.setStatus(HttpServletResponse.SC_OK);
} else {
filterChain.doFilter(request, response);
}
}
}
授权工作正常,这意味着令牌已通过 JWKS 正确验证,
但是 Principal 始终为 null。
例如:
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
@RestController
public class PrincipalController {
@GetMapping("/principal")
public Principal getPrincipal(@AuthenticationPrincipal Principal principal) {
return principal;
}
}
有什么不对或遗漏的吗?此外,如果可能的话,我还希望有一个自定义对象作为 Principal。
Rosource 服务器是否还需要有关客户端的信息才能提供委托人?在 https://github.com/spring-projects/spring-security-oauth/issues/1012
找到
解决方案是添加 UserInfoTokenServices 类型的 @Bean
,如下所述。
可以使用自定义 Principal & Authorities 提取器进一步自定义此类 @Bean
。
我需要设置一个遗留的 Spring-Boot 1.5 项目来充当 OAuth2 资源服务器,使用公开的 JWKS 在每个请求中验证所有传入的 JWT。
我有 followed/checked 的链接和文章是:
- https://projects.spring.io/spring-security-oauth/docs/oauth2.html
- https://www.baeldung.com/spring-security-oauth2-jws-jwk
- Spring Security OAuth with JWK Example
我的 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>1.5.22.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>project</groupId>
<artifactId>project-resource</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Project Resource</name>
<description>Project Resource</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
<!--https://projects.spring.io/spring-security-oauth/docs/oauth2.html-->
<!--https://projects.spring.io/spring-security-oauth/docs/oauth2.html#resource-server-configuration-->
<!--https://github.com/spring-projects/spring-security-oauth/tree/master/tests/annotation/resource-->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.5.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
我将其配置为充当资源服务器并使用 application.yml
:
security:
oauth2:
resource:
jwk:
key-set-uri: http://sso.server/.well-known/jwks.json
主要的class就这么简单:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
@SpringBootApplication
@EnableResourceServer
public class ProjectResourceApplication {
public static void main(String[] args) {
SpringApplication.run(ProjectResourceApplication.class, args);
}
}
为了使用来自另一个域的 API,我还必须提供此 CorsFilter
,
因为简单的 httpSecurity.cors() 不起作用。
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CORSFilter extends OncePerRequestFilter {
@Value("${custom.cors.allowOrigin:*}")
private String allowOrigin;
@Value("${custom.cors.allowMethods:GET, POST, PUT, DELETE, OPTIONS}")
private String allowMethods;
@Value("${custom.cors.allowHeaders:Content-Type,Authorization}")
private String allowHeaders;
@Value("${custom.cors.allowCredentials:true}")
private String allowCredentials;
@Value("${custom.cors.maxAge:3600}")
private String maxAge;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
response.addHeader("Access-Control-Allow-Origin", allowOrigin);
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
response.addHeader("Access-Control-Allow-Methods", allowMethods);
response.addHeader("Access-Control-Allow-Headers", allowHeaders);
response.addHeader("Access-Control-Allow-Credentials", allowCredentials);
response.addHeader("Access-Control-Max-Age", maxAge);
response.setStatus(HttpServletResponse.SC_OK);
} else {
filterChain.doFilter(request, response);
}
}
}
授权工作正常,这意味着令牌已通过 JWKS 正确验证, 但是 Principal 始终为 null。
例如:
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
@RestController
public class PrincipalController {
@GetMapping("/principal")
public Principal getPrincipal(@AuthenticationPrincipal Principal principal) {
return principal;
}
}
有什么不对或遗漏的吗?此外,如果可能的话,我还希望有一个自定义对象作为 Principal。 Rosource 服务器是否还需要有关客户端的信息才能提供委托人?在 https://github.com/spring-projects/spring-security-oauth/issues/1012
找到解决方案是添加 UserInfoTokenServices 类型的 @Bean
,如下所述。
可以使用自定义 Principal & Authorities 提取器进一步自定义此类 @Bean
。