国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Spring Security UserDetails實現原理詳解

Spring Security UserDetails實現原理詳解

2020-09-08 00:24碼農小胖哥 Java教程

這篇文章主要介紹了Spring Security UserDetails實現原理詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下

1. 前言

今天開始我們來一步步窺探它是如何工作的。我們又該如何駕馭它。本篇將通過 Spring Boot 2.x 來講解 Spring Security 中的用戶主體UserDetails。以及從中找點樂子。

2. Spring Boot 集成 Spring Security

這個簡直老生常談了。不過為了照顧大多數還是說一下。集成 Spring Security 只需要引入其對應的 Starter 組件。Spring Security 不僅僅能保護Servlet Web 應用,也可以保護Reactive Web應用,本文我們講前者。我們只需要在 Spring Security 項目引入以下依賴即可:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<dependencies>
  <!-- actuator 指標監控 非必須 -->
  <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>
  <!-- 測試  -->
  <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

啟動項目,訪問Actuator端點http://localhost:8080/actuator會跳轉到一個登錄頁面http://localhost:8080/login如下:

Spring Security UserDetails實現原理詳解

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

3.1 UserDetailsService

UserDetailsService接口。該接口只提供了一個方法:

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

該方法很容易理解:通過用戶名來加載用戶 。這個方法主要用于從系統數據中查詢并加載具體的用戶到Spring Security中。

3.2 UserDetails

從上面UserDetailsService 可以知道最終交給Spring Security的是UserDetails 。該接口是提供用戶信息的核心接口。該接口實現僅僅存儲用戶的信息。后續會將該接口提供的用戶信息封裝到認證對象Authentication中去。UserDetails 默認提供了:

  • 用戶的權限集, 默認需要添加ROLE_ 前綴
  • 用戶的加密后的密碼, 不加密會使用{noop}前綴
  • 應用內唯一的用戶名
  • 賬戶是否過期
  • 賬戶是否鎖定
  • 憑證是否過期
  • 用戶是否可用

如果以上的信息滿足不了你使用,你可以自行實現擴展以存儲更多的用戶信息。比如用戶的郵箱、手機號等等。通常我們使用其實現類:

org.springframework.security.core.userdetails.User

該類內置一個建造器UserBuilder 會很方便地幫助我們構建UserDetails 對象,后面我們會用到它。

3.3 UserDetailsServiceAutoConfiguration

UserDetailsServiceAutoConfiguration 全限定名為:

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

源碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@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;
  }
 
}

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

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

3.4 自定義UserDetailsManager

我們來自定義一個UserDetailsManager 來看看能不能達到自定義用戶管理的效果。首先我們針對UserDetailsManager 的所有方法進行一個代理的實現,我們依然將用戶存在內存中,區別就是這是我們自定義的:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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 自行實現具體的更新密碼邏輯
  }
 
  public boolean userExists(String username) {
 
    return users.containsKey(username);
  }
 
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    return users.get(username);
  }
}

該類負責具體對UserDetails 的增刪改查操作。我們將其注入Spring 容器:

?
1
2
3
4
5
6
7
8
9
@Bean
public UserDetailsRepository userDetailsRepository() {
  UserDetailsRepository userDetailsRepository = new UserDetailsRepository();
 
  // 為了讓我們的登錄能夠運行 這里我們初始化一個用戶Felordcn 密碼采用明文 當你在密碼12345上使用了前綴{noop} 意味著你的密碼不使用加密,authorities 一定不能為空 這代表用戶的角色權限集合
  UserDetails felordcn = User.withUsername("Felordcn").password("{noop}12345").authorities(AuthorityUtils.NO_AUTHORITIES).build();
  userDetailsRepository.createUser(felordcn);
  return userDetailsRepository;
}

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@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);
    }
  };
}

這樣實際執行委托給了UserDetailsRepository 來做。我們重復 章節3. 的動作進入登陸頁面分別輸入Felordcn和12345 成功進入。

3.5 數據庫管理用戶

經過以上的配置,相信聰明的你已經知道如何使用數據庫來管理用戶了 。只需要將 UserDetailsRepository 中的 users 屬性替代為抽象的Dao接口就行了,無論你使用Jpa還是Mybatis來實現。

4. 總結

今天我們對Spring Security 中的用戶信息 UserDetails 相關進行的一些解讀。并自定義了用戶信息處理服務。相信你已經對在Spring Security中如何加載用戶信息,如何擴展用戶信息有所掌握了。后面我們會由淺入深慢慢解讀Spring Security。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

原文鏈接:https://blog.51cto.com/14901317/2529093

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 成人免费色| 国产精品视频一二三区 | 成人片网址 | 久久不射电影网 | 天天爽天天干 | 男人午夜视频在线观看 | 婷婷综合在线 | 精品一级 | 久久69精品久久久久久久电影好 | 国产午夜精品一区二区三区嫩草 | 日本视频中文字幕 | 夜夜操天天操 | 午夜视频网 | 成人精品动漫一区二区三区 | 97精品国产 | 91精品国产色综合久久 | 国产羞羞视频 | 国产精品亚洲第一区 | 国内精品一区二区三区视频 | 欧美日韩精品久久久免费观看 | 亚洲久久久久久 | 亚洲精品91 | 久久久亚洲国产天美传媒修理工 | 精品美女 | 黄在线观看 | 91av在线免费观看 | 日韩高清一区 | 日韩综合在线 | 亚洲网站视频 | 午夜精品久久久久久久久 | 91精品国产综合久久香蕉922 | 国产精品美女久久久久久免费 | 日日夜夜精品视频 | 色接久久 | 国产精品视频入口 | 久久久久久一区二区三区 | 亚洲午夜av| 夜夜操比 | 亚洲网站在线观看 | 精品国产乱码久久久久久久软件 | 91精品久久久久久 |