知識庫 / Spring / Spring Security RSS 訂閱

Spring Security OAuth2 簡易單點登錄

Spring Security
HongKong
6
02:32 PM · Dec 06 ,2025

1. 概述

在本教程中,我們將討論如何使用 Spring Security OAuth 和 Spring Boot,以及 Keycloak 作為授權服務器,實現 單點登錄 (SSO) – 單點登錄

我們將使用 4 個獨立應用程序:

  • 一個授權服務器 – 作為中心身份驗證機制
  • 一個資源服務器 – 提供 Foo 的提供者
  • 兩個客户端應用程序 – 使用 SSO 的應用程序

簡單來説,當用户通過一個客户端應用程序訪問資源時,他們將被重定向到首先進行身份驗證,通過授權服務器。Keycloak 會將用户登錄,即使在第一個應用程序中仍然登錄,如果使用相同的瀏覽器訪問第二個客户端應用程序,用户就不需要再次輸入憑據。

我們將使用 OAuth2 中的 授權碼 grant 類型來驅動身份驗證的委託。

我們將使用 Spring Security 5 中的 OAuth 堆棧。 如果您想使用 Spring Security OAuth 的舊版堆棧,請查看:Simple Single Sign-On with Spring Security OAuth2 (舊版堆棧)

根據 遷移指南:

Spring Security 將此功能稱為 OAuth 2.0 登錄,而 Spring Security OAuth 則將其稱為 SSO

好了,讓我們直接開始吧。

2. 授權服務器

此前,Spring Security OAuth 棧提供了將授權服務器設置為 Spring Application 的可能性。

然而,Spring Security OAuth 棧已被 Spring 廢棄,現在我們將使用 Keycloak 作為我們的授權服務器。

因此,這次我們將以 Keycloak 服務器作為嵌入式 Keycloak 服務器的方式,在 Spring Boot 應用中設置我們的授權服務器。

在我們的預配置中,我們將定義兩個客户端,ssoClient-1ssoClient-2, 每個客户端應用程序一個。

3. 資源服務器

接下來,我們需要一個資源服務器,或者説是 REST API,它將為我們的客户端應用程序提供 Foo 數據。

它與我們之前為 Angular 客户端應用程序所使用的基本相同。

4. 客户端應用程序

現在,讓我們來看一下我們的 Thymeleaf 客户端應用程序。當然,我們將會使用 Spring Boot 以最小化配置。

請記住,為了演示單點登錄功能,我們需要兩個這樣的應用程序

4.1. Maven 依賴

首先,我們需要在我們的 pom.xml 中添加以下依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</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-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webflux</artifactId>
</dependency>
<dependency>
    <groupId>io.projectreactor.netty</groupId>
    <artifactId>reactor-netty</artifactId>
</dependency>

為了涵蓋所有客户端支持需求,包括安全方面,我們只需要添加 spring-boot-starter-oauth2-client。 此外,由於舊的 RestTemplate 將被棄用,我們將使用 WebClient,因此我們添加了 spring-webfluxreactor-netty

4.2. 安全配置

接下來,我們最重要的部分:第一款客户端應用程序的安全配置:

@EnableWebSecurity
public class UiSecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/", "/login**")
            .permitAll()
            .anyRequest()
            .authenticated()
            .and()
            .oauth2Login();
        return http.build();
    }

    @Bean
    WebClient webClient(ClientRegistrationRepository clientRegistrationRepository, 
      OAuth2AuthorizedClientRepository authorizedClientRepository) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 = 
          new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository, 
          authorizedClientRepository);
        oauth2.setDefaultOAuth2AuthorizedClient(true);
        return WebClient.builder()
            .apply(oauth2.oauth2Configuration())
            .build();
    }

}

該配置的核心部分是 oauth2Login() 方法,它用於啓用 Spring Security 的 OAuth 2.0 登錄支持。 鑑於我們使用 Keycloak,Keycloak 默認是一個用於 Web 應用和 RESTful Web 服務的一體化身份驗證解決方案,因此我們無需添加任何額外的 SSO 配置。

最後,我們還定義了一個 WebClient Bean,用作一個簡單的 HTTP 客户端,用於處理髮送到我們的資源服務器的請求。

以下是 application.yml

spring:
  security:
    oauth2:
      client:
        registration:
          custom:
            client-id: ssoClient-1
            client-secret: ssoClientSecret-1
            scope: read,write,openid
            authorization-grant-type: authorization_code
            redirect-uri: http://localhost:8082/ui-one/login/oauth2/code/custom
        provider:
          custom:
            authorization-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/auth
            token-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/token
            user-info-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/userinfo
            jwk-set-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/certs
            user-name-attribute: preferred_username
  thymeleaf:
    cache: false
    
