在向 Java 社区推出两年半之后,VMWare发布了 Spring Authorization Server 1.0。 Spring Authorization Server项目构建在Spring Security之上,支持创建OpenID Connect 1.0 Identity Provider 和OAuth 2.1 Authorization Server。该项目取代了业已不再维护的Spring Security OAuth项目。
Spring Authorization Server 也基于Spring Framework 6.0,需要使用 Java 17 作为最低版本。该项目支持特征列表中描述的 Authorization Grants、Token Format、Client Authentication 和 Protocol Endpoints。
有个示例应用阐述了使用Spring Initializr创建 Spring Boot 应用的基本配置。该示例应用是基于 REST 的,需要在**pom.xml
**文件中包含_spring-boot-starter-web_依赖:
dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
复制代码
为了阐述登录功能,请考虑如下创建 REST 端点的样例:
@RestController
public class TimeController {
@GetMapping("/time")
public String retrieveTime() {
DateTimeFormatter dateTimeFormatter =
DateTimeFormatter.ofPattern("HH:mm:ss");
LocalTime localTime = LocalTime.now();
return dateTimeFormatter.format(localTime);
}
}
复制代码
一个基础的 Spring Boot 应用类用来启动应用与前文创建的 REST 端点:
@SpringBootApplication
public class TimeApplication {
public static void main(String[] args) {
SpringApplication.run(TimeApplication.class, args);
}
}
复制代码
在启动应用之后,打开http://localhost:8080/time URL,将会显示当前时间:
现在,我们添加 Spring Authorization Server 依赖:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
<version>1.0.0</version>
</dependency>
复制代码
当再次启动应用后,日志中会打印出密码,例如:
Using generated security password: d73d5904-25a1-44ed-91e1-a32c4c5aedb8
复制代码
现在,当访问http://localhost:8080/time时,请求会重定向到http://localhost:8080/login,并展示如下所示的页面:
我们使用默认的用户名_user_以及打印出的密码登录之后,请求会被重定向到http://localhost:8080/time?continue,并再次显示当前时间。
“开发第一个样例”文档详细介绍了 Spring Authorization Server 需要的几个**@Bean组件,它们应该定义在带有@Configuration**注解的类中。第一个 bean 用来定义 OAuth2 Protocol Endpoint:
@Bean
@Order(1)
public SecurityFilterChain protocolFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http
.exceptionHandling((exceptions) -> exceptions
.authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint("/login"))
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults());
return http.build();
}
复制代码
第二个 bean 用来定义 Spring Security Authentication:
@Bean
@Order(2)
public SecurityFilterChain authenticationFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
复制代码
在真正的产品中,我们应该使用合理的方案来存储用户,但是在这个简单的样例中,用户_james_和密码_gosling_存储在了内存中:
@Bean
public UserDetailsService userDetailsService() {
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("james")
.password("gosling")
.roles("FOUNDER")
.build();
return new InMemoryUserDetailsManager(userDetails);
}
复制代码
新的客户端使用**RegisteredClientRepository**注册在了内存中:
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient =
RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("id")
.clientSecret("secret")
.clientAuthenticationMethod(
ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.redirectUri(
"http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
.redirectUri("http://127.0.0.1:8080/authorized")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.scope("message.read")
.scope("message.write")
.clientSettings(
ClientSettings.builder()
.requireAuthorizationConsent(true).build())
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
复制代码
访问令牌会使用如下的 bean 进行签名,它会使用**com.nimbusds.jose.jwk.RSAKey
,而不是java.security.interfaces.RSAKey
**:
@Bean
public JWKSource<SecurityContext> jwkSource() {
KeyPair keyPair = generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}
private static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator =
KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
复制代码
JwtDecoder会用来解码已签名的访问令牌,它会使用com.nimbusds.jose.proc.SecurityContext
,而不是**org.springframework.security.core.context.SecurityContext
**:
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
复制代码
最后,**AuthorizationServerSettings**会用来配置 OAuth2 认证服务器:
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
复制代码
现在,当浏览http://localhost:8080/time时,可以使用用户名_james_和密码_gosling_来查看当前的时间。在遵循这些步骤后,该应用可以扩展为使用各种 OAuth2 和 OpenID Connect 1.0 功能,如令牌。
有多个视频对 Spring Authorization Server 进行了详细解释,例如 Spring Security 团队的核心提交者Joe Grandja在旧金山 JUG 上做了Spring Authorization Server入门的演讲,Spring Security in Action的作者Laurentiu Spilca在 Spring I/O 上介绍了如何使用Spring Security实现OAuth 2认证服务器。
该项目是基于VMware Tanzu开源软件支持策略发布的,这意味着主要版本的支持时间长达三年。另外,VMware 还提供 24/7 的商业支持。
更多信息可以参考入门指南、参考文档和 GitHub 上的示例。
原文链接:
Spring Authorization Server 1.0 Provides Oauth 2.1 and OpenID Connect 1.0 Implementations
相关阅读:
一文看懂OAuth 2.0 (附实践案例)
OAuth 2.0与OpenID Connect协议的完整指南
评论