国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Spring Security in Action 第三章 SpringSecurity管理用戶

這篇具有很好參考價值的文章主要介紹了Spring Security in Action 第三章 SpringSecurity管理用戶。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

本專欄將從基礎(chǔ)開始,循序漸進,以實戰(zhàn)為線索,逐步深入SpringSecurity相關(guān)知識相關(guān)知識,打造完整的SpringSecurity學(xué)習(xí)步驟,提升工程化編碼能力和思維能力,寫出高質(zhì)量代碼。希望大家都能夠從中有所收獲,也請大家多多支持。
專欄地址:SpringSecurity專欄
本文涉及的代碼都已放在gitee上:gitee地址
如果文章知識點有錯誤的地方,請指正!大家一起學(xué)習(xí),一起進步。
專欄匯總:專欄匯總


本章涵蓋了
  • 用UserDetails接口描述一個用戶
  • 在認證流程中使用UserDetailsService
  • 創(chuàng)建一個自定義的UserDetailsService的實現(xiàn)
  • 創(chuàng)建UserDetailsManager的自定義實現(xiàn)?在認證流程中使用JdbcUserDetailsManager

我的一位大學(xué)同事的廚藝很好。他不是高級餐廳的廚師,但他對烹飪相當有熱情。有一天,在討論中分享想法時,我問他如何設(shè)法記住這么多食譜。他告訴我,這很容易?!澳悴槐赜涀≌麄€食譜,但要記住基本成分之間的搭配方式。這就像一些現(xiàn)實世界的合約,告訴你什么可以混合或不應(yīng)該混合。然后對于每個配方,你只記得一些技巧”。

這個比喻類似于架構(gòu)的工作方式。對于任何強大的框架,我們都會使用契約來將框架的實現(xiàn)與建立在其上的應(yīng)用解耦。在Java中,我們使用接口來定義合同。程序員類似于廚師,知道各種成分是如何 "運作 "的,從而選擇合適的 “實現(xiàn)”。程序員知道框架的抽象,并使用這些抽象來與之整合。

本章是關(guān)于詳細了解你在第2章的第一個例子中遇到的基本角色之一–UserDetailsService。與UserDetailsService一起,我們將討論:

  • UserDetails,它為Spring Security描述用戶。
  • GrantedAuthority,它允許我們定義用戶可以執(zhí)行的動作。
  • UserDetailsManager,它擴展了UserDetailsService合約。除了繼承的行為,它還描述了創(chuàng)建用戶和修改或刪除用戶密碼等動作。

通過第二章,你已經(jīng)對UserDetailsService和PasswordEncoder在認證過程中的作用有了一個概念。但我們只討論了如何插入一個由你定義的實例,而不是使用Spring Boot配置的默認實例。我們還有更多細節(jié)要討論:

  • Spring Security提供了哪些實現(xiàn)以及如何使用它們
  • 如何為合同定義一個自定義的實現(xiàn),以及何時這樣做
  • 實現(xiàn)你在現(xiàn)實世界應(yīng)用中發(fā)現(xiàn)的接口的方法
  • 使用這些接口的最佳實踐

計劃從Spring Security如何理解用戶定義開始。為此,我們將討論UserDetails和GrantedAuthority合約。然后,我們將詳細介紹UserDetailsService以及UserDetailsManager如何擴展這個契約。你將應(yīng)用這些接口的實現(xiàn)(比如InMemoryUserDetailsManager,JdbcUserDetailsManager,以及LdapUserDetailsManager)。當這些實現(xiàn)不適合你的系統(tǒng)時,你會寫一個自定義實現(xiàn)。

3.1 在Spring Security中實現(xiàn)認證

在上一章中,我們開始了Spring Security的學(xué)習(xí)。在第一個例子中,我們討論了Spring Boot是如何定義一些默認值的,這些默認值定義了一個新的應(yīng)用程序最初的工作方式。你還學(xué)習(xí)了如何使用我們經(jīng)常在應(yīng)用程序中發(fā)現(xiàn)的各種替代方法來覆蓋這些默認值。但我們只考慮了這些的表面情況,以便你對我們要做的事情有一個概念。在這一章,以及第四章和第五章中,我們將更詳細地討論這些接口,以及不同的實現(xiàn)和你可能在現(xiàn)實世界的應(yīng)用中找到它們。

圖3.1展示了Spring Security中的認證流程。這個架構(gòu)是Spring Security實現(xiàn)的認證過程的骨干。了解它真的很重要,因為你將在任何Spring Security的實現(xiàn)中依賴它。你會發(fā)現(xiàn),我們幾乎在本書的所有章節(jié)中都討論了這個架構(gòu)的一部分。你會經(jīng)常看到它,以至于你可能會把它背下來,這很好。如果你知道這個架構(gòu),你就像一個知道自己的成分的廚師,可以把任何食譜放在一起。

在圖3.1中,陰影框代表我們開始使用的組件:UserDetailsService和PasswordEncoder。這兩個組件集中在流程的一部分,我經(jīng)常把它稱為 “用戶管理部分”。在本章中,UserDetailsService和PasswordEncoder是直接處理用戶細節(jié)和他們的證書的組件。我們將在第四章詳細討論PasswordEncoder。我還將在本書中詳細介紹你可以在認證流程中定制的其他組件:在第5章中,我們將看看AuthenticationProvider和SecurityContext,在第9章中,我們將看看過濾器。

Spring Security in Action 第三章 SpringSecurity管理用戶,# SpringSecurity,spring,區(qū)塊鏈,java

圖3.1 Spring Security的認證流程。AuthenticationFilter攔截請求并將認證責(zé)任委托給AuthenticationManager。為了實現(xiàn)認證邏輯,AuthenticationManager使用一個認證提供者。為了檢查用戶名和密碼,AuthenticationProvider使用UserDetailsService和PasswordEncoder。

作為用戶管理的一部分,我們使用UserDetailsService和UserDetailsManager接口。UserDetailsService只負責(zé)按用戶名檢索用戶。這個動作是框架完成認證所需要的唯一動作。UserDetailsManager增加了關(guān)于添加、修改或刪除用戶的行為,這在大多數(shù)應(yīng)用程序中都是必需的功能。這兩個契約之間的分離是接口隔離原則的一個很好的例子。分離接口可以獲得更好的靈活性,因為如果你的應(yīng)用程序不需要,框架不會強迫你實現(xiàn)行為。如果應(yīng)用程序只需要驗證用戶,那么實現(xiàn)UserDetailsService合同就足以涵蓋所需的功能。為了管理用戶,UserDetailsService和UserDetailsManager組件需要一種方法來表示它們。

