欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring Security UserDetails實(shí)現(xiàn)原理詳解

 更新時(shí)間:2020年09月07日 09:41:15   作者:碼農(nóng)小胖哥  
這篇文章主要介紹了Spring Security UserDetails實(shí)現(xiàn)原理詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下

1. 前言

今天開(kāi)始我們來(lái)一步步窺探它是如何工作的。我們又該如何駕馭它。本篇將通過(guò) Spring Boot 2.x 來(lái)講解 Spring Security 中的用戶主體UserDetails。以及從中找點(diǎn)樂(lè)子。

2. Spring Boot 集成 Spring Security

這個(gè)簡(jiǎn)直老生常談了。不過(guò)為了照顧大多數(shù)還是說(shuō)一下。集成 Spring Security 只需要引入其對(duì)應(yīng)的 Starter 組件。Spring Security 不僅僅能保護(hù)Servlet Web 應(yīng)用,也可以保護(hù)Reactive Web應(yīng)用,本文我們講前者。我們只需要在 Spring Security 項(xiàng)目引入以下依賴即可:

  <dependencies>
    <!-- actuator 指標(biāo)監(jiān)控 非必須 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!-- spring security starter 必須 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!-- spring mvc servlet web 必須 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--  lombok 插件 非必須    -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <!-- 測(cè)試  -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

3. UserDetailsServiceAutoConfiguration

啟動(dòng)項(xiàng)目,訪問(wèn)Actuator端點(diǎn)http://localhost:8080/actuator會(huì)跳轉(zhuǎn)到一個(gè)登錄頁(yè)面http://localhost:8080/login如下:

要求你輸入用戶名 Username (默認(rèn)值為user)和密碼 Password 。密碼在springboot控制臺(tái)會(huì)打印出類似 Using generated security password: e1f163be-ad18-4be1-977c-88a6bcee0d37 的字樣,后面的長(zhǎng)串就是密碼,當(dāng)然這不是生產(chǎn)可用的。如果你足夠細(xì)心會(huì)從控制臺(tái)打印日志發(fā)現(xiàn)該隨機(jī)密碼是由UserDetailsServiceAutoConfiguration 配置類生成的,我們就從它開(kāi)始順藤摸瓜來(lái)一探究竟。

3.1 UserDetailsService

UserDetailsService接口。該接口只提供了一個(gè)方法:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

該方法很容易理解:通過(guò)用戶名來(lái)加載用戶 。這個(gè)方法主要用于從系統(tǒng)數(shù)據(jù)中查詢并加載具體的用戶到Spring Security中。

3.2 UserDetails

從上面UserDetailsService 可以知道最終交給Spring Security的是UserDetails 。該接口是提供用戶信息的核心接口。該接口實(shí)現(xiàn)僅僅存儲(chǔ)用戶的信息。后續(xù)會(huì)將該接口提供的用戶信息封裝到認(rèn)證對(duì)象Authentication中去。UserDetails 默認(rèn)提供了:

  • 用戶的權(quán)限集, 默認(rèn)需要添加ROLE_ 前綴
  • 用戶的加密后的密碼, 不加密會(huì)使用{noop}前綴
  • 應(yīng)用內(nèi)唯一的用戶名
  • 賬戶是否過(guò)期
  • 賬戶是否鎖定
  • 憑證是否過(guò)期
  • 用戶是否可用

如果以上的信息滿足不了你使用,你可以自行實(shí)現(xiàn)擴(kuò)展以存儲(chǔ)更多的用戶信息。比如用戶的郵箱、手機(jī)號(hào)等等。通常我們使用其實(shí)現(xiàn)類:

org.springframework.security.core.userdetails.User

該類內(nèi)置一個(gè)建造器UserBuilder 會(huì)很方便地幫助我們構(gòu)建UserDetails 對(duì)象,后面我們會(huì)用到它。

3.3 UserDetailsServiceAutoConfiguration

UserDetailsServiceAutoConfiguration 全限定名為:

org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration

源碼如下:

@Configuration
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean({ AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class })
public class UserDetailsServiceAutoConfiguration {

