Spring Boot 和 OAuth2

本指南將向您展示如何使用 OAuth 2.0Spring Boot 建立一個範例應用程式,用於執行各種“社交登入”操作。

它從一個簡單的、單一供應商的單點登入開始,並逐步發展為一個具有多種身份驗證供應商選擇的客戶端:GitHubGoogle

這些範例都是使用 Spring Boot 和 Spring Security 作為後端的單頁應用程式。它們也都使用純 jQuery 作為前端。但是,轉換為不同的 JavaScript 框架或使用伺服器端渲染所需的更改將會非常小。

所有範例都是使用 Spring Boot 中的原生 OAuth 2.0 支援來實現的。

有幾個範例互相構建,在每個步驟中添加新功能

  • simple:一個非常基本的靜態應用程式,只有一個首頁,並透過 Spring Boot 的 OAuth 2.0 配置屬性進行無條件登入(如果您訪問首頁,您將被自動重新導向到 GitHub)。

  • click:添加了一個明確的連結,使用者必須點擊才能登入。

  • logout:也為經過身份驗證的使用者添加了一個登出連結。

  • two-providers:添加了第二個登入供應商,以便使用者可以在首頁上選擇使用哪一個。

  • custom-error:為未經身份驗證的使用者添加錯誤訊息,並添加基於 GitHub API 的自定義身份驗證。

從一個應用程式遷移到功能階梯中的下一個應用程式所需的變更可以在 原始碼 中追蹤。每個版本的應用程式都是它自己的目錄,因此您可以比較它們的差異。

每個應用程式都可以匯入到 IDE 中。您可以執行 SocialApplication 中的 main 方法來啟動應用程式。它們都會在 https://127.0.0.1:8080 上顯示一個首頁(如果您想登入並查看內容,則所有應用程式都需要您至少擁有一個 GitHub 和 Google 帳戶)。

