1. 簡介
OAuth 是一種開放標準,描述了一種授權過程。它可用於授權用户訪問 API。例如,REST API 可以限制僅對具有適當角色的註冊用户訪問。
OAuth 授權服務器負責驗證用户並頒發包含用户數據和適當訪問策略的訪問令牌。
在本教程中,我們將使用 Spring Security OAuth 授權服務器 項目來實現一個簡單的 OAuth 應用程序。
在此過程中,我們將創建一個客户端-服務器應用程序,該應用程序將從 REST API 中檢索 Baeldung 文章列表。客户端服務和服務器服務都需要 OAuth 身份驗證。
2. 授權服務器實現
我們將首先查看 OAuth 授權服務器的配置。它將作為文章資源和客户端服務器的身份驗證來源。
2.1. 依賴項
首先,我們需要在我們的 pom.xml文件中添加一些依賴項:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
<version>3.2.2</version>
</dependency>2.2. 配置
首先,我們將創建一個 <em >application.yml</em> 文件,以指定身份驗證服務器將運行的端口,通過設置 <em >server.port</em> 屬性來實現:
server:
port: 9000接下來,由於每個授權服務器都需要具有唯一的頒發者 URL,我們將設置我們的 URL 為本地主機別名 http://auth-server ,在端口 9000 上,通過設置 spring.security.oauth2.authorizationserver.issuer 屬性來實現:
spring:
security:
oauth2:
authorizationserver:
issuer: http://auth-server:9000此外,我們還將添加一個條目“127.0.0.1 auth-server”到我們的 /etc/hosts文件中。這允許我們在本地機器上運行客户端和認證服務器,並避免客户端和認證服務器之間會話 Cookie 覆蓋的問題。
最後,我們將配置客户端服務的倉庫。在我們的示例中,我們將有一個名為 articles-client的單個客户端:
spring:
security:
oauth2:
authorizationserver:
client:
articles-client:
registration:
client-id: articles-client
client-secret: "{noop}secret"
client-name: Articles Client
client-authentication-methods:
- client_secret_basic
authorization-grant-types:
- authorization_code
- refresh_token
redirect-uris:
- http://127.0.0.1:8080/login/oauth2/code/articles-client-oidc
- http://127.0.0.1:8080/authorized
scopes:
- openid
- articles.read我們正在配置的屬性如下:
- 客户端 ID – Spring 將使用它來識別嘗試訪問資源的客户端
- 客户端密鑰 – 客户端和服務器都已知的一個密鑰,用於提供兩者之間的信任
- 身份驗證方法 – 在我們的情況下,我們將使用基本身份驗證,它僅包含用户名和密碼
- 授權類型 – 我們希望允許客户端生成授權碼和刷新令牌
- 重定向 URI – 客户端將在基於重定向的流程中使用它
- 範圍 – 此參數定義客户端可能擁有的授權。 在我們的情況下,我們將使用必需的 OidcScopes.OPENID 及其自定義範圍 articles.read
然後我們可以轉向 Spring Beans 的配置。 首先,我們將通過使用帶有 @Configuration 和 以及 @EnableWebSecurity 註解的配置類來啓用 Spring Web Security 模塊:
@Configuration
@EnableWebSecurity
public class DefaultSecurityConfig {
// ...
}接下來,我們將配置一個 Spring Security 過濾器鏈,以應用默認的 OAuth 安全並生成默認的表單登錄頁面:
@Bean
@Order(1)
SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(withDefaults()); // Enable OpenID Connect 1.0
return http.formLogin(withDefaults()).build();
}然後,我們將配置第二個 Spring Security 過濾器鏈用於身份驗證:
@Bean
@Order(2)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest()
.authenticated())
.formLogin(withDefaults());
return http.build();
}在這裏,我們調用 authorizeRequests.anyRequest().authenticated() 來要求所有請求進行身份驗證。我們還通過調用 formLogin(defaults()) 方法提供基於表單的身份驗證。
最後,我們將定義一組用於測試的示例用户。為了本示例的目的,我們將創建一個僅包含一個管理員用户的存儲庫:
@Bean
UserDetailsService users() {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
UserDetails user = User.builder()
.username("admin")
.password("password")
.passwordEncoder(encoder::encode)
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}3. 資源服務器
現在我們將創建一個資源服務器,該服務器將從 GET 端點返回文章列表。這些端點應僅允許對我們的 OAuth 服務器進行身份驗證的請求。
3.1. 依賴項
首先,我們將包含所需的依賴項:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
<version>3.2.2</version>
</dependency>3.2. 配置
在開始執行代碼實現之前,我們應該在 application.yml 文件中配置一些屬性。第一個是服務器端口:
server:
port: 8090接下來,是安全配置時間。我們需要為我們的認證服務器設置正確的 URL,使用我們在 ProviderSettings bean 中配置的主機和端口:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://auth-server:9000現在我們可以設置我們的 Web 安全配置。 再次強調,每個指向文章資源的請求都應被授權,並且必須擁有正確的 articles.read 權限:
@Configuration
@EnableWebSecurity
public class ResourceServerConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.securityMatcher("/articles/**")
.authorizeHttpRequests(authorize -> authorize.anyRequest()
.hasAuthority("SCOPE_articles.read"))
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
return http.build();
}
}如所示,我們還調用了 oauth2ResourceServer() 方法,該方法將根據 application.yml 配置來配置 OAuth 服務器連接。
3.3. Articles 控制器
最後,我們將創建一個 REST 控制器,它將在 GET /articles 端點下返回文章列表:
@RestController
public class ArticlesController {
@GetMapping("/articles")
public String[] getArticles() {
return new String[] { "Article 1", "Article 2", "Article 3" };
}
}4. API 客户端
為了最後一部分,我們將創建一個 REST API 客户端,該客户端將從資源服務器獲取文章列表。
4.1. 依賴項
為了開始,我們將包含必要的依賴項:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<version>6.1.3</version>
</dependency>
<dependency>
<groupId>io.projectreactor.netty</groupId>
<artifactId>reactor-netty</artifactId>
<version>1.1.15</version>
</dependency>
4.2. 配置
正如之前所做的那樣,我們將定義一些用於身份驗證的配置屬性:
server:
port: 8080
spring:
security:
oauth2:
client:
registration:
articles-client-oidc:
provider: spring
client-id: articles-client
client-secret: secret
authorization-grant-type: authorization_code
redirect-uri: "http://127.0.0.1:8080/login/oauth2/code/{registrationId}"
scope: openid
client-name: articles-client-oidc
articles-client-authorization-code:
provider: spring
client-id: articles-client
client-secret: secret
authorization-grant-type: authorization_code
redirect-uri: "http://127.0.0.1:8080/authorized"
scope: articles.read
client-name: articles-client-authorization-code
provider:
spring:
issuer-uri: http://auth-server:9000現在我們將創建一個 WebClient 實例,用於向我們的資源服務器執行 HTTP 請求。我們將使用標準實現,僅添加 OAuth 授權過濾器:
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}WebClient 需要OAuth2AuthorizedClientManager 作為依賴項。我們來創建一個默認實現:
@Bean
OAuth2AuthorizedClientManager authorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizedClientProvider authorizedClientProvider =
OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode()
.refreshToken()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}最後,我們將配置Web安全:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.oauth2Login(oauth2Login ->
oauth2Login.loginPage("/oauth2/authorization/articles-client-oidc"))
.oauth2Client(withDefaults());
return http.build();
}
}在這裏,以及在其他服務器上,我們都需要對每個請求進行身份驗證。此外,我們需要配置登錄頁面的 URL(在 .yml 配置中定義),以及 OAuth 客户端。
4.3. Articles客户端控制器
最後,我們可以創建數據訪問控制器。我們將使用先前配置的 WebClient 向我們的資源服務器發送 HTTP 請求:
@RestController
public class ArticlesController {
private WebClient webClient;
@GetMapping(value = "/articles")
public String[] getArticles(
@RegisteredOAuth2AuthorizedClient("articles-client-authorization-code") OAuth2AuthorizedClient authorizedClient
) {
return this.webClient
.get()
.uri("http://127.0.0.1:8090/articles")
.attributes(oauth2AuthorizedClient(authorizedClient))
.retrieve()
.bodyToMono(String[].class)
.block();
}
}在上述示例中,我們從請求中獲取 OAuth 授權令牌,形式為 OAuth2AuthorizedClient 類。Spring 自動將其綁定,使用 @RegisteredOAuth2AuthorizedClient 註解進行識別。在本例中,它從先前在 .yml 文件中配置的 article-client-authorizaiton-code 中提取。
此授權令牌隨後傳遞到 HTTP 請求。
4.4. 訪問文章列表
現在,當我們進入瀏覽器並嘗試訪問 http://127.0.0.1:8080/articles 頁面時,我們將自動重定向到 OAuth 服務器登錄頁面,其 URL 為 http://auth-server:9000/login。
在提供正確的用户名和密碼後,授權服務器會將我們重定向回請求的 URL,即文章列表。
對文章端點的進一步請求將不需要登錄,因為訪問令牌將存儲在 Cookie 中。
5. 結論
在本文中,我們學習瞭如何設置、配置和使用 Spring Security OAuth 授權服務器。