
本指南介紹如何使用“社交登錄”構建示例應用,以執行各種操作OAuth 2.0和彈簧啟動.
它從簡單的單提供程序單一登錄開始,一直到具有身份驗證提供程序選擇的客戶端:GitHub?或谷歌.
【資料圖】
這些示例都是在后端使用 Spring Boot 和 Spring Security 的單頁應用程序。他們也都使用普通jQuery在前端。但是,轉換為不同的JavaScript框架或使用服務器端渲染所需的更改將是最小的。
所有示例均使用彈簧啟動.
有幾個示例相互構建,在每個步驟中添加新功能:
簡單:一個非常基本的靜態應用程序,只有一個主頁,并通過Spring Boot的OAuth 2.0配置屬性無條件登錄(如果您訪問主頁,您將被自動重定向到GitHub)。點擊:添加用戶必須單擊才能登錄的顯式鏈接。注銷:還為經過身份驗證的用戶添加注銷鏈接。兩個提供商:添加第二個登錄提供程序,以便用戶可以在主頁上選擇要使用的登錄提供程序。自定義錯誤:為未經身份驗證的用戶添加錯誤消息,以及基于 GitHub API 的自定義身份驗證。可以在功能階梯中跟蹤從一個應用程序遷移到下一個應用程序所需的更改源代碼.該應用程序的每個版本都是其自己的目錄,以便您可以比較它們的差異。 |
每個應用程序都可以導入到 IDE 中。可以在 中運行該方法以啟動應用。他們都想出了一個主頁??main?
???SocialApplication?
???http://localhost:8080??(如果您想登錄并查看內容,所有這些都要求您至少擁有一個 GitHub 和 Google 帳戶)。
您還可以使用或通過構建 jar 文件并使用 and 運行它(根據??mvn spring-boot:run?
???mvn package?
???java -jar target/*.jar?
?春季啟動文檔和其他可用文檔).如果您使用包裝紙在頂層,例如
$ cd simple$ ../mvnw package$ java -jar target/*.jar
這些應用程序都可以運行,因為它們將使用在GitHub和Google注冊的OAuth 2.0客戶端作為該地址。若要在不同的主機或端口上運行它們,需要以這種方式注冊應用。如果使用默認值,則不會有將憑據泄露到本地主機之外的危險。但是,請注意在 Internet 上公開的內容,不要將自己的應用注冊置于公共源代碼管理中。? |
在本部分中,你將創建一個使用 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?
?
在新項目中,在文件夾中創建。您應該添加一些樣式表和JavaScript鏈接,以便結果如下所示:??index.html?
???src/main/resources/static?
?
索引.html
Demo <script type="text/javascript" src="/webjars/jquery/jquery.min.js"></script> <script type="text/javascript" src="/webjars/bootstrap/js/bootstrap.min.js"></script> Demo
這些都不是演示 OAuth 2.0 登錄功能所必需的,但最終擁有一個令人愉快的 UI 會很好,所以你不妨從主頁中的一些基本內容開始。
如果啟動應用程序并加載主頁,則會注意到樣式表尚未加載。因此,您還需要通過添加jQuery和Twitter Bootstrap來添加它們:
絨球.xml
org.webjars jquery 3.4.1 org.webjars bootstrap 4.3.1 org.webjars webjars-locator-core
最后一個依賴項是 webjars “定位器”,它由 webjars 站點作為庫提供。Spring 可以使用定位器在 webjar 中定位靜態資產,而無需知道確切的版本(因此 中的無版本鏈接)。默認情況下,webjar 定位器在 Spring Boot 應用程序中處于激活狀態,只要您不關閉 MVC 自動配置即可。??/webjars/**?
???index.html?
?
完成這些更改后,應用應該有一個漂亮的主頁。
為了使應用程序安全,您可以簡單地將 Spring 安全性添加為依賴項。由于你想要做一個“社交”登錄(委托給GitHub),你應該包括Spring Security OAuth 2.0客戶端啟動器:
絨球.xml
org.springframework.boot spring-boot-starter-oauth2-client
通過添加它,它將默認使用 OAuth 2.0 保護您的應用程序。
接下來,您需要將應用配置為使用 GitHub 作為身份驗證提供程序。為此,請執行以下操作:
添加新的 GitHub 應用配置應用程序.yml啟動應用程序要使用 GitHub 的 OAuth 2.0 身份驗證系統進行登錄,您必須首先添加新的 GitHub 應用.
選擇“新建 OAuth 應用程序”,然后顯示“注冊新的 OAuth 應用程序”頁面。輸入應用名稱和說明。然后,輸入應用的主頁,該主頁應為??http://localhost:8080??,在這種情況下。最后,將授權回調 URL 指定為,然后單擊注冊應用程序。??http://localhost:8080/login/oauth2/code/github?
?
OAuth 重定向 URI 是最終用戶的用戶代理在向 GitHub 進行身份驗證并在“授權應用程序”頁面上授予對應用程序的訪問權限后重定向回應用程序中的路徑。
默認的重定向 URI 模板為 。注冊 ID是 的唯一標識符。? |
?application.yml?
?然后,要創建指向 GitHub 的鏈接,請將以下內容添加到您的:??application.yml?
?
應用程序.yml
spring: security: oauth2: client: registration: github: clientId: github-client-id clientSecret: github-client-secret# ...
只需使用剛剛通過 GitHub 創建的 OAuth 2.0 憑據,替換為客戶端 ID 和客戶端密碼。??github-client-id?
???github-client-secret?
?
通過此更改,您可以再次運行應用并訪問主頁??http://localhost:8080??.現在,您應該被重定向到使用 GitHub 登錄,而不是主頁。如果您這樣做,并接受要求您進行的任何授權,您將被重定向回本地應用程序,并且主頁將可見。
如果您保持登錄到 GitHub,則無需使用此本地應用程序重新進行身份驗證,即使您在沒有 Cookie 和緩存數據的新瀏覽器中打開它也是如此。(這就是單一登錄的含義。
如果您正在使用示例應用程序完成本節,請務必清除瀏覽器緩存中的 Cookie 和 HTTP 基本憑據。對于單個服務器執行此操作的最佳方法是打開一個新的專用窗口。 |
授予對此示例的訪問權限是安全的,因為只有本地運行的應用才能使用令牌,并且它要求的范圍是有限的。但是,當您登錄這樣的應用程序時,請注意您正在批準的內容:他們可能會要求允許做超出您滿意的事情(例如,他們可能會要求允許更改您的個人數據,這不太可能符合您的利益)。
您剛剛編寫的應用程序(在 OAuth 2.0 術語中)是一個客戶端應用程序,它使用授權代碼授予從 GitHub(授權服務器)獲取訪問令牌。
然后,它使用訪問令牌向 GitHub 詢問一些個人詳細信息(僅您允許它執行的操作),包括您的登錄 ID 和您的姓名。在此階段,GitHub 充當資源服務器,解碼您發送的令牌,并檢查它是否授予應用程序訪問用戶詳細信息的權限。如果該過程成功,應用程序會將用戶詳細信息插入到 Spring 安全性上下文中,以便對您進行身份驗證。
如果您查看瀏覽器工具(Chrome 或 Firefox 上的 F12)并跟蹤所有躍點的網絡流量,您將看到與 GitHub 來回重定向,最后您將以新標題返回主頁。此 cookie(默認情況下)是 Spring (或任何基于 servlet)應用程序的身份驗證詳細信息的令牌。??Set-Cookie?
???JSESSIONID?
?
因此,我們有一個安全的應用程序,從某種意義上說,要查看任何內容,用戶必須向外部提供商(GitHub)進行身份驗證。
我們不想將其用于網上銀行網站。但出于基本的識別目的,以及在網站的不同用戶之間隔離內容,這是一個很好的起點。這就是為什么這種身份驗證如今非常流行的原因。
在下一節中,我們將向應用程序添加一些基本功能。我們還將讓用戶在獲得初始重定向到 GitHub 時更清楚地了解發生了什么。
在本部分中,您將修改簡單您剛剛通過添加用于登錄 GitHub 的顯式鏈接構建的應用程序。新鏈接不會立即重定向,而是在主頁上可見,用戶可以選擇登錄或保持未經身份驗證。只有當用戶單擊鏈接時,才會呈現安全內容。
若要在用戶經過身份驗證的條件下呈現內容,可以選擇服務器端或客戶端呈現。
在這里,您將使用杰奎里,但如果您更喜歡使用其他內容,則翻譯客戶端代碼應該不是很困難。
要開始使用動態內容,您需要標記幾個 HTML 元素,如下所示:
索引.html
With GitHub: click hereLogged in as:
默認情況下,第一個將顯示,第二個不會。另請注意帶有屬性的空。? 稍后,你將添加一個服務器端終結點,該終結點將以 JSON 形式返回登錄的用戶詳細信息。 但是,首先,添加以下 JavaScript,它將訪問該端點。根據端點的響應,此 JavaScript 將使用用戶名填充標記并相應地切換:? 索引.html 請注意,此 JavaScript 需要調用服務器端端點。? 現在,您將添加剛才提到的服務器端終結點,調用它。它將發回當前登錄的用戶,我們可以在主類中輕松完成此操作:? 社交應用.java 請注意 、 和注入到處理程序方法中的用法。? 在終結點中返回 whole 不是一個好主意,因為它可能包含您不希望向瀏覽器客戶端透露的信息。? 您需要進行最后一項更改。 此應用程序現在可以正常工作并像以前一樣進行身份驗證,但它仍然會在顯示頁面之前重定向。為了使鏈接可見,我們還需要通過擴展來關閉主頁上的安全性:? 社交應用 Spring Boot 對 a 附加了特殊含義,該類的注釋為 : 它使用它來配置攜帶 OAuth 2.0 身份驗證處理器的安全過濾器鏈。? 上述配置指示允許的端點的白名單,其他每個端點都需要身份驗證。 您希望允許: 但是,您不會在此配置中看到任何有關此配置的內容。所有內容(包括)都保持安全,除非由于最后的配置而指明。? 最后,由于我們通過 Ajax 與后端接口,因此我們希望將端點配置為使用 401 響應,而不是重定向到登錄頁面的默認行為。配置為我們實現了這一目標。? 完成這些更改后,應用程序就完成了,如果您運行它并訪問主頁,您應該會看到一個樣式精美的 HTML 鏈接,指向“使用 GitHub 登錄”。該鏈接不會將您直接帶到 GitHub,而是轉到處理身份驗證(并將重定向發送到 GitHub)的本地路徑。進行身份驗證后,您將被重定向回本地應用程序,它現在顯示您的姓名(假設您已在 GitHub 中設置權限以允許訪問該數據)。 在本節中,我們將修改點擊我們通過添加一個允許用戶注銷應用程序的按鈕來構建應用程序。這似乎是一個簡單的功能,但它需要一點小心來實現,所以值得花一些時間討論如何做到這一點。大多數更改都與以下事實有關:我們正在將應用程序從只讀資源轉換為讀寫資源(注銷需要更改狀態),因此在任何不僅僅是靜態內容的實際應用程序中都需要相同的更改。 在客戶端上,我們只需要提供一個注銷按鈕和一些 JavaScript 來回調服務器以請求取消身份驗證。首先,在 UI 的“經過身份驗證”部分,我們添加按鈕: 索引.html 然后我們提供它在 JavaScript 中引用的函數:? 索引.html 該函數執行 POST 操作,然后清除動態內容。現在我們可以切換到服務器端來實現該端點。? Spring 安全性內置了對端點的支持,該端點將為我們做正確的事情(清除會話并使 cookie 無效)。要配置端點,我們只需擴展以下中的現有方法:? 社交應用.java 端點要求我們向其 POST 并保護用戶免受跨站點請求偽造(CSRF,發音為“海上沖浪”)的侵害,它需要將令牌包含在請求中。令牌的值鏈接到當前會話,這是提供保護的原因,因此我們需要一種方法將這些數據放入我們的 JavaScript 應用程序中。? 許多JavaScript框架都內置了對CSRF的支持(例如,在Angular中,他們稱之為XSRF),但它的實現方式通常與Spring Security的開箱即用行為略有不同。例如,在Angular中,前端希望服務器向它發送一個名為“XSRF-TOKEN”的cookie,如果它看到這一點,它將把值作為名為“X-XSRF-TOKEN”的標頭發送回去。我們可以用簡單的jQuery客戶端實現相同的行為,然后服務器端的更改將與其他前端實現一起工作,沒有或很少的更改。為了向 Spring Security 傳授這一點,我們需要添加一個創建 cookie 的過濾器。 在我們執行以下操作:? 社交應用.java 由于此示例中我們沒有使用更高級別的框架,因此您需要顯式添加 CSRF 令牌,您剛剛將其作為后端的 cookie 提供。為了使代碼更簡單一些,請包含庫:? 絨球.xml 然后,您可以在 HTML 中引用它: 索引.html 最后,您可以在 XHR 中使用方便的方法:? 索引.html 完成這些更改后,我們已準備好運行應用程序并嘗試新的注銷按鈕。啟動應用并在新的瀏覽器窗口中加載主頁。單擊“登錄”鏈接將您帶到GitHub(如果您已經登錄,則可能不會注意到重定向)。單擊“注銷”按鈕以取消當前會話并將應用程序返回到未經身份驗證的狀態。如果您好奇,您應該能夠在瀏覽器與本地服務器交換的請求中看到新的 cookie 和標頭。 請記住,現在注銷端點正在與瀏覽器客戶端一起使用,然后所有其他HTTP請求(POST,PUT,DELETE等)也將正常工作。因此,對于具有一些更現實功能的應用程序來說,這應該是一個很好的平臺。 在本部分中,您將修改注銷您已經構建的應用程序,添加貼紙頁面,以便最終用戶可以在多組憑據之間進行選擇。 讓我們將Google添加為最終用戶的第二個選項。 要使用 Google 的 OAuth 2.0 身份驗證系統進行登錄,您必須在 Google API 控制臺中設置一個項目以獲取 OAuth 2.0 憑據。 谷歌的OAuth 2.0實現?對于身份驗證符合OpenID Connect 1.0?規格和開放身份認證. 按照OpenID Connect頁面,從“設置 OAuth 2.0”部分開始。 完成“獲取 OAuth 2.0 憑據”說明后,您應該有一個新的 OAuth 客戶端,其憑據由客戶端 ID 和客戶端密鑰組成。 此外,還需要提供重定向 URI,就像之前為 GitHub 所做的那樣。 在“設置重定向 URI”子部分中,確保“授權的重定向 URI”字段設置為 。? 然后,您需要將客戶端配置為指向谷歌。由于 Spring Security 是在考慮多個客戶端的情況下構建的,因此您可以將我們的 Google 憑據添加到您為 GitHub 創建的憑據旁邊: 應用程序.yml 如您所見,Google是Spring Security提供開箱即用支持的另一個提供商。 在客戶端中,更改是微不足道的 - 您只需添加另一個鏈接: 索引.html URL 中的最終路徑應與 中的客戶端注冊 ID 匹配。? Spring 安全性附帶了一個默認的提供程序選擇頁面,可以通過指向而不是 來訪問該頁面。? 許多應用程序需要在本地保存有關其用戶的數據,即使身份驗證委托給外部提供程序也是如此。我們不在這里顯示代碼,但很容易通過兩個步驟完成。 提示:在對象中添加一個字段以鏈接到外部提供程序中的唯一標識符(不是用戶名,而是外部提供程序中帳戶的唯一名稱)。? 在本部分中,您將修改兩個提供商之前構建的應用,用于向無法進行身份驗證的用戶提供一些反饋。同時,您將擴展身份驗證邏輯以包含僅允許屬于特定 GitHub 組織的用戶的規則。“組織”是GitHub特定領域的概念,但可以為其他提供商設計類似的規則。例如,對于 Google,您可能只想對來自特定網域的用戶進行身份驗證。 這兩個提供商示例使用 GitHub 作為 OAuth 2.0 提供程序: 應用程序.yml 在客戶端上,您可能希望為無法進行身份驗證的用戶提供一些反饋。為了促進這一點,您可以添加一個 div,您最終將向其添加一條信息性消息。 索引.html 然后,添加對終結點的調用,并用結果填充 :? 索引.html 錯誤函數與后端一起檢查是否有任何要顯示的錯誤 若要支持檢索錯誤消息,需要在身份驗證失敗時捕獲它。為此,您可以配置一個 ,如下所示:? 每當身份驗證失敗時,上述內容都會將錯誤消息保存到會話中。 然后,您可以添加一個簡單的控制器,如下所示:? 社交應用.java 這將替換應用程序中的默認頁面,這對于我們的情況來說很好,但可能不夠復雜,無法滿足您的需求。? 如果用戶不能或不想使用 GitHub 登錄,則 Spring Security 已經發出 401 響應,因此如果您無法進行身份驗證(例如,通過拒絕令牌授予),該應用程序已經在工作。 為了給事情增添趣味,您可以擴展身份驗證規則以拒絕不在正確組織中的用戶。 您可以使用 GitHub API 了解有關用戶的更多信息,因此您只需將其插入身份驗證過程的正確部分。 幸運的是,對于這樣一個簡單的用例,Spring Boot 提供了一個簡單的擴展點:如果你聲明了一個 of 類型,它將用于標識用戶主體。您可以使用該掛鉤來斷言用戶在正確的組織中,如果不是,則引發異常:? 社交應用.java 請注意,此代碼依賴于代表經過身份驗證的用戶訪問 GitHub API 的實例。完成此操作后,它會遍歷組織,尋找與“spring-projects”匹配的組織(這是用于存儲Spring開源項目的組織)。如果您希望能夠成功進行身份驗證并且您不在 Spring 工程團隊中,則可以在此處替換您自己的值。如果沒有匹配項,它會拋出一個 ,Spring Security 會選取該響應并轉換為 401 響應。? 也必須創建為豆子,但這微不足道,因為它的成分都可以通過使用:? 顯然,上面的代碼可以推廣到其他身份驗證規則,有些適用于GitHub,有些適用于其他OAuth 2.0提供程序。您所需要的只是提供程序 API 的一些知識。? 我們已經看到了如何使用 Spring Boot 和 Spring Security 來構建多種風格的應用程序,只需很少的努力。貫穿所有示例的主題是使用外部 OAuth 2.0 提供程序進行身份驗證。 所有示例應用都可以輕松擴展和重新配置,以用于更具體的用例,通常只需更改配置文件即可。請記住,如果你在自己的服務器中使用示例版本向 GitHub(或類似)注冊并獲取你自己的主機地址的客戶端憑據。并且記住不要將這些憑據放在源代碼管理中!?
??
???id?
???
???
<script type="text/javascript"> $.get("/user", function(data) { $("#user").html(data.name); $(".unauthenticated").hide() $(".authenticated").show() });</script>
?/user?
?端點?
?/user?
??/user?
?@SpringBootApplication@RestControllerpublic class SocialApplication { @GetMapping("/user") public Map
?@RestController?
???@GetMapping?
???OAuth2User?
??OAuth2User?
?公開主頁
?WebSecurityConfigurerAdapter?
?@SpringBootApplication@RestControllerpublic 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 }}
?WebSecurityConfigurerAdapter?
???@SpringBootApplication?
??/?
?因為這是您剛剛動態化的頁面,其某些內容對未經身份驗證的用戶可見??/error?
?因為這是用于顯示錯誤的 Spring 引導端點,并且??/webjars/**?
?因為您希望您的 JavaScript 為所有訪問者運行,無論是否經過身份驗證?/user?
???/user?
???.anyRequest().authenticated()?
??authenticationEntryPoint?
?添加注銷按鈕
客戶端更改
?logout()?
?var logout = function() { $.post("/logout", function() { $("#user").html(""); $(".unauthenticated").show(); $(".authenticated").hide(); }) return true;}
?logout()?
???/logout?
?添加注銷端點
?/logout?
???configure()?
???WebSecurityConfigurerAdapter?
?@Overrideprotected void configure(HttpSecurity http) throws Exception { // @formatter:off http // ... existing code here .logout(l -> l .logoutSuccessUrl("/").permitAll() ) // ... existing code here // @formatter:on}
?/logout?
??WebSecurityConfigurerAdapter?
?@Overrideprotected void configure(HttpSecurity http) throws Exception { // @formatter:off http // ... existing code here .csrf(c -> c .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) ) // ... existing code here // @formatter:on}
在客戶端中添加 CSRF 令牌
?js-cookie?
?<script type="text/javascript" src="/webjars/js-cookie/js.cookie.js"></script>
?Cookies?
?$.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")); } } }});
準備滾動!
使用 GitHub 登錄
初始設置
設置重定向 URI
?http://localhost:8080/login/oauth2/code/google?
?添加客戶端注冊
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
添加登錄鏈接
?application.yml?
??/login?
???/oauth2/authorization/{registrationId}?
?如何添加本地用戶數據庫
User
實現并公開以調用授權服務器以及數據庫。您的實現可以委托給默認實現,這將完成調用授權服務器的繁重工作。您的實現應返回擴展自定義對象和實現的內容。OAuth2UserService
User
OAuth2User
?User?
?為未經身份驗證的用戶添加錯誤頁面
切換到 GitHub
spring: security: oauth2: client: registration: github: client-id: bd1c0a783ccdd1c9b9e4 client-secret: 1a9030fbca47a5b2c28e92f19050bb77824b5ad1 # ...
檢測客戶端中的身份驗證失敗
?/error?
???
$.get("/error", function(data) { if (data) { $(".error").html(data); } else { $(".error").html(""); }});
添加錯誤消息
?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?
?@GetMapping("/error")public String error(HttpServletRequest request) { String message = (String) request.getSession().getAttribute("error.message"); request.getSession().removeAttribute("error.message"); return message;}
?/error?
?在服務器中生成 401
?@Bean?
???OAuth2UserService?
?@Beanpublic OAuth2UserService
?WebClient?
???OAuth2AuthenticationException?
??WebClient?
???spring-boot-starter-oauth2-client?
?@Beanpublic WebClient rest(ClientRegistrationRepository clients, OAuth2AuthorizedClientRepository authz) { ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 = new ServletOAuth2AuthorizedClientExchangeFilterFunction(clients, authz); return WebClient.builder() .filter(oauth2).build();}
?WebClient?
?結論