在向 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,将會顯示目前時間:
21:00:34
現在,我們添加 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