Spring Security提供了UserDetails契約,你必須實現(xiàn)它來以框架理解的方式描述用戶。正如你在本章中所了解的,在Spring Security中,用戶有一組權(quán)限,也就是用戶被允許做的動作。我們將在第7章和第8章討論授權(quán)問題時,大量使用這些權(quán)限。但現(xiàn)在,Spring Security用GrantedAuthority接口表示用戶可以做的動作。我們通常稱這些權(quán)限,一個用戶有一個或多個權(quán)限。在圖3.2中,你可以看到認證流程中的用戶管理部分的組件之間的關(guān)系表示。

Spring Security in Action 第三章 SpringSecurity管理用戶,# SpringSecurity,spring,區(qū)塊鏈,java

圖3.2 參與用戶管理的組件之間的依賴關(guān)系。UserDetailsService返回一個用戶的詳細信息,通過其名字找到用戶。UserDetails合約描述了用戶。一個用戶有一個或多個權(quán)限,由GrantedAuthority接口表示。為了給用戶添加諸如創(chuàng)建、刪除或更改密碼等操作,UserDetailsManager契約擴展了UserDetailsService來添加操作。

了解Spring Security架構(gòu)中這些對象之間的聯(lián)系以及實現(xiàn)它們的方法,可以讓你在處理應(yīng)用程序時有多種選擇。這些選項中的任何一個都可能是你正在開發(fā)的應(yīng)用程序中的正確拼圖,你需要明智地做出選擇。但為了能夠選擇,你首先需要知道你可以選擇什么。

3.2 描述用戶

在本節(jié)中,你將學(xué)習(xí)如何描述你的應(yīng)用程序的用戶,以便Spring Security能夠理解他們。學(xué)習(xí)如何表示用戶并使框架了解他們是構(gòu)建認證流程的一個重要步驟?;谟脩?,應(yīng)用程序會做出一個決定–對某一功能的調(diào)用是否被允許。為了與用戶打交道,你首先需要了解如何在你的應(yīng)用程序中定義用戶的原型。在這一節(jié)中,我將通過實例描述如何在Spring Security應(yīng)用程序中為用戶建立一個藍圖。

對于Spring Security來說,用戶定義應(yīng)該尊重UserDetails合約。UserDetails合約代表了Spring Security所理解的用戶。你的應(yīng)用程序中描述用戶的類必須實現(xiàn)這個接口,通過這種方式,框架可以理解它。

3.2.1 解讀UserDetails合同的定義

在本節(jié)中,你將學(xué)習(xí)如何實現(xiàn)UserDetails接口來描述你的應(yīng)用程序中的用戶。我們將討論UserDetails合約所聲明的方法,以了解我們?nèi)绾我约盀槭裁匆獙崿F(xiàn)每一個方法。首先,讓我們看看下面列表中介紹的接口。

清單3.1 UserDetails 接口

Spring Security in Action 第三章 SpringSecurity管理用戶,# SpringSecurity,spring,區(qū)塊鏈,java

getUsername()和getPassword()方法返回,正如你所期望的,用戶名和密碼。應(yīng)用程序在認證過程中使用這些值,這些是該合同中唯一與認證有關(guān)的細節(jié)。其他五個方法都與授權(quán)用戶訪問應(yīng)用程序的資源有關(guān)。

一般來說,應(yīng)用程序應(yīng)該允許用戶做一些在應(yīng)用程序的上下文中有意義的動作。例如,用戶應(yīng)該能夠讀取數(shù)據(jù)、寫入數(shù)據(jù)或刪除數(shù)據(jù)。我們說一個用戶有或沒有執(zhí)行某個動作的權(quán)限,而一個權(quán)限代表一個用戶擁有的權(quán)限。我們實現(xiàn)getAuthorities()方法來返回授予用戶的權(quán)限組。

注意 正如你將在第7章中學(xué)習(xí)的那樣,Spring Security使用權(quán)限來指代細粒度的權(quán)限或角色,后者是權(quán)限的組。為了使你的閱讀更加輕松,在本書中,我把細粒度的權(quán)限稱為權(quán)限。

此外,正如在UserDetails合同中所看到的,用戶可以:

  • 讓賬戶過期
  • 鎖定賬戶
  • 讓憑證過期
  • 禁用該帳戶

如果你選擇在你的應(yīng)用程序的邏輯中實現(xiàn)這些用戶限制,你需要覆蓋以下方法:isAccountNonExpired(), isAccountNonLocked(), isCredentialsNonExpired(), isEnabled(),使那些需要啟用的方法返回true。并非所有的應(yīng)用程序都有過期或在某些條件下被鎖定的賬戶。如果你不需要在你的應(yīng)用程序中實現(xiàn)這些功能,你可以簡單地讓這四個方法返回真。

注意 UserDetails接口中最后四個方法的名字可能聽起來很奇怪??梢哉f,從簡潔的編碼和可維護性的角度來看,這些方法的選擇是不明智的。例如,isAccountNonExpired()這個名字看起來像一個雙重否定,乍一看,可能會產(chǎn)生混淆。但是,要注意分析所有四個方法的名稱。這些方法的命名是這樣的:在授權(quán)失敗的情況下,它們都返回false,否則返回true。這是正確的方法,因為人類的思維傾向于將 "假 "字與消極性聯(lián)系起來,將 "真 "字與積極的情況聯(lián)系起來。

3.2.2 關(guān)于GrantedAuthority合同的詳細說明

正如你在第3.2.1節(jié)UserDetails接口的定義中所觀察到的,授予一個用戶的行動被稱為權(quán)限。在第7章和第8章中,我們將基于這些用戶權(quán)限來編寫授權(quán)配置。因此,知道如何定義它們是很有必要的。

授權(quán)代表了用戶在你的應(yīng)用程序中可以做什么。沒有權(quán)限,所有的用戶都是平等的。雖然有一些簡單的應(yīng)用程序中的用戶是平等的,但在大多數(shù)實際情況下,一個應(yīng)用程序會定義多種類型的用戶。一個應(yīng)用程序可能有只能閱讀特定信息的用戶,而其他人也可以修改數(shù)據(jù)。而你需要根據(jù)應(yīng)用的功能需求,使你的應(yīng)用對他們進行區(qū)分,這就是用戶需要的權(quán)限。為了描述Spring Security中的權(quán)限,你可以使用GrantedAuthority接口。

在我們討論實現(xiàn)UserDetails之前,讓我們先了解一下GrantedAuthority接口。我們在定義用戶詳細信息時使用這個接口。它代表了授予用戶的特權(quán)。一個用戶可以沒有任何數(shù)量的權(quán)限,通常,他們至少有一個。下面是GrantedAuthority定義的實現(xiàn)。

public interface GrantedAuthority extends Serializable { 
    String getAuthority(); 
}

