一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

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

服務(wù)器之家 - 編程語言 - Java教程 - Spring Security UserDetails實現(xiàn)原理詳解

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

2020-09-08 00:24碼農(nóng)小胖哥 Java教程

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

1. 前言

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

2. Spring Boot 集成 Spring Security

這個簡直老生常談了。不過為了照顧大多數(shù)還是說一下。集成 Spring Security 只需要引入其對應(yīng)的 Starter 組件。Spring Security 不僅僅能保護Servlet Web 應(yīng)用,也可以保護Reactive Web應(yīng)用,本文我們講前者。我們只需要在 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 指標監(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>
  <!-- 測試  -->
  <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會跳轉(zhuǎn)到一個登錄頁面http://localhost:8080/login如下:

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

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

3.1 UserDetailsService

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

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

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

3.2 UserDetails

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

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

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

org.springframework.security.core.userdetails.User

該類內(nèi)置一個建造器UserBuilder 會很方便地幫助我們構(gòu)建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的情況下生效。千萬不要糾結(jié)這些類干嘛用的! 該類只初始化了一個UserDetailsManager 類型的Bean。UserDetailsManager 類型負責(zé)對安全用戶實體抽象UserDetails的增刪查改操作。同時還繼承了UserDetailsService接口。

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

3.4 自定義UserDetailsManager

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

?
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 自行實現(xiàn)具體的更新密碼邏輯
  }
 
  public boolean userExists(String username) {
 
    return users.containsKey(username);
  }
 
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    return users.get(username);
  }
}

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

?
1
2
3
4
5
6
7
8
9
@Bean
public UserDetailsRepository userDetailsRepository() {
  UserDetailsRepository userDetailsRepository = new UserDetailsRepository();
 
  // 為了讓我們的登錄能夠運行 這里我們初始化一個用戶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;
}

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

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

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

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

4. 總結(jié)

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

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。

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

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 精品视频在线免费观看 | 91视频免费观看网站 | 国产午夜一区二区在线观看 | 性关系免费视频 | 175m美女被网友灌醉啪啪玩脚 | 精品性影院一区二区三区内射 | 欧美区日韩区 | 日本中文字幕在线精品 | chinese老头和老太交hd | 女人张开腿 让男人桶个爽 免费观看 | 免费在线电视 | 色小妹在线 | 精品欧美一区二区在线观看欧美熟 | 大好硬好深好爽想要视频 | 欧美成人手机 | 青青青视频蜜桃一区二区 | 日本黄色录像视频 | 国产精品永久免费10000 | 国产午夜精品一区二区三区 | 国产精品一区二区久久 | 丝袜足液精子免费视频 | 免费yjsp妖精com | 欧美日韩一二三区免费视频观看 | kisssis无减删全集在线观看 | 久久久久久久99精品免费观看 | 亚洲日本中文字幕天堂网 | 希岛爱理作品在线观看 | 高清在线一区二区 | 女人扒开下面让男人桶爽视频 | 久久免费看少妇高潮A片2012 | 成人在线播放视频 | 五月桃花网婷婷亚洲综合 | 韩国一级淫片特黄特刺激 | 国产3344视频在线观看免费 | 91精品国产综合久久消防器材 | 欧美日韩国产一区二区三区伦 | 6080欧美一区二区三区四区 | 日日日操 | 精品一区二区三区波多野结衣 | 久久强奷乱码老熟女 | 91porn最新网址|