您也可以使用 mvn spring-boot:run 在命令列上執行所有應用程式,或者透過構建 jar 檔案並使用 mvn packagejava -jar target/*.jar 執行它(根據 Spring Boot 文件 和其他 可用文件)。如果您使用頂層的 wrapper,則無需安裝 Maven,例如:

$ cd simple
$ ../mvnw package
$ java -jar target/*.jar
這些應用程式都在 localhost:8080 上運作,因為它們將使用在 GitHub 和 Google 上註冊的 OAuth 2.0 客戶端。若要在不同的主機或埠上執行它們,您需要以這種方式註冊您的應用程式。如果您使用預設值,則不會有將您的憑證洩漏到 localhost 之外的危險。但是,請小心您在網際網路上公開的內容,並且不要將您自己的應用程式註冊資訊放入公開的原始碼控制中。

使用 GitHub 的單點登入

在本節中,您將建立一個使用 GitHub 進行身份驗證的最小應用程式。透過利用 Spring Boot 中的自動配置功能,這將非常容易。

建立新專案

首先,您需要建立一個 Spring Boot 應用程式,這可以透過多種方式完成。最簡單的方法是前往 https://start.spring.io 並產生一個空的專案(選擇 "Web" 相依性作為起點)。等效地,在命令列上執行此操作

$ mkdir ui && cd ui
$ curl https://start.spring.io/starter.tgz -d style=web -d name=simple | tar -xzvf -

然後,您可以將該專案匯入到您最喜歡的 IDE 中(預設情況下它是一個普通的 Maven Java 專案),或者只需在命令列上使用檔案和 mvn 即可。

新增首頁

在您的新專案中,在 src/main/resources/static 資料夾中建立 index.html。您應該新增一些樣式表和 JavaScript 連結,以便結果如下所示

index.html
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
    <title>Demo</title>
    <meta name="description" content=""/>
    <meta name="viewport" content="width=device-width"/>
    <base href="/"/>
    <link rel="stylesheet" type="text/css" href="/webjars/bootstrap/css/bootstrap.min.css"/>
    <script type="text/javascript" src="/webjars/jquery/jquery.min.js"></script>
    <script type="text/javascript" src="/webjars/bootstrap/js/bootstrap.min.js"></script>
</head>
<body>
	<h1>Demo</h1>
	<div class="container"></div>
</body>
</html>

這些都不是示範 OAuth 2.0 登入功能所必需的,但最終有一個令人愉悅的 UI 會很好,所以您不妨從首頁中的一些基本內容開始。

如果您啟動應用程式並載入首頁,您會注意到樣式表尚未載入。因此,您需要新增 jQuery 和 Twitter Bootstrap 來新增這些樣式表

pom.xml
<dependency>
	<groupId>org.webjars</groupId>
	<artifactId>jquery</artifactId>
	<version>3.4.1</version>
</dependency>
<dependency>
	<groupId>org.webjars</groupId>
	<artifactId>bootstrap</artifactId>
	<version>4.3.1</version>
</dependency>
<dependency>
	<groupId>org.webjars</groupId>
	<artifactId>webjars-locator-core</artifactId>
</dependency>

最後一個相依性是 webjars "locator",它由 webjars 網站作為一個程式庫提供。Spring 可以使用定位器來定位 webjars 中的靜態資源,而無需知道確切的版本(因此 index.html 中的無版本 /webjars/** 連結)。只要您不關閉 MVC 自動配置,webjar 定位器預設會在 Spring Boot 應用程式中啟用。

完成這些變更後,您的應用程式應該有一個看起來不錯的首頁。

使用 GitHub 和 Spring Security 保護應用程式

為了使應用程式安全,您可以簡單地將 Spring Security 新增為相依性。由於您想要執行“社交”登入(委派給 GitHub),因此您應該包含 Spring Security OAuth 2.0 Client starter

pom.xml
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

透過新增它,它將預設使用 OAuth 2.0 來保護您的應用程式。

接下來,您需要配置您的應用程式以使用 GitHub 作為身份驗證供應商。為此,請執行以下操作

新增新的 GitHub 應用程式

若要使用 GitHub 的 OAuth 2.0 身份驗證系統進行登入,您必須首先 新增新的 GitHub 應用程式

選取 "New OAuth App",然後會顯示 "Register a new OAuth application" 頁面。輸入應用程式名稱和描述。然後,輸入您的應用程式首頁,在本例中應為 https://127.0.0.1:8080。最後,將授權回呼 URL 指示為 https://127.0.0.1:8080/login/oauth2/code/github,然後點擊 Register Application

OAuth 重新導向 URI 是最終使用者的使用者代理程式在 GitHub 驗證身份並在Authorize application頁面上授予對應用程式的存取權後重新導向回的應用程式中的路徑。

預設重新導向 URI 範本為 {baseUrl}/login/oauth2/code/{registrationId}registrationIdClientRegistration 的唯一識別碼。

配置 application.yml

然後,若要建立到 GitHub 的連結,請將以下內容新增到您的 application.yml

application.yml
spring:
  security:
    oauth2:
      client:
        registration:
          github:
            clientId: github-client-id
            clientSecret: github-client-secret
# ...

只需使用您剛使用 GitHub 建立的 OAuth 2.0 憑證,將 github-client-id 替換為客戶端 ID,並將 github-client-secret 替換為客戶端密碼。

啟動應用程式

完成該變更後,您可以再次執行您的應用程式並訪問 https://127.0.0.1:8080 的首頁。現在,您應該被重新導向到使用 GitHub 登入,而不是首頁。如果您這樣做,並接受您被要求進行的任何授權,您將被重新導向回本地應用程式,並且首頁將可見。

如果您保持登入 GitHub,即使您在沒有 cookie 和沒有快取資料的全新瀏覽器中開啟它,您也不必使用此本地應用程式重新進行身份驗證。(這就是單點登入的含義。)

如果您正在使用範例應用程式完成本節,請務必清除瀏覽器快取中的 cookie 和 HTTP 基本憑證。對單個伺服器執行此操作的最佳方法是開啟一個新的私人視窗。

授予對此範例的存取權是安全的,因為只有在本地執行的應用程式才能使用令牌,並且它要求的範圍受到限制。但是,請注意您在登入像這樣的應用程式時批准的內容:它們可能會要求您執行超出您舒適範圍的操作的權限(例如,它們可能會要求您更改個人資料的權限,這不太可能符合您的利益)。

剛剛發生了什麼?

您剛才編寫的應用程式在 OAuth 2.0 術語中是一個Client Application,它使用授權碼授予來從 GitHub(授權伺服器)取得存取令牌。

然後,它使用存取令牌向 GitHub 詢問一些個人詳細資料(僅限您允許它執行的操作),包括您的登入 ID 和您的姓名。在此階段,GitHub 充當資源伺服器,解碼您傳送的令牌,並檢查它是否授予應用程式存取使用者詳細資料的權限。如果該過程成功,應用程式會將使用者詳細資料插入到 Spring Security 上下文中,以便您通過身份驗證。

如果您查看瀏覽器工具(在 Chrome 或 Firefox 上按 F12)並追蹤所有躍點的網路流量,您將看到與 GitHub 來回重新導向,最後您將使用新的 Set-Cookie 標頭回到首頁。此 cookie(預設情況下為 JSESSIONID)是 Spring(或任何基於 servlet 的)應用程式的身份驗證詳細資料的令牌。

因此,我們有一個安全的應用程式,就某種意義而言,若要查看任何內容,使用者必須使用外部供應商 (GitHub) 進行身份驗證。

我們不想將其用於網路銀行網站。但對於基本識別目的,以及區分您網站的不同使用者之間的內容,這是一個極好的起點。這就是為什麼這種身份驗證現在非常流行的原因。

在下一節中,我們將向應用程式添加一些基本功能。我們還將使使用者在最初重新導向到 GitHub 時更加清楚地了解正在發生的事情。

新增歡迎頁面

在本節中,您將透過新增一個明確的連結以使用 GitHub 登入來修改您剛建立的 simple 應用程式。新的連結將顯示在首頁上,而不是立即被重新導向,使用者可以選擇登入或保持未經身份驗證。只有在使用者點擊連結後,才會呈現安全內容。

首頁上的條件內容

若要在使用者通過身份驗證的條件下呈現內容,您可以選擇伺服器端或用戶端呈現。

在這裡,您將使用 JQuery 更改用戶端,但是如果您更喜歡使用其他東西,則不應該很難翻譯用戶端代碼。

若要開始使用動態內容,您需要標記一些 HTML 元素,如下所示

index.html
<div class="container unauthenticated">
    With GitHub: <a href="/oauth2/authorization/github">click here</a>
</div>
<div class="container authenticated" style="display:none">
    Logged in as: <span id="user"></span>
</div>

預設情況下,第一個 <div> 將顯示,而第二個將不顯示。另請注意帶有 id 屬性的空 <span>

稍後,您將新增一個伺服器端端點,該端點將以 JSON 形式傳回登入的使用者詳細資料。

但是,首先,加入以下 JavaScript 程式碼,它會呼叫該端點。根據端點的回應,此 JavaScript 會將使用者的名稱填入 <span> 標籤中,並適當地切換 <div>

index.html
<script type="text/javascript">
    $.get("/user", function(data) {
        $("#user").html(data.name);
        $(".unauthenticated").hide()
        $(".authenticated").show()
    });
</script>

請注意,此 JavaScript 預期伺服器端點的名稱為 /user

/user 端點

現在,您將加入剛剛提到的伺服器端點,並將其命名為 /user。它會傳回目前登入的使用者,這在我們的主類別中可以很容易地做到。

SocialApplication.java
@SpringBootApplication
@RestController
public class SocialApplication {

    @GetMapping("/user")
    public Map<String, Object> user(@AuthenticationPrincipal OAuth2User principal) {
        return Collections.singletonMap("name", principal.getAttribute("name"));
    }

    public static void main(String[] args) {
        SpringApplication.run(SocialApplication.class, args);
    }

}

請注意 @RestController@GetMapping 的使用,以及注入到處理常式方法中的 OAuth2User

在端點中傳回整個 OAuth2User 並不是一個好主意,因為它可能包含您不希望向瀏覽器客戶端公開的資訊。

公開首頁

您還需要做最後一個變更。

這個應用程式現在可以正常運作,並像以前一樣進行身份驗證,但它仍然會在顯示頁面之前重新導向。為了使連結可見,我們還需要透過擴充 WebSecurityConfigurerAdapter 來關閉首頁的安全性。

SocialApplication
@SpringBootApplication
@RestController
public class SocialApplication extends WebSecurityConfigurerAdapter {

    // ...

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    	// @formatter:off
        http
            .authorizeRequests(a -> a
                .antMatchers("/", "/error", "/webjars/**").permitAll()
                .anyRequest().authenticated()
            )
            .exceptionHandling(e -> e
                .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
            )
            .oauth2Login();
        // @formatter:on
    }

}

Spring Boot 對於使用 @SpringBootApplication 註釋的類別上的 WebSecurityConfigurerAdapter 具有特殊意義:它使用它來配置攜帶 OAuth 2.0 身份驗證處理器的安全篩選器鏈。

上述配置指示允許的端點白名單,其他每個端點都需要身份驗證。

您想要允許

  • /,因為那是您剛才製作的動態頁面,其中一些內容對未經驗證的使用者可見

  • /error,因為那是 Spring Boot 用於顯示錯誤的端點,以及

  • /webjars/**,因為您希望您的 JavaScript 為所有訪客執行,無論是否已驗證

但是,您不會在此配置中看到任何關於 /user 的資訊。除非因為結尾的 .anyRequest().authenticated() 配置而被指示,否則所有內容(包括 /user)都保持安全。

最後,由於我們透過 Ajax 與後端互動,因此我們會希望配置端點以回應 401,而不是重新導向到登入頁面的預設行為。配置 authenticationEntryPoint 可以為我們實現這一點。

完成這些變更後,應用程式就完成了,如果您執行它並造訪首頁,您應該會看到一個設計精美的 HTML 連結,指向 "login with GitHub"。該連結不會直接將您帶到 GitHub,而是帶到處理身份驗證的本地路徑(並將重新導向發送到 GitHub)。一旦您通過身份驗證,您將被重新導向回本地應用程式,現在它會顯示您的姓名(假設您已在 GitHub 中設定您的權限以允許存取該資料)。

新增登出按鈕

在本節中,我們將修改我們建立的 click 應用程式,新增一個允許使用者登出應用程式的按鈕。這似乎是一個簡單的功能,但它需要一些注意才能實現,因此值得花一些時間討論如何正確地做到這一點。大多數變更都與我們將應用程式從唯讀資源轉換為可讀寫資源有關(登出需要狀態變更),因此任何不只是靜態內容的實際應用程式都需要相同的變更。

用戶端變更

在客戶端,我們只需要提供一個登出按鈕和一些 JavaScript 來回呼伺服器,要求取消身份驗證。首先,在 UI 的 "authenticated" 部分,我們新增按鈕

index.html
<div class="container authenticated">
  Logged in as: <span id="user"></span>
  <div>
    <button onClick="logout()" class="btn btn-primary">Logout</button>
  </div>
</div>

然後,我們提供它在 JavaScript 中引用的 logout() 函數

index.html
var logout = function() {
    $.post("/logout", function() {
        $("#user").html('');
        $(".unauthenticated").show();
        $(".authenticated").hide();
    })
    return true;
}

logout() 函數會對 /logout 執行 POST 操作,然後清除動態內容。現在我們可以切換到伺服器端來實現該端點。

新增登出端點

Spring Security 內建支援 /logout 端點,它會為我們做正確的事情(清除會話並使 cookie 無效)。要配置端點,我們只需擴充 WebSecurityConfigurerAdapter 中現有的 configure() 方法

SocialApplication.java
@Override
protected void configure(HttpSecurity http) throws Exception {
	// @formatter:off
    http
        // ... existing code here
        .logout(l -> l
            .logoutSuccessUrl("/").permitAll()
        )
        // ... existing code here
    // @formatter:on
}

/logout 端點要求我們對其執行 POST 操作,並且為了保護使用者免受跨網站請求偽造 (CSRF,發音為 "sea surf") 的攻擊,它要求在請求中包含一個 token。該 token 的值與目前會話相關聯,這就是提供保護的原因,因此我們需要一種方法將該資料放入我們的 JavaScript 應用程式中。

許多 JavaScript 框架都內建支援 CSRF(例如,在 Angular 中他們稱之為 XSRF),但它的實作方式通常與 Spring Security 的開箱即用行為略有不同。例如,在 Angular 中,前端希望伺服器向其發送一個名為 "XSRF-TOKEN" 的 cookie,如果它看到該 cookie,它會將該值作為名為 "X-XSRF-TOKEN" 的 header 發送回去。我們可以使用我們簡單的 jQuery 客戶端實現相同的行為,然後伺服器端變更將在沒有或很少變更的情況下與其他前端實作一起運作。要讓 Spring Security 了解這一點,我們需要新增一個建立 cookie 的篩選器。

WebSecurityConfigurerAdapter 中,我們執行以下操作

SocialApplication.java
@Override
protected void configure(HttpSecurity http) throws Exception {
	// @formatter:off
    http
        // ... existing code here
        .csrf(c -> c
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
        )
        // ... existing code here
    // @formatter:on
}

在客戶端中新增 CSRF Token

由於我們沒有在此範例中使用更高層級的框架,因此您需要明確新增 CSRF token,您剛才使其可以從後端作為 cookie 使用。為了簡化程式碼,請包含 js-cookie 程式庫

pom.xml
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>js-cookie</artifactId>
    <version>2.1.0</version>
</dependency>

然後,您可以在 HTML 中參考它

index.html
<script type="text/javascript" src="/webjars/js-cookie/js.cookie.js"></script>

最後,您可以在 XHR 中使用 Cookies 方便方法

index.html
$.ajaxSetup({
  beforeSend : function(xhr, settings) {
    if (settings.type == 'POST' || settings.type == 'PUT'
        || settings.type == 'DELETE') {
      if (!(/^http:.*/.test(settings.url) || /^https:.*/
        .test(settings.url))) {
        // Only send the token to relative URLs i.e. locally.
        xhr.setRequestHeader("X-XSRF-TOKEN",
          Cookies.get('XSRF-TOKEN'));
      }
    }
  }
});