要創(chuàng)建一個權(quán)限,你只需要為該權(quán)限找到一個名稱,這樣你就可以在以后編寫授權(quán)規(guī)則時參考它。例如,一個用戶可以讀取應(yīng)用程序所管理的記錄或刪除它們。你可以根據(jù)你給這些動作起的名字來編寫授權(quán)規(guī)則。在第7章和第8章,你將學(xué)習(xí)如何根據(jù)用戶的權(quán)限來編寫授權(quán)規(guī)則。

在本章中,我們將實現(xiàn)getAuthority()方法,以字符串形式返回權(quán)限名稱。GrantedAuthority接口只有一個抽象方法,在本書中,你經(jīng)常會發(fā)現(xiàn)一些例子,我們使用lambda表達式來實現(xiàn)它。另一種可能性是使用SimpleGranted- Authority類來創(chuàng)建權(quán)限實例。

SimpleGrantedAuthority類提供了一種方法來創(chuàng)建GrantedAuthority類型的不可變實例。你在建立實例時提供了權(quán)限名稱。在接下來的代碼片段中,你會發(fā)現(xiàn)兩個實現(xiàn)GrantedAuthority的例子。在這里,我們利用一個lambda表達式,然后使用SimpleGrantedAuthority類。

注意 在用lambda表達式實現(xiàn)接口之前,用@FunctionalInterface注解驗證該接口是否被標記為功能性的,這是一個好的做法。這種做法的原因是,如果接口沒有被標記為功能性,就意味著其開發(fā)者保留了在未來版本中為其添加更多抽象方法的權(quán)利。在Spring Security中,GrantedAuthority接口沒有被標記為功能性的。然而,我們將在本書中使用lambda表達式來實現(xiàn)該接口,以使代碼更短、更容易閱讀,即使這不是我推薦你在真實世界的項目中做的事情。

3.2.3 編寫UserDetails的最小實現(xiàn)

在這一節(jié)中,你將編寫UserDetails合約的第一個實現(xiàn)。我們從一個基本的實現(xiàn)開始,其中每個方法返回一個靜態(tài)值。然后我們把它改成一個你更有可能在實際場景中找到的版本,一個允許你有多個不同用戶實例的版本?,F(xiàn)在你知道了如何實現(xiàn)UserDetails和GrantedAuthority接口,我們可以為一個應(yīng)用程序編寫最簡單的用戶定義。

通過一個名為DummyUser的類,我們來實現(xiàn)列表3.2中對用戶的最小描述。我使用這個類主要是為了演示實現(xiàn)UserDetails契約的方法。這個類的實例總是只提到一個用戶,“bill”,他有一個密碼 "12345 "和一個名為 "READ "的權(quán)限。

清單3.2 DummyUser類

public class DummyUser implements UserDetails {
    @Override
    public String getUsername() {
        return "bill";
    }
    @Override
    public String getPassword() {
        return "12345";
    }
// Omitted code
}

列表3.2中的類實現(xiàn)了UserDetails接口,需要實現(xiàn)它的所有方法。你可以在這里找到 getUsername() 和 getPassword() 的實現(xiàn)。在這個例子中,這些方法只為每個屬性返回一個固定的值。

接下來,我們?yōu)闄?quán)限列表添加一個定義。清單3.3顯示了getAuthorities()方法的實現(xiàn)。這個方法返回一個只有一個GrantedAuthority接口實現(xiàn)的集合。

清單3.3 getAuthorities()方法的實現(xiàn)

public class DummyUser implements UserDetails {
    // Omitted code
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(() -> "READ");
    }
// Omitted code
}

最后,你必須為UserDetails接口的最后四個方法添加一個實現(xiàn)。對于DummyUser類,這些方法總是返回true,這意味著用戶永遠是活躍的、可用的。你可以在下面的列表中找到這些例子。

清單3.4 最后四個UserDetails接口方法的實現(xiàn)

public class DummyUser implements UserDetails {
    // Omitted code
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public boolean isEnabled() {
        return true;
    }
// Omitted code
}

當然,這種最小的實現(xiàn)意味著該類的所有實例都代表同一個用戶。這是理解契約的一個良好開端,但不是你在實際應(yīng)用中會做的事情。對于一個真實的應(yīng)用,你應(yīng)該創(chuàng)建一個可以用來生成代表不同用戶的實例的類。在這種情況下,你的定義至少要把用戶名和密碼作為類中的屬性,如下面的列表所示。

清單3.5 一個更實用的UserDetails接口的實現(xiàn)

public class SimpleUser implements UserDetails {
    private final String username;
    private final String password;
    public SimpleUser(String username, String password) {
        this.username = username;
        this.password = password;
    }
    @Override
    public String getUsername() {
        return this.username;
    }
    @Override
    public String getPassword() {
        return this.password;
    }
// Omitted code
}

3.2.4 使用構(gòu)建器來創(chuàng)建UserDetails類型的實例

有些應(yīng)用程序很簡單,不需要自定義實現(xiàn)User- Details接口。在這一節(jié)中,我們看一下如何使用Spring Security提供的構(gòu)建器類來創(chuàng)建簡單的用戶實例。你不用在你的應(yīng)用程序中再聲明一個類,而是用User builder類快速獲得一個代表你的用戶的實例。

org.springframework.security.core.userdetails包中的User類是構(gòu)建UserDetails類型實例的一種簡單方法。使用這個類,你可以創(chuàng)建UserDetails的不可變的實例。你需要至少提供一個用戶名和一個密碼,而且用戶名不應(yīng)該是一個空字符串。清單3.6演示了如何使用這個構(gòu)建器。以這種方式構(gòu)建用戶,你不需要有UserDetails契約的實現(xiàn)。

清單3.6 用用戶構(gòu)建器類構(gòu)建一個用戶

UserDetails u = User.withUsername("bill") 
    .password("12345") 
    .authorities("read", "write") 
    .accountExpired(false)
    .disabled(true) .build();

以前面的列表為例,讓我們更深入地了解User構(gòu)建器類的結(jié)構(gòu)。User.withUsername(String username)方法返回一個嵌套在 User 類中的構(gòu)建器類 UserBuilder 的實例。另一種創(chuàng)建構(gòu)建器的方法是從另一個 UserDetails 的實例開始。在列表3.7中,第一行構(gòu)建了一個UserBuilder,從給定的字符串的用戶名開始。之后,我們演示了如何從一個已經(jīng)存在的 UserDetails 實例開始創(chuàng)建一個構(gòu)建器。

清單3.7 創(chuàng)建User.UserBuilder實例