  private static final String NOOP_PASSWORD_PREFIX = "{noop}";

  private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern.compile("^\\{.+}.*$");

  private static final Log logger = LogFactory.getLog(UserDetailsServiceAutoConfiguration.class);

  @Bean
  @ConditionalOnMissingBean(
      type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository")
  @Lazy
  public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
      ObjectProvider<PasswordEncoder> passwordEncoder){
    SecurityProperties.User user = properties.getUser();
    List<String> roles = user.getRoles();
    return new InMemoryUserDetailsManager(
        User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
            .roles(StringUtils.toStringArray(roles)).build());
  }

  private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) {
    String password = user.getPassword();
    if (user.isPasswordGenerated()) {
      logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
    }
    if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
      return password;
    }
    return NOOP_PASSWORD_PREFIX + password;
  }

}

我們來(lái)簡(jiǎn)單解讀一下該類,從@Conditional系列注解我們知道該類在類路徑下存在AuthenticationManager、在Spring 容器中存在Bean ObjectPostProcessor并且不存在Bean AuthenticationManager, AuthenticationProvider, UserDetailsService的情況下生效。千萬(wàn)不要糾結(jié)這些類干嘛用的! 該類只初始化了一個(gè)UserDetailsManager 類型的Bean。UserDetailsManager 類型負(fù)責(zé)對(duì)安全用戶實(shí)體抽象UserDetails的增刪查改操作。同時(shí)還繼承了UserDetailsService接口。

明白了上面這些讓我們把目光再回到UserDetailsServiceAutoConfiguration 上來(lái)。該類初始化了一個(gè)名為InMemoryUserDetailsManager 的內(nèi)存用戶管理器。該管理器通過(guò)配置注入了一個(gè)默認(rèn)的UserDetails存在內(nèi)存中,就是我們上面用的那個(gè)user ,每次啟動(dòng)user都是動(dòng)態(tài)生成的。那么問(wèn)題來(lái)了如果我們定義自己的UserDetailsManager Bean是不是就可以實(shí)現(xiàn)我們需要的用戶管理邏輯呢?

3.4 自定義UserDetailsManager

我們來(lái)自定義一個(gè)UserDetailsManager 來(lái)看看能不能達(dá)到自定義用戶管理的效果。首先我們針對(duì)UserDetailsManager 的所有方法進(jìn)行一個(gè)代理的實(shí)現(xiàn),我們依然將用戶存在內(nèi)存中,區(qū)別就是這是我們自定義的:

package cn.felord.spring.security;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.HashMap;
import java.util.Map;

/**
 * 代理 {@link org.springframework.security.provisioning.UserDetailsManager} 所有功能
 *
 * @author Felordcn
 */
public class UserDetailsRepository {

  private Map<String, UserDetails> users = new HashMap<>();

  public void createUser(UserDetails user) {
    users.putIfAbsent(user.getUsername(), user);
  }

  public void updateUser(UserDetails user) {
    users.put(user.getUsername(), user);
  }

  public void deleteUser(String username) {
    users.remove(username);
  }

  public void changePassword(String oldPassword, String newPassword) {
    Authentication currentUser = SecurityContextHolder.getContext()
        .getAuthentication();

    if (currentUser == null) {
      // This would indicate bad coding somewhere
      throw new AccessDeniedException(
          "Can't change password as no Authentication object found in context "
              + "for current user.");
    }

    String username = currentUser.getName();

    UserDetails user = users.get(username);

    if (user == null) {
      throw new IllegalStateException("Current user doesn't exist in database.");
    }

    // todo copy InMemoryUserDetailsManager 自行實(shí)現(xiàn)具體的更新密碼邏輯
  }

  public boolean userExists(String username) {

    return users.containsKey(username);
  }

  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    return users.get(username);
  }
}