準備就緒!

完成這些變更後,我們就可以執行應用程式並嘗試新的登出按鈕。啟動應用程式並在新瀏覽器視窗中載入首頁。點擊 "Login" 連結將您帶到 GitHub(如果您已登入 GitHub,您可能不會注意到重新導向)。點擊 "Logout" 按鈕取消目前會話並將應用程式返回到未驗證狀態。如果您感到好奇,您應該能夠在瀏覽器與本地伺服器交換的請求中看到新的 cookie 和 header。

請記住,現在登出端點正在與瀏覽器客戶端一起運作,那麼所有其他 HTTP 請求(POST、PUT、DELETE 等)也將同樣運作良好。因此,這應該是一個具有更實際功能的應用程式的良好平台。

使用 GitHub 登入

在本節中,您將修改您已建立的 logout 應用程式,新增一個 sticker 頁面,以便最終使用者可以在多個憑證集中進行選擇。

讓我們新增 Google 作為最終使用者的第二個選項。

初始設定

要使用 Google 的 OAuth 2.0 身份驗證系統進行登入,您必須在 Google API Console 中設定一個專案以取得 OAuth 2.0 憑證。

請按照 OpenID Connect 頁面上的說明進行操作,從 "Setting up OAuth 2.0" 部分開始。

完成 "Obtain OAuth 2.0 credentials" 指示後,您應該擁有一個新的 OAuth Client,其憑證包含 Client ID 和 Client Secret。