//用他們的用戶名建立一個用戶。
User.UserBuilder builder1 = User.withUsername("bill");
      UserDetails u1 = builder1
      .password("12345")
      .authorities("read", "write")
          //密碼編碼器只是一個做編碼的函數(shù)。
      .passwordEncoder(p -> encode(p))
      .accountExpired(false)
      .disabled(true)
          //在構(gòu)建管道的末端,調(diào)用build()方法
      .build();
//你也可以從一個現(xiàn)有的UserDetails實例建立一個用戶。
User.UserBuilder builder2 = User.withUserDetails(u);
UserDetails u2 = builder2.build();

你可以看到,通過清單 3.7 中定義的任何一個構(gòu)建器,你可以使用構(gòu)建器來獲得由 UserDetails 合同代表的用戶。在構(gòu)建管道的末端,你調(diào)用 build() 方法。如果你提供了密碼,它將應(yīng)用定義的函數(shù)對密碼進行編碼,構(gòu)建 UserDetails 的實例,并返回它。

注意 密碼編碼器與我們在第2章討論的bean不同。這個名字可能讓人困惑,但在這里我們只有一個函數(shù)<String, String>。這個函數(shù)的唯一職責(zé)是在給定的編碼中轉(zhuǎn)換一個密碼字。在下一節(jié)中,我們將詳細討論我們在第二章中使用的來自Spring Security的PasswordEncoder合約。

3.2.5 結(jié)合與用戶有關(guān)的多種責(zé)任

在上一節(jié)中,你學(xué)到了如何實現(xiàn)UserDetails接口。在現(xiàn)實世界的場景中,它往往更復(fù)雜。在大多數(shù)情況下,你會發(fā)現(xiàn)一個用戶與多個職責(zé)相關(guān)。而如果你把用戶存儲在數(shù)據(jù)庫中,然后在應(yīng)用程序中,你也需要一個類來表示持久化實體?;蛘?,如果你通過網(wǎng)絡(luò)服務(wù)從另一個系統(tǒng)檢索用戶,那么你可能需要一個數(shù)據(jù)傳輸對象來表示用戶實例。假設(shè)第一種情況很簡單,但也很典型,讓我們考慮一下,我們在一個SQL數(shù)據(jù)庫中有一個表,我們在其中存儲用戶。為了讓這個例子更簡短,我們只給每個用戶一個權(quán)限。下面的列表顯示了映射該表的實體類。

清單3.8 定義JPA用戶實體類

@Entity
public class User {
  @Id
  private Long id;
  private String username;
  private String password;
  private String authority;
// Omitted getters and setters
}

如果你讓同一個類也為用戶實現(xiàn)Spring Security合約的 細節(jié),這個類就會變得更加復(fù)雜。你對下一個列表中的代碼有什么看法?看起來如何?從我的角度來看,它是一團糟。我會迷失在其中。

清單3.9 用戶類有兩個職責(zé)

Spring Security in Action 第三章 SpringSecurity管理用戶,# SpringSecurity,spring,區(qū)塊鏈,java

該類包含JPA注釋、getters和setters,其中g(shù)etUsername()和getPassword()都覆蓋了UserDetails合同中的方法。它有一個返回字符串的getAuthority()方法,以及一個返回集合的getAuthorities()方法。getAuthority()方法只是類中的一個getter,而getAuthorities()實現(xiàn)了UserDetails接口中的方法。而在添加與其他實體的關(guān)系時,事情就變得更加復(fù)雜了。再說一遍,這段代碼一點也不友好!

我們怎樣才能把這段代碼寫得更干凈呢?前面的代碼例子的泥濘方面的根源是兩個責(zé)任的混合。雖然在應(yīng)用程序中確實需要這兩種職責(zé),但在這種情況下,沒有人說你必須把它們放在同一個類中。讓我們試著通過定義一個單獨的名為SecurityUser的類來分離這些職責(zé),該類裝飾User類。如清單3.10所示,SecurityUser類實現(xiàn)了UserDetails契約,并使用它將我們的用戶插入到Spring的安全架構(gòu)中。User類只剩下它的JPA實體責(zé)任。

清單3.10 將用戶類僅作為JPA實體來實現(xiàn)

@Entity
public class User {
  @Id
  private int id;
  private String username;
  private String password;
  private String authority;
// Omitted getters and setters
}

列表3.10中的User類只剩下了它的JPA實體責(zé)任,因此,變得更加可讀。如果你閱讀這段代碼,你現(xiàn)在可以只關(guān)注與持久化有關(guān)的細節(jié) 與持久化相關(guān)的細節(jié),從Spring Security的角度來看,這些細節(jié)并不重要。在 下一個列表中,我們實現(xiàn)了SecurityUser類來包裝用戶實體。

清單3.11 SecurityUser類實現(xiàn)了UserDetails合同。

public class SecurityUser implements UserDetails {
  private final User user;
  public SecurityUser(User user) {
    this.user = user;
  }
  @Override
  public String getUsername() {
    return user.getUsername();
  }
  @Override
  public String getPassword() {
    return user.getPassword();
  }
  @Override
  public Collection<? extends GrantedAuthority> getAuthorities() {
    return List.of(() -> user.getAuthority());
  }
// Omitted code
}

正如你所看到的,我們使用SecurityUser類只是為了將系統(tǒng)中的用戶細節(jié)映射到Spring Security所理解的UserDetails契約中。為了表明SecurityUser在沒有用戶實體的情況下是沒有意義的,我們把這個字段變成了最終的。你必須通過構(gòu)造函數(shù)來提供用戶。SecurityUser類對User實體類進行了分級,并添加了與Spring Security合同相關(guān)的所需代碼,而沒有將代碼混入JPA實體,從而實現(xiàn)了多個不同的任務(wù)。

注意 你可以找到不同的方法來分離這兩項職責(zé)。我不想說我在本節(jié)中介紹的方法是最好的或唯一的。通常情況下,你選擇的實現(xiàn)類設(shè)計的方式在不同的情況下有很大的不同。但主要的想法是一樣的:避免混合責(zé)任,盡量寫出解耦的代碼,以提高應(yīng)用程序的可維護性。

3.3 指導(dǎo)Spring Security如何管理用戶

在上一節(jié)中,你實現(xiàn)了UserDetails契約來描述用戶,以便Spring Security能夠理解他們。但Spring Security是如何管理用戶的呢?在比較憑證時,他們是從哪里來的,以及你如何添加新的用戶或改變現(xiàn)有的用戶?在第2章中,你了解到框架定義了一個特定的組件,認證過程將用戶管理委托給它:UserDetailsService實例。我們甚至定義了一個UserDetailsService來覆蓋Spring Boot提供的默認實現(xiàn)。