該類負(fù)責(zé)具體對(duì)UserDetails 的增刪改查操作。我們將其注入Spring 容器:

  @Bean
  public UserDetailsRepository userDetailsRepository() {
    UserDetailsRepository userDetailsRepository = new UserDetailsRepository();

    // 為了讓我們的登錄能夠運(yùn)行 這里我們初始化一個(gè)用戶Felordcn 密碼采用明文 當(dāng)你在密碼12345上使用了前綴{noop} 意味著你的密碼不使用加密,authorities 一定不能為空 這代表用戶的角色權(quán)限集合
    UserDetails felordcn = User.withUsername("Felordcn").password("{noop}12345").authorities(AuthorityUtils.NO_AUTHORITIES).build();
    userDetailsRepository.createUser(felordcn);
    return userDetailsRepository;
  }

為了方便測(cè)試 我們也內(nèi)置一個(gè)名稱為Felordcn 密碼為12345的UserDetails用戶,密碼采用明文 當(dāng)你在密碼12345上使用了前綴{noop} 意味著你的密碼不使用加密,這里我們并沒(méi)有指定密碼加密方式你可以使用PasswordEncoder 來(lái)指定一種加密方式。通常推薦使用Bcrypt作為加密方式。默認(rèn)Spring Security使用的也是此方式。authorities 一定不能為null 這代表用戶的角色權(quán)限集合。接下來(lái)我們實(shí)現(xiàn)一個(gè)UserDetailsManager 并注入Spring 容器:

  @Bean
  public UserDetailsManager userDetailsManager(UserDetailsRepository userDetailsRepository) {
    return new UserDetailsManager() {
      @Override
      public void createUser(UserDetails user) {
        userDetailsRepository.createUser(user);
      }

      @Override
      public void updateUser(UserDetails user) {
        userDetailsRepository.updateUser(user);
      }

      @Override
      public void deleteUser(String username) {
        userDetailsRepository.deleteUser(username);
      }

      @Override
      public void changePassword(String oldPassword, String newPassword) {
        userDetailsRepository.changePassword(oldPassword, newPassword);
      }

      @Override
      public boolean userExists(String username) {
        return userDetailsRepository.userExists(username);
      }

      @Override
      public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userDetailsRepository.loadUserByUsername(username);
      }
    };
  }

這樣實(shí)際執(zhí)行委托給了UserDetailsRepository 來(lái)做。我們重復(fù) 章節(jié)3. 的動(dòng)作進(jìn)入登陸頁(yè)面分別輸入Felordcn和12345 成功進(jìn)入。

3.5 數(shù)據(jù)庫(kù)管理用戶

經(jīng)過(guò)以上的配置,相信聰明的你已經(jīng)知道如何使用數(shù)據(jù)庫(kù)來(lái)管理用戶了 。只需要將 UserDetailsRepository 中的 users 屬性替代為抽象的Dao接口就行了,無(wú)論你使用Jpa還是Mybatis來(lái)實(shí)現(xiàn)。

4. 總結(jié)