設定重新導向 URI

此外,您需要像之前對 GitHub 所做的那樣提供重新導向 URI。

在 "Set a redirect URI" 子部分中,確保 Authorized redirect URIs 欄位設定為 https://127.0.0.1:8080/login/oauth2/code/google

新增客戶端註冊

然後,您需要配置客戶端以指向 Google。由於 Spring Security 的構建考慮了多個客戶端,因此您可以將我們的 Google 憑證與您為 GitHub 建立的憑證一起新增。

application.yml
spring:
  security:
    oauth2:
      client:
        registration:
          github:
            clientId: github-client-id
            clientSecret: github-client-secret
          google:
            client-id: google-client-id
            client-secret: google-client-secret

如您所見,Google 是 Spring Security 提供開箱即用支援的另一個提供者。

在客戶端中,變更很簡單 - 您可以只新增另一個連結

index.html
<div class="container unauthenticated">
  <div>
    With GitHub: <a href="/oauth2/authorization/github">click here</a>
  </div>
  <div>
    With Google: <a href="/oauth2/authorization/google">click here</a>
  </div>
</div>
URL 中的最終路徑應與 application.yml 中的客戶端註冊 ID 相符。
Spring Security 提供一個預設的提供者選擇頁面,可以透過指向 /login 而不是 /oauth2/authorization/{registrationId} 來訪問該頁面。