在這一節(jié)中,我們嘗試了實現(xiàn)UserDetailsService類的各種方法。通過在我們的例子中實現(xiàn)UserDetailsService合約所描述的責(zé)任,你將了解用戶管理是如何工作的。之后,你會發(fā)現(xiàn)UserDetailsManager接口如何為UserDetailsService定義的契約增加更多的行為。在本節(jié)的最后,我們將使用Spring Security提供的UserDetailsManager接口的實現(xiàn)。我們將寫一個示例項目,其中我們將使用Spring Security提供的最著名的實現(xiàn)之一–JdbcUserDetailsManager。通過學(xué)習(xí),你將知道如何告訴Spring Security在哪里找到用戶,這在認證流程中是至關(guān)重要的。

3.3.1 了解UserDetailsService合同

在本節(jié)中,你將了解UserDetailsService接口的定義。在理解如何以及為什么要實現(xiàn)它之前,你必須首先理解契約。現(xiàn)在是時候詳細介紹UserDetailsService以及如何與這個組件的實現(xiàn)一起工作了。UserDetailsService接口只包含一個方法,如下所示。

Spring Security in Action 第三章 SpringSecurity管理用戶,# SpringSecurity,spring,區(qū)塊鏈,java

認證的實現(xiàn)會調(diào)用loadUserByUsername(String username)方法來獲取具有給定用戶名的用戶的詳細信息(圖3.3)。當然,該用戶名被認為是唯一的。這個方法返回的用戶是UserDetails合約的一個實現(xiàn)。如果該用戶名不存在,該方法會拋出一個 UsernameNotFoundException。

Spring Security in Action 第三章 SpringSecurity管理用戶,# SpringSecurity,spring,區(qū)塊鏈,java

圖3.3 AuthenticationProvider是實現(xiàn)認證邏輯的組件,它使用UserDetailsService來加載用戶的詳細信息。為了按用戶名查找用戶,它調(diào)用loadUserByUsername(String username)方法。

注意 UsernameNotFoundException是一個RuntimeException。UserDetailsService接口中的throws子句僅僅是為了記錄的目的。UsernameNotFoundException直接繼承自AuthenticationException類型,它是所有與認證過程相關(guān)的異常的父類。AuthenticationException進一步繼承了RuntimeException類。

3.3.2 實現(xiàn)UserDetailsService合同

在本節(jié)中,我們通過一個實際的例子來演示UserDetailsService的實現(xiàn)。你的應(yīng)用程序管理著關(guān)于證書和其他用戶方面的細節(jié)。這些信息可能存儲在數(shù)據(jù)庫中,也可能由你通過Web服務(wù)或其他方式訪問的另一個系統(tǒng)處理(圖3.3)。不管這在你的系統(tǒng)中是如何發(fā)生的,Spring Security需要你做的唯一一件事就是實現(xiàn)按用戶名檢索用戶。

在下一個例子中,我們寫一個UserDetailsService,它有一個內(nèi)存中的用戶列表。在第2章中,你使用了一個提供的實現(xiàn)來做同樣的事情,即InMemoryUserDetailsManager。因為你已經(jīng)熟悉了這個實現(xiàn)的工作方式,所以我選擇了一個類似的功能,但這次是由我們自己實現(xiàn)的。當我們創(chuàng)建UserDetails- Service類的實例時,我們提供一個用戶列表。你可以在項目sia-ch3-ex1中找到這個例子。在名為model的包中,我們定義了UserDetails,如下表所示。

清單3.12 UserDetails接口的實現(xiàn)

package com.laurentiuspilca.ssia.model;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

public class User implements UserDetails {
	//用戶類是不可改變的。當你建立實例時,你給出了三個屬性的值,而這些值在之后不能被改變。
    private final String username;
    private final String password;
    private final String authority;

    //為了使例子簡單,一個用戶只有一個權(quán)限。
    public User(String username, String password, String authority) {
        this.username = username;
        this.password = password;
        this.authority = authority;
    }

    //返回一個只包含GrantedAuthority對象的列表,該對象的名稱是在你建立實例時提供的。
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(() -> authority);
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    //該賬戶不會過期或被鎖定。
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

在名為services的包中,我們創(chuàng)建了一個名為InMemoryUserDetailsService的類。下面的列表顯示了我們?nèi)绾螌崿F(xiàn)這個類。

清單3.13 UserDetailsService接口的實現(xiàn)

package com.laurentiuspilca.ssia.services;

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.List;

public class InMemoryUserDetailsService implements UserDetailsService {

    //UserDetailsService在內(nèi)存中管理用戶的列表。
    private final List<UserDetails> users;

    public InMemoryUserDetailsService(List<UserDetails> users) {
        this.users = users;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return users.stream()
            //從用戶列表中,篩選出具有所要求的用戶名的用戶
                .filter(u -> u.getUsername().equals(username))
            //如果有這樣一個用戶,則將其返回
                .findFirst()
            //如果一個具有此用戶名的用戶不存在,則拋出一個異常。
                .orElseThrow(() -> new UsernameNotFoundException("User not found"));
    }
}

loadUserByUsername(String username)方法在用戶列表中搜索給定的用戶名并返回想要的UserDetails實例。如果沒有該用戶名的實例,它會拋出一個UsernameNotFoundException。我們現(xiàn)在可以使用這個實現(xiàn)作為我們的UserDetailsService。下一個列表顯示了我們?nèi)绾卧谂渲妙愔邪阉鳛橐粋€Bean添加,并在其中注冊一個用戶。

清單3.14 UserDetailsService在配置類中被注冊為一個Bean。

package com.laurentiuspilca.ssia.config;

import com.laurentiuspilca.ssia.model.User;
import com.laurentiuspilca.ssia.services.InMemoryUserDetailsService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.List;

@Configuration
public class ProjectConfig {

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails u = new User("john", "12345", "read");
        List<UserDetails> users = List.of(u);
        return new InMemoryUserDetailsService(users);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

最后,我們創(chuàng)建一個簡單的端點并測試其實現(xiàn)。下面的列表定義了這個端點。

清單3.15 用于測試實現(xiàn)的端點的定義

package com.laurentiuspilca.ssia.controllers;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello";
    }
}

當使用cURL調(diào)用端點時,我們觀察到,對于密碼為12345的用戶John,我們得到的是HTTP 200 OK。如果我們使用其他東西,應(yīng)用程序會返回401未授權(quán)。

curl -u john:12345 http://localhost:8080/hello

3.3.3 實現(xiàn)UserDetailsManager合同

在這一節(jié)中,我們討論使用和實現(xiàn)UserDetailsManager接口。這個接口擴展了UserDetailsService合約,并為其增加了更多方法。Spring Security需要UserDetailsService合約來進行授權(quán)。但一般來說,在應(yīng)用程序中,也需要對用戶進行管理。大多數(shù)時候,一個應(yīng)用程序應(yīng)該能夠添加新的用戶或刪除現(xiàn)有的用戶。在這種情況下,我們實現(xiàn)了一個由Spring Security定義的更特殊的接口,即UserDetailsManager。它擴展了UserDetailsService,增加了更多我們需要實現(xiàn)的操作。