server: 
  port: 8082
  servlet: 
    context-path: /ui-one

resourceserver:
  api:
    project:
      url: http://localhost:8081/sso-resource-server/api/foos/        

在此,spring.security.oauth2.client.registration 是註冊客户端的根命名空間。我們定義了一個客户端,其註冊 ID 為 custom。然後我們定義了其 client-idclient-secretscopeauthorization-grant-typeredirect-uri,這些當然應該與我們授權服務器上定義的相同。

之後,我們定義了我們的服務提供者或授權服務器,再次使用相同的 ID custom,並列出了 Spring Security 可以使用的不同 URI。這就是我們所需要定義的全部,並且 該框架會無縫地為我們處理整個登錄過程,包括重定向到 Keycloak

此外,請注意,在我們的示例中,我們部署了我們的授權服務器,當然,我們也可以使用其他第三方提供者,例如 Facebook 或 GitHub。

4.3. 控制器

現在,讓我們在客户端應用中實現控制器,以便從資源服務器請求 Foo

@Controller
public class FooClientController {

    @Value("${resourceserver.api.url}")
    private String fooApiUrl;

    @Autowired
    private WebClient webClient;

    @GetMapping("/foos")
    public String getFoos(Model model) {
        List<FooModel> foos = this.webClient.get()
            .uri(fooApiUrl)
            .retrieve()
            .bodyToMono(new ParameterizedTypeReference<List<FooModel>>() {
            })
            .block();
        model.addAttribute("foos", foos);
        return "foos";
    }
}

如我們所見,這裏只有一個方法,它會將資源提供給 foos 模板。 我們不需要添加任何代碼來進行登錄。

4.4. 前端

現在,讓我們來查看我們客户端應用程序的前端配置。我們這裏不會深入探討,主要是因為我們在網站上已經詳細介紹了它。

我們的客户端應用程序具有非常簡單的前端,以下是 <em >index.html</em >> 的內容:

<a class="navbar-brand" th:href="@{/foos/}">Spring OAuth Client Thymeleaf - 1</a>
<label>Welcome !</label> <br /> <a th:href="@{/foos/}">Login</a>

以及 foos.html:

<a class="navbar-brand" th:href="@{/foos/}">Spring OAuth Client Thymeleaf -1</a>
Hi, <span sec:authentication="name">preferred_username</span>   
    
<h1>All Foos:</h1>
<table>
  <thead>
    <tr>
      <td>ID</td>
      <td>Name</td>                    
    </tr>
  </thead>
  <tbody>
    <tr th:if="${foos.empty}">
      <td colspan="4">No foos</td>
    </tr>
    <tr th:each="foo : ${foos}">
      <td><span th:text="${foo.id}"> ID </span></td>
      <td><span th:text="${foo.name}"> Name </span></td>                    
    </tr>
  </tbody>
</table>

foos.html 頁面需要用户進行身份驗證。 如果未經過身份驗證的用户嘗試訪問 foos.html,則會被重定向到 Keycloak 的登錄頁面

4.5. 第二客户端應用程序

我們將配置一個名為 Spring OAuth Client Thymeleaf -2 的第二個應用程序,使用另一個 client_id ssoClient-2

它與我們剛剛描述的第一個應用程序非常相似。

application.yml 將會不同,以包含不同的 client_idclient_secretredirect_uri 在其 spring.security.oauth2.client.registration: 中。

spring:
  security:
    oauth2:
      client:
        registration:
          custom:
            client-id: ssoClient-2
            client-secret: ssoClientSecret-2
            scope: read,write,openid
            authorization-grant-type: authorization_code
            redirect-uri: http://localhost:8084/ui-two/login/oauth2/code/custom

當然,我們還需要為它指定不同的服務器端口,以便並行運行它們:

server: 
  port: 8084
  servlet: 
    context-path: /ui-two

最後,我們將調整前端 HTML 以使標題為 Spring OAuth Client Thymeleaf – 2,而不是 – 1,以便區分這兩種。

5. 測試 SSO 行為

為了測試 SSO 行為,我們運行我們的應用程序。

我們需要所有 4 個啓動應用程序——授權服務器、資源服務器以及兩個客户端應用程序——正常運行。

現在,打開一個瀏覽器,例如 Chrome,並使用憑據 [<email protected]/123 登錄到 Client-1。 接下來,在另一個窗口或標籤頁中,訪問 Client-2 的 URL。 點擊登錄按鈕後,我們將立即被重定向到 Foos 頁面,從而跳過身份驗證步驟。

同樣,如果用户首先登錄到 Client-2,則無需為 Client-1 鍵入用户名/密碼。

6. 結論

在本教程中,我們重點介紹了使用 Spring Security OAuth2 和 Spring Boot,以 Keycloak 作為身份提供商實現單點登錄。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.