如何新增本地使用者資料庫

許多應用程式需要本地保存有關其使用者的資料,即使身份驗證委託給外部提供者也是如此。我們在這裡不顯示程式碼,但在兩個步驟中很容易做到。

  1. 為您的資料庫選擇一個後端,並為適合您需求的自定義 User 物件設定一些儲存庫(例如,使用 Spring Data),該物件可以從外部身份驗證完全或部分填充。

  2. 實作並公開 OAuth2UserService 以呼叫授權伺服器以及您的資料庫。您的實作可以委託給預設實作,它將完成呼叫授權伺服器的繁重工作。您的實作應傳回擴充您的自定義 User 物件並實作 OAuth2User 的內容。

提示:在 User 物件中新增一個欄位以連結到外部提供者中的唯一識別碼(不是使用者的姓名,而是外部提供者中帳戶獨有的內容)。

為未經驗證的使用者新增錯誤頁面

在本節中,您將修改您先前建立的 two-providers 應用程式,以向無法通過身份驗證的使用者提供一些回饋。同時,您將擴充身份驗證邏輯以包含一個規則,該規則僅允許屬於特定 GitHub 組織的使用者。 "organization" 是一個 GitHub 領域特定的概念,但可以為其他提供者設計類似的規則。例如,使用 Google,您可能只想驗證來自特定網域的使用者。