public interface UserDetailsManager extends UserDetailsService {
    void createUser(UserDetails user);
    void updateUser(UserDetails user);
    void deleteUser(String username);
    void changePassword(String oldPassword, String newPassword);
    boolean userExists(String username);
}

我們在第二章中使用的InMemoryUserDetailsManager對象實際上是一個UserDetailsManager。當時,我們只考慮到它的UserDetailsService特性,但現(xiàn)在你更明白為什么我們能夠在實例上調(diào)用createUser()方法。

3.3.4 使用jdbcuserdetailsmanager進行用戶管理

在InMemoryUserDetailsManager之外,我們經(jīng)常使用另一個UserDetailManager,即JdbcUserDetailsManager。JdbcUserDetailsManager在SQL數(shù)據(jù)庫中管理用戶。它通過JDBC直接連接到數(shù)據(jù)庫。這樣,JdbcUserDetailsManager獨立于任何其他與數(shù)據(jù)庫連接有關(guān)的框架或規(guī)范。

為了理解JdbcUserDetailsManager是如何工作的,最好是通過一個例子將其付諸實施。在下面的例子中,你將實現(xiàn)一個應(yīng)用程序,使用JdbcUserDetailsManager來管理MySQL數(shù)據(jù)庫中的用戶。圖3.4概述了JdbcUserDetailsManager的實現(xiàn)在認證流程中的位置。

你將通過創(chuàng)建一個數(shù)據(jù)庫和兩個表來開始我們關(guān)于如何使用JdbcUserDetailsManager的演示應(yīng)用程序。在我們的例子中,我們將數(shù)據(jù)庫命名為spring,并將其中一個表命名為users,另一個命名為authorities。這些名字是JdbcUserDetailsManager所知道的默認表名。正如你將在本節(jié)末尾學(xué)到的,JdbcUserDetailsManager的實現(xiàn)很靈活,如果你想的話,可以覆蓋這些默認的名字。users表的目的是為了保存用戶記錄。JdbcUserDetails Manager的實現(xiàn)希望在用戶表中有三列:一個用戶名、一個密碼和啟用,你可以用它來停用用戶。

Spring Security in Action 第三章 SpringSecurity管理用戶,# SpringSecurity,spring,區(qū)塊鏈,java

圖3.4 Spring Security的認證流程。這里我們使用一個JDBCUserDetailsManager作為我們的UserDetailsService組件。JdbcUserDetailsManager使用一個數(shù)據(jù)庫來管理用戶。

你可以選擇使用你的數(shù)據(jù)庫管理系統(tǒng)(DBMS)的命令行工具或客戶端應(yīng)用程序來自己創(chuàng)建數(shù)據(jù)庫及其結(jié)構(gòu)。例如,對于MySQL,你可以選擇使用MySQL Workbench來做這件事。但最簡單的是讓Spring Boot自己為你運行腳本。要做到這一點,只需在項目的資源文件夾中再添加兩個文件:schema.sql 和 data.sql。在schema.sql文件中,你可以添加與數(shù)據(jù)庫結(jié)構(gòu)有關(guān)的查詢,如創(chuàng)建、更改或刪除表。在data.sql文件中,你添加與表內(nèi)數(shù)據(jù)有關(guān)的查詢,如INSERT、UPDATE或DELETE。當你啟動應(yīng)用程序時,Spring Boot會自動為你運行這些文件。對于構(gòu)建需要數(shù)據(jù)庫的例子,一個更簡單的解決方案是使用H2內(nèi)存數(shù)據(jù)庫。這樣,你就不需要安裝一個單獨的DBMS解決方案。

注意 如果你愿意,在開發(fā)本書所介紹的應(yīng)用程序時,你也可以用H2。我選擇用一個外部DBMS來實現(xiàn)這些例子,以明確它是系統(tǒng)的一個外部組件,這樣可以避免混淆。

你使用下一個列表中的代碼,用MySQL服務(wù)器創(chuàng)建用戶表。你可以把這個腳本添加到Spring Boot項目的schema.sql文件中。

清單3.16 創(chuàng)建用戶表的SQL查詢

CREATE TABLE IF NOT EXISTS `spring`.`users` (
`id` INT NOT NULL AUTO_INCREMENT,
`username` VARCHAR(45) NOT NULL,
`password` VARCHAR(45) NOT NULL,
`enabled` INT NOT NULL,
PRIMARY KEY (`id`));

權(quán)限表存儲每個用戶的權(quán)限。每條記錄存儲一個用戶名和為該用戶名的用戶授予的權(quán)限。

清單3.17 創(chuàng)建權(quán)限表的SQL查詢

CREATE TABLE IF NOT EXISTS `spring`.`authorities` (
`id` INT NOT NULL AUTO_INCREMENT,
`username` VARCHAR(45) NOT NULL,
`authority` VARCHAR(45) NOT NULL,
PRIMARY KEY (`id`));

注意 為了簡單起見,在本書提供的例子中,我跳過了對索引或外鍵的定義。

為了確保你有一個用于測試的用戶,在每個表中插入一條記錄。你可以在Spring Boot項目的資源文件夾中的data.sql文件中添加這些查詢:

INSERT IGNORE INTO `spring`.`authorities` VALUES (NULL, 'john', 'write'); 
INSERT IGNORE INTO `spring`.`users` VALUES (NULL, 'john', '12345', '1');

對于你的項目,你需要至少添加以下列表中的依賴項。檢查你的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-security</artifactId>
        </dependency>

	<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>


        

注意 在你的例子中,你可以使用任何SQL數(shù)據(jù)庫技術(shù),只要你把正確的JDBC驅(qū)動添加到依賴關(guān)系中。

你可以在項目的application.properties文件中配置數(shù)據(jù)源,或者作為一個單獨的Bean。如果你選擇使用application.properties文件,你需要在該文件中添加以下幾行。

spring.datasource.url=jdbc:mysql://localhost/spring
spring.datasource.username=<your user>
spring.datasource.password=<your password>

在項目的配置類中,你定義了UserDetailsService和 PasswordEncoder。JdbcUserDetailsManager需要DataSource來連接數(shù)據(jù)庫。連接到數(shù)據(jù)庫。數(shù)據(jù)源可以通過方法的一個參數(shù)自動連接。方法的一個參數(shù)(如下面的列表所示)或通過類的一個屬性來自動連接數(shù)據(jù)源。

清單3.19 在配置類中注冊JdbcUserDetailsManager