今天我們對(duì)Spring Security 中的用戶信息 UserDetails 相關(guān)進(jìn)行的一些解讀。并自定義了用戶信息處理服務(wù)。相信你已經(jīng)對(duì)在Spring Security中如何加載用戶信息,如何擴(kuò)展用戶信息有所掌握了。后面我們會(huì)由淺入深慢慢解讀Spring Security。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • JavaWeb實(shí)現(xiàn)同一帳號(hào)同一時(shí)間只能一個(gè)地點(diǎn)登陸(類似QQ登錄的功能)

    JavaWeb實(shí)現(xiàn)同一帳號(hào)同一時(shí)間只能一個(gè)地點(diǎn)登陸(類似QQ登錄的功能)

    最近做了企業(yè)項(xiàng)目,其中有這樣的需求要求同一帳號(hào)同一時(shí)間只能一個(gè)地點(diǎn)登陸類似QQ登錄的功能。下面小編通過(guò)本文給大家分享實(shí)現(xiàn)思路,感興趣的朋友參考下吧
    2016-11-11
  • Java 動(dòng)態(tài)加載jar和class文件實(shí)例解析

    Java 動(dòng)態(tài)加載jar和class文件實(shí)例解析

    這篇文章主要介紹了Java 動(dòng)態(tài)加載jar和class文件實(shí)例解析,分享了相關(guān)代碼示例,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-02-02
  • Javacv使用ffmpeg實(shí)現(xiàn)音視頻同步播放

    Javacv使用ffmpeg實(shí)現(xiàn)音視頻同步播放

    這篇文章主要介紹了Javacv使用ffmpeg實(shí)現(xiàn)音視頻同步播放,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-12-12
  • Java如何設(shè)置過(guò)期時(shí)間的map的幾種方法

    Java如何設(shè)置過(guò)期時(shí)間的map的幾種方法

    本文主要介紹了Java如何設(shè)置過(guò)期時(shí)間的map的幾種方法,常見(jiàn)的解決方法有:ExpiringMap、LoadingCache及基于HashMap的封裝三種,下面就詳細(xì)的介紹一下,感興趣的可以了解下
    2022-03-03
  • 一篇文章帶你學(xué)會(huì)Spring?MVC表單標(biāo)簽

    一篇文章帶你學(xué)會(huì)Spring?MVC表單標(biāo)簽

    Spring MVC表單標(biāo)簽是網(wǎng)頁(yè)的可配置和可重復(fù)使用的構(gòu)建塊,下面這篇文章主要給大家介紹了如何通過(guò)一篇文章學(xué)會(huì)Spring?MVC表單標(biāo)簽的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-03-03
  • Java源碼解析之超級(jí)接口Map

    Java源碼解析之超級(jí)接口Map

    今天給各位小伙伴介紹一下超級(jí)接口Map,文中對(duì)接口Map講的非常詳細(xì),對(duì)正在學(xué)習(xí)java的小伙伴們有很好的幫助喲,需要的朋友可以參考下
    2021-05-05
  • SpringBoot實(shí)現(xiàn)過(guò)濾器、攔截器與切片的實(shí)現(xiàn)和區(qū)別

    SpringBoot實(shí)現(xiàn)過(guò)濾器、攔截器與切片的實(shí)現(xiàn)和區(qū)別

    本文詳細(xì)介紹了使用過(guò)濾器、攔截器與切片實(shí)現(xiàn)每個(gè)請(qǐng)求耗時(shí)的統(tǒng)計(jì),并比較三者的區(qū)別與聯(lián)系,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-02-02
  • Java入門絆腳石之Override和Overload的區(qū)別詳解

    Java入門絆腳石之Override和Overload的區(qū)別詳解

    重寫是子類對(duì)父類的允許訪問(wèn)的方法的實(shí)現(xiàn)過(guò)程進(jìn)行重新編寫, 返回值和形參都不能改變。即外殼不變,核心重寫!重寫的好處在于子類可以根據(jù)需要,定義特定于自己的行為。重載是在一個(gè)類里面,方法名字相同,而參數(shù)不同。返回類型可以相同也可以不同
    2021-10-10
  • Java設(shè)計(jì)模式之原型模式詳細(xì)解析

    Java設(shè)計(jì)模式之原型模式詳細(xì)解析

    這篇文章主要介紹了Java設(shè)計(jì)模式之原型模式詳細(xì)解析,原型模式就是用一個(gè)已經(jīng)創(chuàng)建的實(shí)例作為原型,通過(guò)復(fù)制該原型對(duì)象來(lái)創(chuàng)建一個(gè)和原型對(duì)象相同的新對(duì)象,需要的朋友可以參考下
    2023-11-11
  • MyBatis-Plus UpdateWrapper 使用常見(jiàn)陷阱和解決方案

    MyBatis-Plus UpdateWrapper 使用常見(jiàn)陷阱和解決方案

    MyBatis-Plus是Mybatis的一個(gè)增強(qiáng),簡(jiǎn)化了Mybatis的開(kāi)發(fā)過(guò)程,不僅保持了Mybatis原有的功能,而且在無(wú)代碼侵略下增加了許多的增強(qiáng)的功能,提供了豐富的CRUD操作,單表的CRUD操作無(wú)需編寫SQL語(yǔ)句,本文介紹的是UpdateWrapper的常見(jiàn)陷阱和對(duì)應(yīng)的解決方案,感興趣的朋友一起看看吧
    2024-08-08

最新評(píng)論