切換到 GitHub

two-providers 範例使用 GitHub 作為 OAuth 2.0 提供者

application.yml
spring:
  security:
    oauth2:
      client:
        registration:
          github:
            client-id: bd1c0a783ccdd1c9b9e4
            client-secret: 1a9030fbca47a5b2c28e92f19050bb77824b5ad1
          # ...

在客戶端中檢測身份驗證失敗

在客戶端,您可能希望為無法通過身份驗證的使用者提供一些回饋。為了方便起見,您可以新增一個 div,您最終會將一個資訊訊息新增到其中。

index.html
<div class="container text-danger error"></div>

然後,新增對 /error 端點的呼叫,使用結果填入 <div>

index.html
$.get("/error", function(data) {
    if (data) {
        $(".error").html(data);
    } else {
        $(".error").html('');
    }
});

error 函數會向後端檢查是否有任何錯誤要顯示

新增錯誤訊息

為了支援檢索錯誤訊息,您需要在身份驗證失敗時捕獲它。為此,您可以配置一個 AuthenticationFailureHandler,如下所示

protected void configure(HttpSecurity http) throws Exception {
	// @formatter:off
	http
	    // ... existing configuration
	    .oauth2Login(o -> o
            .failureHandler((request, response, exception) -> {
			    request.getSession().setAttribute("error.message", exception.getMessage());
			    handler.onAuthenticationFailure(request, response, exception);
            })
        );
}

上述程式碼會在每次身份驗證失敗時將錯誤訊息儲存到會話中。