package com.laurentiuspilca.ssia.config;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.JdbcUserDetailsManager;

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {

  @Bean
  public UserDetailsService userDetailsService(DataSource dataSource) {
   	return new JdbcUserDetailsManager(dataSource);

  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
  }

}

要訪問應(yīng)用程序的任何端點,你現(xiàn)在需要使用HTTP Basic認證,并使用存儲在數(shù)據(jù)庫中的一個用戶。為了證明這一點,我們創(chuàng)建一個新的端點,如下表所示,然后用cURL調(diào)用它。

package com.laurentiuspilca.ssia.controllers;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello!";
    }
}

在下一個代碼片斷中,你會發(fā)現(xiàn)用正確的用戶名和密碼調(diào)用端點時的結(jié)果。

curl -u john:12345 http://localhost:8080/hello
Hello!

JdbcUserDetailsManager還允許你配置使用的查詢。在前面的例子中,我們確保為表和列使用了準確的名稱,因為JdbcUserDetailsManager的實現(xiàn)期待這些名稱。但是對于你的應(yīng)用程序來說,這些名字可能不是最好的選擇。接下來的列表顯示了如何覆蓋JdbcUserDetailsManager的查詢。

清單3.21 改變JdbcUserDetailsManager的查詢以找到用戶

package com.laurentiuspilca.ssia.config;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.JdbcUserDetailsManager;

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {

  @Bean
  public UserDetailsService userDetailsService(DataSource dataSource) {
    String usersByUsernameQuery = "select username, password, enabled from spring.users where username = ?";
    String authsByUserQuery = "select username, authority from spring.authorities where username = ?";
    var userDetailsManager = new JdbcUserDetailsManager(dataSource);
    userDetailsManager.setUsersByUsernameQuery(usersByUsernameQuery);
    userDetailsManager.setAuthoritiesByUsernameQuery(authsByUserQuery);
    return userDetailsManager;

  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
  }

}

以同樣的方式,我們可以改變JdbcUserDetailsManager實現(xiàn)所使用的所有查詢。

練習(xí)。編寫一個類似的應(yīng)用程序,在數(shù)據(jù)庫中以不同的方式命名表和列。覆蓋JdbcUserDetailsManager實現(xiàn)的查詢

使用ldapuserdetailsmanager進行用戶管理
Spring Security還為LDAP提供了一個UserDetailsManager的實現(xiàn)。盡管它沒有JdbcUserDetailsManager那么流行,但如果你需要與LDAP系統(tǒng)集成進行用戶管理,你可以信賴它。在項目sia- ch3-ex3中,你可以找到一個使用LdapUserDetailsManager的簡單演示。因為我不能在這個演示中使用真正的LDAP服務(wù)器,我在我的Spring Boot應(yīng)用程序中設(shè)置了一個嵌入式的LDAP服務(wù)器。為了設(shè)置嵌入式LDAP服務(wù)器,我定義了一個簡單的LDAP數(shù)據(jù)交換格式(LDIF)文件。下面的列表顯示了我的LDIF文件的內(nèi)容。

#定義了基礎(chǔ)實體
dn: dc=springframework,dc=org
objectclass: top
objectclass: domain
objectclass: extensibleObject
dc: springframework

#定義了一個group實體
dn: ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: groups

#定義一個用戶
dn: uid=john,ou=groups,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: John
sn: John
uid: john
userPassword: 12345

在LDIF文件中,我只添加了一個用戶,在這個例子的最后,我們需要測試應(yīng)用程序的行為。我們可以直接將LDIF文件添加到資源文件夾中。這樣,它就自動在classpath中,所以我們以后可以很容易地引用它。我把這個LDIF文件命名為server.ldif。為了與LDAP一起工作,并允許Spring Boot啟動嵌入式LDAP服務(wù)器,你需要將pom.xml添加到依賴項中,如下面的代碼片段。

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-ldap</artifactId>
</dependency>

<dependency>
    <groupId>com.unboundid</groupId>
    <artifactId>unboundid-ldapsdk</artifactId>
</dependency>

在application.properties文件中,你還需要添加嵌入式LDAP服務(wù)器的配置,如下圖代碼片段所示。應(yīng)用程序需要啟動嵌入式LDAP服務(wù)器的值包括LDIF文件的位置、LDAP服務(wù)器的端口和基礎(chǔ)域組件(DN)標簽值。

spring.ldap.embedded.ldif=classpath:server.ldif
spring.ldap.embedded.base-dn=dc=springframework,dc=org
spring.ldap.embedded.port=33389

一旦你有一個用于認證的LDAP服務(wù)器,你就可以配置你的應(yīng)用程序來使用它。下一個列表顯示了如何配置LdapUserDetailsManager,使你的應(yīng)用程序能夠通過LDAP服務(wù)器認證用戶。

清單3.23 配置文件中LdapUserDetailsManager的定義

package com.laurentiuspilca.ssia.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.ldap.DefaultLdapUsernameToDnMapper;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.userdetails.LdapUserDetailsManager;

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {

    //在Spring上下文中添加一個UserDetailsService實現(xiàn)。
    @Override
    @Bean
    public UserDetailsService userDetailsService() {
        //創(chuàng)建一個上下文源,指定LDAP服務(wù)器的地址。
        var cs = new DefaultSpringSecurityContextSource("ldap://127.0.0.1:33389/dc=springframework,dc=org");
        cs.afterPropertiesSet();

        //創(chuàng)建LdapUserDetailsManager實例。
        LdapUserDetailsManager manager = new LdapUserDetailsManager(cs);
        
        //設(shè)置一個用戶名映射器,指示LdapUserDetailsManager如何搜索用戶。
        manager.setUsernameMapper(
                new DefaultLdapUsernameToDnMapper("ou=groups", "uid"));
        manager.setGroupSearchBase("ou=groups");
        return manager;
    }

    //設(shè)置應(yīng)用程序需要搜索用戶的群體搜索基礎(chǔ)
    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

}

讓我們也創(chuàng)建一個簡單的端點來測試安全配置。我添加了一個controller類,如下面的代碼片斷中所示。

package com.laurentiuspilca.ssia.controllers;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello!";
    }
}

現(xiàn)在啟動應(yīng)用程序并調(diào)用/hello端點。如果你想讓應(yīng)用程序允許你調(diào)用端點,你需要用用戶John進行認證。接下來的代碼片段向你展示了用cURL調(diào)用端點的結(jié)果。

curl -u john:12345 http://localhost:8080/hello
Hello!