然後,您可以新增一個簡單的 /error 控制器,如下所示

SocialApplication.java
@GetMapping("/error")
public String error(HttpServletRequest request) {
	String message = (String) request.getSession().getAttribute("error.message");
	request.getSession().removeAttribute("error.message");
	return message;
}
這將會取代應用程式中預設的 /error 頁面,這對我們的案例來說沒問題,但可能對您的需求來說不夠完善。

在伺服器上產生 401 錯誤

如果使用者無法或不想使用 GitHub 登入,Spring Security 已經會產生 401 回應,因此如果您驗證失敗(例如拒絕授權 token),應用程式已經可以正常運作。

為了讓事情更有趣一點,您可以擴展驗證規則,拒絕不屬於正確組織的使用者。

您可以使用 GitHub API 來了解更多關於使用者的資訊,因此您只需要將其插入到驗證流程的正確部分即可。

幸運的是,對於這樣簡單的用例,Spring Boot 提供了一個簡單的擴充點:如果您宣告一個型別為 OAuth2UserService@Bean,它將被用來識別使用者主體。您可以使用這個 Hook 來斷言使用者是否屬於正確的組織,如果不是,則拋出例外。

SocialApplication.java
@Bean
public OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService(WebClient rest) {
    DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
    return request -> {
        OAuth2User user = delegate.loadUser(request);
        if (!"github".equals(request.getClientRegistration().getRegistrationId())) {
        	return user;
        }

        OAuth2AuthorizedClient client = new OAuth2AuthorizedClient
                (request.getClientRegistration(), user.getName(), request.getAccessToken());
        String url = user.getAttribute("organizations_url");
        List<Map<String, Object>> orgs = rest
                .get().uri(url)
                .attributes(oauth2AuthorizedClient(client))
                .retrieve()
                .bodyToMono(List.class)
                .block();

        if (orgs.stream().anyMatch(org -> "spring-projects".equals(org.get("login")))) {
            return user;
        }

        throw new OAuth2AuthenticationException(new OAuth2Error("invalid_token", "Not in Spring Team", ""));
    };
}

請注意,這段程式碼依賴於一個 WebClient 實例,代表已驗證的使用者存取 GitHub API。完成後,它會遍歷組織,尋找與 "spring-projects" 匹配的組織(這是用於儲存 Spring 開源專案的組織)。如果您想要成功驗證,但您不在 Spring Engineering 團隊中,您可以替換成您自己的值。如果沒有匹配項,它會拋出一個 OAuth2AuthenticationException,Spring Security 會捕獲這個例外並將其轉換為 401 回應。

WebClient 也必須創建為一個 bean,但這很簡單,因為它的所有組成部分都可以通過使用 spring-boot-starter-oauth2-client 自動注入。

@Bean
public WebClient rest(ClientRegistrationRepository clients, OAuth2AuthorizedClientRepository authz) {
    ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 =
            new ServletOAuth2AuthorizedClientExchangeFilterFunction(clients, authz);
    return WebClient.builder()
            .filter(oauth2).build();
}
顯然,上面的程式碼可以推廣到其他驗證規則,有些適用於 GitHub,有些適用於其他 OAuth 2.0 提供者。您所需要的只是 WebClient 和一些關於供應商 API 的知識。

結論

我們已經看到了如何使用 Spring Boot 和 Spring Security 以非常少的努力構建多種風格的應用程式。貫穿所有範例的主題是使用外部 OAuth 2.0 提供者進行身份驗證。

所有的範例應用程式都可以輕鬆地擴展和重新配置,以用於更特定的用例,通常只需要更改配置文件。請記住,如果您在自己的伺服器中使用範例的版本,請向 GitHub(或類似服務)註冊,並取得您自己主機地址的客戶端憑證。並且記住不要將這些憑證放在原始碼控制中!

想撰寫新的指南或貢獻現有的指南嗎?請查看我們的 貢獻指南

所有指南均以 ASLv2 授權發布程式碼,並以 姓名標示-禁止改作創用 CC 3.0 授權條款發布寫作內容。

取得程式碼