總結(jié)文章來源地址http://www.zghlxwxcb.cn/news/detail-731548.html

  • UserDetails接口是你用來描述Spring Security中用戶的契約。
  • UserDetailsService接口是Spring Security期望你在認證架構(gòu)中實現(xiàn)的契約,以描述應(yīng)用程序獲取用戶詳細信息的方式。
  • UserDetailsManager接口擴展了UserDetailsService,并增加了與創(chuàng)建、改變或刪除用戶有關(guān)的行為。
  • Spring Security提供了UserDetailsManager合約的一些實現(xiàn)。其中包括InMemoryUserDetailsManager、JdbcUserDetailsManager和LdapUserDetailsManager。
  • JdbcUserDetailsManager的優(yōu)點是直接使用JDBC,不會將應(yīng)用程序鎖定在其他框架中。

到了這里,關(guān)于Spring Security in Action 第三章 SpringSecurity管理用戶的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • 第三章nginx詳解

    第三章nginx詳解

    特點: 1,穩(wěn)定性高。(沒有apache穩(wěn)定) 2,系統(tǒng)資源消耗地較低。(處理http請求的并發(fā)能力非常高,單臺物理服務(wù)器可以處理30000-50000個并發(fā)請求) 穩(wěn)定:一般在企業(yè)中,為了保持服務(wù)器的穩(wěn)定,并發(fā)量的設(shè)置在20000個左右。占用內(nèi)存2M左右。 nginx主要功能: 1,靜態(tài)文件服

    2024年02月12日
    瀏覽(23)
  • 第三章-運輸層

    第三章-運輸層

    運輸層協(xié)議為運行在不同主機上的進程之間提供邏輯通信,即從應(yīng)用程序角度看兩個主機好像直連一樣,實際可能相隔萬里 運輸層協(xié)議是在端系統(tǒng)上實現(xiàn)的,而不是路由器,為什么這么強調(diào),因為運輸層會將應(yīng)用報文劃分為較小的塊然后加上一個運輸層首部來生成運輸層報文

    2024年02月14日
    瀏覽(23)
  • 第三章:函數(shù)

    1.定義 設(shè) x , y 是兩個變量,x的變化范圍是實數(shù)集D,如果對于任何的x∈D,按照一定的法則都有唯一確定的y值與之對應(yīng)。則稱變量y是變量x的函數(shù)。記為 y = f(x) 。稱D為函數(shù)的 定義域 ,x為自變量,y為因變量。全體函數(shù)值的集合稱為函數(shù)y的 值域 。 2.函數(shù)的表示方法 1. 公式

    2024年02月01日
    瀏覽(27)
  • 第三章 Elasticsearch簡介

    第三章 Elasticsearch簡介

    Elasticsearch (后稱為 ES )是一個天生支持分布式的搜索、聚合分析和存儲引擎。 搜索引擎 全文檢索引擎 分布式文檔系統(tǒng) 分布式數(shù)據(jù)庫 OLAP系統(tǒng) 分布式搜索中間件 不要去死背概念,概念應(yīng)該作為一種輔助的手段幫助我們?nèi)ダ斫庖豁椉夹g(shù)或知識,總之,等你真正會用了,你就

    2024年02月06日
    瀏覽(26)
  • 第三章 decimal模塊

    第三章 decimal模塊

    decimal 模塊是 Python 提供的用于進行十進制定點和浮點運算的內(nèi)置模塊。使用它可以快速正確地進行十進制定點和浮點數(shù)的舍入運算,并且可以控制有效數(shù)字的個數(shù)。 使用 decimal 模塊主要是因為它與 Python 自帶的浮點數(shù)相比,有以下優(yōu)點 : 基于浮點模型,提供與數(shù)字計算相同

    2024年02月09日
    瀏覽(15)
  • 第三章 選擇與循環(huán)

    第三章 選擇與循環(huán)

    程序員必備技能(思想):增量編寫法。每寫一部分代碼要及時運行看結(jié)果是否正確,對于復(fù)雜程序很重要。 常用的運算符優(yōu)先級: 邏輯非 ! 算術(shù)運算符 關(guān)系運算符 || 賦值運算符 單目運算符 邏輯非 ! 算術(shù)運算符 +、-、×、/、% 關(guān)系運算符 、、=、=、==、!= 邏輯運算符 、|

    2024年02月09日
    瀏覽(32)
  • 第三章-上網(wǎng)行為安全

    第三章-上網(wǎng)行為安全

    1)寬帶濫用 2)上網(wǎng)難監(jiān)管 3)信息泄露 4)網(wǎng)絡(luò)違法 5)安全威脅 1)上網(wǎng)行為三要素:用戶、流量、行為 2)功能需求 (AC的功能)-- 重點 用戶認證 應(yīng)用控制 網(wǎng)頁過濾 行為審計 流量管理 應(yīng)用選路 互聯(lián)網(wǎng)上網(wǎng)行為管控 一體化網(wǎng)關(guān) 無線Wi-Fi管控營銷 無線防共享上網(wǎng) 全網(wǎng)上

    2024年01月23日
    瀏覽(32)
  • Linux第三章

    Linux第三章

    無論是Windows、MacOS、Linux均采用多用戶的管理模式進行權(quán)限管理。在Linux系統(tǒng)中,擁有最大權(quán)限的賬戶名為:root(超級管理員) root用戶擁有最大的系統(tǒng)操作權(quán)限,而普通用戶在許多地方的權(quán)限是受限的(普通用戶的權(quán)限,一般在其HOME目錄內(nèi)是不受限的,一旦出了HOME目錄,大

    2023年04月26日
    瀏覽(32)
  • 第三章 處理機調(diào)度

    第三章 處理機調(diào)度

    目錄 一、調(diào)度的概念、層次 2.1 調(diào)度的基本概念 2.2?調(diào)度的三個層次 2.2.1 高級調(diào)度 2.2.2 低級調(diào)度 2.2.3 中級調(diào)度 2.2.3.1 進程的掛起態(tài) 2.2.4 三層調(diào)度的聯(lián)系、對比 二、進程調(diào)度的時機、切換與過程、方式 2.1 進程調(diào)度的時機 2.2 進程調(diào)度的方式 2.2.1 非搶占方式 2.2.2 搶占方式

    2024年02月10日
    瀏覽(24)
  • 第三章 shell條件測試

    第三章 shell條件測試

    目錄 1.1. 用途 1.2. 基本語法 1.2.1. 格式: 1.2.2. 示例 1.3. 文件測試 1.3.1. 參數(shù): 1.3.2. 示例 1.4. 整數(shù)測試 1.4.1. 作用 1.4.2. 操作符 1.4.3. 示例 1.5. 邏輯操作符 1.5.1. 符號 1.5.2. 例: 1.6. 命令分隔符 1.7. 案例分析 為了能夠正確處理 Shell 程序運行過程中遇到的各種情況, Linux Shell 提供

    2024年02月21日
    瀏覽(18)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包