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

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

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

服務(wù)器之家 - 編程語(yǔ)言 - Java教程 - Spring Security 如何實(shí)現(xiàn)多種加密方案共存

Spring Security 如何實(shí)現(xiàn)多種加密方案共存

2021-08-05 23:15江南一點(diǎn)雨 Java教程

這篇文章中,松哥給大家介紹了兩種密碼加密方案,但是兩種都是獨(dú)立使用的!能不能在同一個(gè)項(xiàng)目中同時(shí)存在多種密碼加密方案呢?答案是肯定的!

Spring Security 如何實(shí)現(xiàn)多種加密方案共存

這篇文章中,松哥給大家介紹了兩種密碼加密方案,但是兩種都是獨(dú)立使用的!能不能在同一個(gè)項(xiàng)目中同時(shí)存在多種密碼加密方案呢?答案是肯定的!

今天松哥就來(lái)和大家聊一聊,如何在 Spring Security 中,讓多種不同的密碼加密方案并存。

為什么要加密?常見(jiàn)的加密算法等等這些問(wèn)題我就不再贅述了,大家可以參考之前的:Spring Boot 中密碼加密的兩種姿勢(shì)!,咱們直接來(lái)看今天的正文。

1.PasswordEncoder

在 Spring Security 中,跟密碼加密/校驗(yàn)相關(guān)的事情,都是由 PasswordEncoder 來(lái)主導(dǎo)的,PasswordEncoder 擁有眾多的實(shí)現(xiàn)類:

Spring Security 如何實(shí)現(xiàn)多種加密方案共存

這些實(shí)現(xiàn)類,有的已經(jīng)過(guò)期了,有的用處不大。對(duì)于我們而言,最常用的莫過(guò)于 BCryptPasswordEncoder。

PasswordEncoder 本身是一個(gè)接口,里邊只有三個(gè)方法:

  1. public interface PasswordEncoder { 
  2.  String encode(CharSequence rawPassword); 
  3.  boolean matches(CharSequence rawPassword, String encodedPassword); 
  4.  default boolean upgradeEncoding(String encodedPassword) { 
  5.   return false
  6.  } 
  • encode 方法用來(lái)對(duì)密碼進(jìn)行加密。
  • matches 方法用來(lái)對(duì)密碼進(jìn)行比對(duì)。
  • upgradeEncoding 表示是否需要對(duì)密碼進(jìn)行再次加密以使得密碼更加安全,默認(rèn)為 false。

PasswordEncoder 的實(shí)現(xiàn)類,則具體實(shí)現(xiàn)了這些方法。

2.PasswordEncoder 在哪里起作用

對(duì)于我們開(kāi)發(fā)者而言,我們通常都是在 SecurityConfig 中配置一個(gè) PasswordEncoder 的實(shí)例,類似下面這樣:

  1. @Bean 
  2. PasswordEncoder passwordEncoder() { 
  3.     return new BCryptPasswordEncoder(); 

剩下的事情,都是由系統(tǒng)調(diào)用的。今天我們就來(lái)揭開(kāi)系統(tǒng)調(diào)用的神秘面紗!我們一起來(lái)看下系統(tǒng)到底是怎么調(diào)用的!

首先,松哥在前面的文章中和大家提到過(guò),Spring Security 中,如果使用用戶名/密碼的方式登錄,密碼是在 DaoAuthenticationProvider 中進(jìn)行校驗(yàn)的,大家可以參考:SpringSecurity 自定義認(rèn)證邏輯的兩種方式(高級(jí)玩法)。

我們來(lái)看下 DaoAuthenticationProvider 中密碼是如何校驗(yàn)的:

  1. protected void additionalAuthenticationChecks(UserDetails userDetails, 
  2.   UsernamePasswordAuthenticationToken authentication) 
  3.   throws AuthenticationException { 
  4.  if (authentication.getCredentials() == null) { 
  5.   throw new BadCredentialsException(messages.getMessage( 
  6.     "AbstractUserDetailsAuthenticationProvider.badCredentials"
  7.     "Bad credentials")); 
  8.  } 
  9.  String presentedPassword = authentication.getCredentials().toString(); 
  10.  if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { 
  11.   throw new BadCredentialsException(messages.getMessage( 
  12.     "AbstractUserDetailsAuthenticationProvider.badCredentials"
  13.     "Bad credentials")); 
  14.  } 

可以看到,密碼校驗(yàn)就是通過(guò) passwordEncoder.matches 方法來(lái)完成的。

那么 DaoAuthenticationProvider 中的 passwordEncoder 從何而來(lái)呢?是不是就是我們一開(kāi)始在 SecurityConfig 中配置的那個(gè) Bean 呢?

我們來(lái)看下 DaoAuthenticationProvider 中關(guān)于 passwordEncoder 的定義,如下:

  1. public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { 
  2.  private PasswordEncoder passwordEncoder; 
  3.  public DaoAuthenticationProvider() { 
  4.   setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()); 
  5.  } 
  6.  public void setPasswordEncoder(PasswordEncoder passwordEncoder) { 
  7.   this.passwordEncoder = passwordEncoder; 
  8.   this.userNotFoundEncodedPassword = null
  9.  } 
  10.  
  11.  protected PasswordEncoder getPasswordEncoder() { 
  12.   return passwordEncoder; 
  13.  } 

從這段代碼中可以看到,在 DaoAuthenticationProvider 創(chuàng)建之時(shí),就指定了 PasswordEncoder,似乎并沒(méi)有用到我們一開(kāi)始配置的 Bean?其實(shí)不是的!在 DaoAuthenticationProvider 創(chuàng)建之時(shí),會(huì)制定一個(gè)默認(rèn)的 PasswordEncoder,如果我們沒(méi)有配置任何 PasswordEncoder,將使用這個(gè)默認(rèn)的 PasswordEncoder,如果我們自定義了 PasswordEncoder 實(shí)例,那么會(huì)使用我們自定義的 PasswordEncoder 實(shí)例!

從何而知呢?

我們?cè)賮?lái)看看 DaoAuthenticationProvider 是怎么初始化的。

DaoAuthenticationProvider 的初始化是在 InitializeUserDetailsManagerConfigurer#configure 方法中完成的,我們一起來(lái)看下該方法的定義:

  1. public void configure(AuthenticationManagerBuilder auth) throws Exception { 
  2.  if (auth.isConfigured()) { 
  3.   return
  4.  } 
  5.  UserDetailsService userDetailsService = getBeanOrNull( 
  6.    UserDetailsService.class); 
  7.  if (userDetailsService == null) { 
  8.   return
  9.  } 
  10.  PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class); 
  11.  UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class); 
  12.  DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); 
  13.  provider.setUserDetailsService(userDetailsService); 
  14.  if (passwordEncoder != null) { 
  15.   provider.setPasswordEncoder(passwordEncoder); 
  16.  } 
  17.  if (passwordManager != null) { 
  18.   provider.setUserDetailsPasswordService(passwordManager); 
  19.  } 
  20.  provider.afterPropertiesSet(); 
  21.  auth.authenticationProvider(provider); 

從這段代碼中我們可以看到:

  1. 首先去調(diào)用 getBeanOrNull 方法獲取一個(gè) PasswordEncoder 實(shí)例,getBeanOrNull 方法實(shí)際上就是去 Spring 容器中查找對(duì)象。
  2. 接下來(lái)直接 new 一個(gè) DaoAuthenticationProvider 對(duì)象,大家知道,在 new 的過(guò)程中,DaoAuthenticationProvider 中默認(rèn)的 PasswordEncoder 已經(jīng)被創(chuàng)建出來(lái)了。
  3. 如果一開(kāi)始從 Spring 容器中獲取到了 PasswordEncoder 實(shí)例,則將之賦值給 DaoAuthenticationProvider 實(shí)例,否則就是用 DaoAuthenticationProvider 自己默認(rèn)創(chuàng)建的 PasswordEncoder。

至此,就真相大白了,我們配置的 PasswordEncoder 實(shí)例確實(shí)用上了。

3.默認(rèn)的是什么?

同時(shí)大家看到,如果我們不進(jìn)行任何配置,默認(rèn)的 PasswordEncoder 也會(huì)被提供,那么默認(rèn)的 PasswordEncoder 是什么呢?我們就從這個(gè)方法看起:

  1. public DaoAuthenticationProvider() { 
  2.  setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()); 

繼續(xù):

  1. public class PasswordEncoderFactories { 
  2.  public static PasswordEncoder createDelegatingPasswordEncoder() { 
  3.   String encodingId = "bcrypt"
  4.   Map<String, PasswordEncoder> encoders = new HashMap<>(); 
  5.   encoders.put(encodingId, new BCryptPasswordEncoder()); 
  6.   encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder()); 
  7.   encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder()); 
  8.   encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5")); 
  9.   encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance()); 
  10.   encoders.put("pbkdf2", new Pbkdf2PasswordEncoder()); 
  11.   encoders.put("scrypt", new SCryptPasswordEncoder()); 
  12.   encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1")); 
  13.   encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256")); 
  14.   encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder()); 
  15.   encoders.put("argon2", new Argon2PasswordEncoder()); 
  16.  
  17.   return new DelegatingPasswordEncoder(encodingId, encoders); 
  18.  } 
  19.  
  20.  private PasswordEncoderFactories() {} 

可以看到:

  1. 在 PasswordEncoderFactories 中,首先構(gòu)建了一個(gè) encoders,然后給所有的編碼方式都取了一個(gè)名字,再把名字做 key,編碼方式做 value,統(tǒng)統(tǒng)存入 encoders 中。
  2. 最后返回了一個(gè) DelegatingPasswordEncoder 實(shí)例,同時(shí)傳入默認(rèn)的 encodingId 就是 bcrypt,以及 encoders 實(shí)例,DelegatingPasswordEncoder 看名字應(yīng)該是一個(gè)代理對(duì)象。

我們來(lái)看下 DelegatingPasswordEncoder 的定義:

  1. public class DelegatingPasswordEncoder implements PasswordEncoder { 
  2.  private static final String PREFIX = "{"
  3.  private static final String SUFFIX = "}"
  4.  private final String idForEncode; 
  5.  private final PasswordEncoder passwordEncoderForEncode; 
  6.  private final Map<String, PasswordEncoder> idToPasswordEncoder; 
  7.  private PasswordEncoder defaultPasswordEncoderForMatches = new UnmappedIdPasswordEncoder(); 
  8.  public DelegatingPasswordEncoder(String idForEncode, 
  9.   Map<String, PasswordEncoder> idToPasswordEncoder) { 
  10.   if (idForEncode == null) { 
  11.    throw new IllegalArgumentException("idForEncode cannot be null"); 
  12.   } 
  13.   if (!idToPasswordEncoder.containsKey(idForEncode)) { 
  14.    throw new IllegalArgumentException("idForEncode " + idForEncode + "is not found in idToPasswordEncoder " + idToPasswordEncoder); 
  15.   } 
  16.   for (String id : idToPasswordEncoder.keySet()) { 
  17.    if (id == null) { 
  18.     continue
  19.    } 
  20.    if (id.contains(PREFIX)) { 
  21.     throw new IllegalArgumentException("id " + id + " cannot contain " + PREFIX); 
  22.    } 
  23.    if (id.contains(SUFFIX)) { 
  24.     throw new IllegalArgumentException("id " + id + " cannot contain " + SUFFIX); 
  25.    } 
  26.   } 
  27.   this.idForEncode = idForEncode; 
  28.   this.passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode); 
  29.   this.idToPasswordEncoder = new HashMap<>(idToPasswordEncoder); 
  30.  } 
  31.  public void setDefaultPasswordEncoderForMatches( 
  32.   PasswordEncoder defaultPasswordEncoderForMatches) { 
  33.   if (defaultPasswordEncoderForMatches == null) { 
  34.    throw new IllegalArgumentException("defaultPasswordEncoderForMatches cannot be null"); 
  35.   } 
  36.   this.defaultPasswordEncoderForMatches = defaultPasswordEncoderForMatches; 
  37.  } 
  38.  
  39.  @Override 
  40.  public String encode(CharSequence rawPassword) { 
  41.   return PREFIX + this.idForEncode + SUFFIX + this.passwordEncoderForEncode.encode(rawPassword); 
  42.  } 
  43.  
  44.  @Override 
  45.  public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) { 
  46.   if (rawPassword == null && prefixEncodedPassword == null) { 
  47.    return true
  48.   } 
  49.   String id = extractId(prefixEncodedPassword); 
  50.   PasswordEncoder delegate = this.idToPasswordEncoder.get(id); 
  51.   if (delegate == null) { 
  52.    return this.defaultPasswordEncoderForMatches 
  53.     .matches(rawPassword, prefixEncodedPassword); 
  54.   } 
  55.   String encodedPassword = extractEncodedPassword(prefixEncodedPassword); 
  56.   return delegate.matches(rawPassword, encodedPassword); 
  57.  } 
  58.  
  59.  private String extractId(String prefixEncodedPassword) { 
  60.   if (prefixEncodedPassword == null) { 
  61.    return null
  62.   } 
  63.   int start = prefixEncodedPassword.indexOf(PREFIX); 
  64.   if (start != 0) { 
  65.    return null
  66.   } 
  67.   int end = prefixEncodedPassword.indexOf(SUFFIX, start); 
  68.   if (end < 0) { 
  69.    return null
  70.   } 
  71.   return prefixEncodedPassword.substring(start + 1, end); 
  72.  } 
  73.  
  74.  @Override 
  75.  public boolean upgradeEncoding(String prefixEncodedPassword) { 
  76.   String id = extractId(prefixEncodedPassword); 
  77.   if (!this.idForEncode.equalsIgnoreCase(id)) { 
  78.    return true
  79.   } 
  80.   else { 
  81.    String encodedPassword = extractEncodedPassword(prefixEncodedPassword); 
  82.    return this.idToPasswordEncoder.get(id).upgradeEncoding(encodedPassword); 
  83.   } 
  84.  } 
  85.  
  86.  private String extractEncodedPassword(String prefixEncodedPassword) { 
  87.   int start = prefixEncodedPassword.indexOf(SUFFIX); 
  88.   return prefixEncodedPassword.substring(start + 1); 
  89.  } 
  90.  private class UnmappedIdPasswordEncoder implements PasswordEncoder { 
  91.  
  92.   @Override 
  93.   public String encode(CharSequence rawPassword) { 
  94.    throw new UnsupportedOperationException("encode is not supported"); 
  95.   } 
  96.  
  97.   @Override 
  98.   public boolean matches(CharSequence rawPassword, 
  99.    String prefixEncodedPassword) { 
  100.    String id = extractId(prefixEncodedPassword); 
  101.    throw new IllegalArgumentException("There is no PasswordEncoder mapped for the id \"" + id + "\""); 
  102.   } 
  103.  } 

這段代碼比較長(zhǎng),我來(lái)和大家挨個(gè)解釋下:

  1. DelegatingPasswordEncoder 也是實(shí)現(xiàn)了 PasswordEncoder 接口,所以它里邊的核心方法也是兩個(gè):encode 方法用來(lái)對(duì)密碼進(jìn)行編碼,matches 方法用來(lái)校驗(yàn)密碼。
  2. 在 DelegatingPasswordEncoder 的構(gòu)造方法中,通過(guò) 通過(guò)傳入的兩個(gè)參數(shù) encodingId 和 encoders ,獲取到默認(rèn)的編碼器賦值給 passwordEncoderForEncode,默認(rèn)的編碼器實(shí)際上就是 BCryptPasswordEncoder。
  3. 在 encode 方法中對(duì)密碼進(jìn)行編碼,但是編碼的方式加了前綴,前綴是 {編碼器名稱} ,例如如果你使用 BCryptPasswordEncoder 進(jìn)行編碼,那么生成的密碼就類似 {bcrypt}$2a$10$oE39aG10kB/rFu2vQeCJTu/V/v4n6DRR0f8WyXRiAYvBpmadoOBE.。這樣有什么用呢?每種密碼加密之后,都會(huì)加上一個(gè)前綴,這樣看到前綴,就知道該密文是使用哪個(gè)編碼器生成的了。
  4. 最后 matches 方法的邏輯就很清晰了,先從密文中提取出來(lái)前綴,再根據(jù)前綴找到對(duì)應(yīng)的 PasswordEncoder,然后再調(diào)用 PasswordEncoder 的 matches 方法進(jìn)行密碼比對(duì)。
  5. 如果根據(jù)提取出來(lái)的前綴,找不到對(duì)應(yīng)的 PasswordEncoder,那么就會(huì)調(diào)用 UnmappedIdPasswordEncoder#matches 方法,進(jìn)行密碼比對(duì),該方法實(shí)際上并不會(huì)進(jìn)行密碼比對(duì),而是直接拋出異常。

OK,至此,相信大家都明白了 DelegatingPasswordEncoder 的工作原理了。

如果我們想同時(shí)使用多個(gè)密碼加密方案,看來(lái)使用 DelegatingPasswordEncoder 就可以了,而 DelegatingPasswordEncoder 默認(rèn)還不用配置。

4.體驗(yàn)

接下來(lái)我們稍微體驗(yàn)一下 DelegatingPasswordEncoder 的用法。

首先我們來(lái)生成三個(gè)密碼作為測(cè)試密碼:

  1. @Test 
  2. void contextLoads() { 
  3.     Map<String, PasswordEncoder> encoders = new HashMap<>(); 
  4.     encoders.put("bcrypt", new BCryptPasswordEncoder()); 
  5.     encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5")); 
  6.     encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance()); 
  7.     DelegatingPasswordEncoder encoder1 = new DelegatingPasswordEncoder("bcrypt", encoders); 
  8.     DelegatingPasswordEncoder encoder2 = new DelegatingPasswordEncoder("MD5", encoders); 
  9.     DelegatingPasswordEncoder encoder3 = new DelegatingPasswordEncoder("noop", encoders); 
  10.     String e1 = encoder1.encode("123"); 
  11.     String e2 = encoder2.encode("123"); 
  12.     String e3 = encoder3.encode("123"); 
  13.     System.out.println("e1 = " + e1); 
  14.     System.out.println("e2 = " + e2); 
  15.     System.out.println("e3 = " + e3); 

生成結(jié)果如下:

  1. e1 = {bcrypt}$2a$10$Sb1gAUH4wwazfNiqflKZve4Ubh.spJcxgHG8Cp29DeGya5zsHENqi 
  2. e2 = {MD5}{Wucj/L8wMTMzFi3oBKWsETNeXbMFaHZW9vCK9mahMHc=}4d43db282b36d7f0421498fdc693f2a2 
  3. e3 = {noop}123 

接下來(lái),我們把這三個(gè)密碼拷貝到 SecurityConfig 中去:

  1. @Configuration("aaa"
  2. public class SecurityConfig extends WebSecurityConfigurerAdapter { 
  3.  
  4.     @Override 
  5.     @Bean 
  6.     protected UserDetailsService userDetailsService() { 
  7.  
  8.         InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); 
  9.         manager.createUser(User.withUsername("javaboy").password("{bcrypt}$2a$10$Sb1gAUH4wwazfNiqflKZve4Ubh.spJcxgHG8Cp29DeGya5zsHENqi").roles("admin").build()); 
  10.         manager.createUser(User.withUsername("sang").password("{noop}123").roles("admin").build()); 
  11.         manager.createUser(User.withUsername("江南一點(diǎn)雨").password("{MD5}{Wucj/L8wMTMzFi3oBKWsETNeXbMFaHZW9vCK9mahMHc=}4d43db282b36d7f0421498fdc693f2a2").roles("user").build()); 
  12.         return manager; 
  13.     } 
  14.  
  15.     @Override 
  16.     protected void configure(HttpSecurity http) throws Exception { 
  17.         http.authorizeRequests() 
  18.                 .antMatchers("/admin/**").hasRole("admin"
  19.                 .antMatchers("/user/**").hasRole("user"
  20.                 ... 
  21.     } 

這里三個(gè)用戶使用三種不同的密碼加密方式。

配置完成后,重啟項(xiàng)目,分別使用 javaboy/123、sang/123 以及 江南一點(diǎn)雨/123 進(jìn)行登錄,發(fā)現(xiàn)都能登錄成功。

5.意義何在?

為什么我們會(huì)有這種需求?想在項(xiàng)目種同時(shí)存在多種密碼加密方案?其實(shí)這個(gè)主要是針對(duì)老舊項(xiàng)目改造用的,密碼加密方式一旦確定,基本上沒(méi)法再改了(你總不能讓用戶重新注冊(cè)一次吧),但是我們又想使用最新的框架來(lái)做密碼加密,那么無(wú)疑,DelegatingPasswordEncoder 是最佳選擇。

好啦,這就是今天和小伙伴們分享的多種密碼加密方案問(wèn)題,感興趣的小伙伴記得點(diǎn)個(gè)在看鼓勵(lì)下松哥哦~

原文鏈接:原文地址:https://mp.weixin.qq.com/s/8GJCqcEYW7ZGKlKXRTwGMg

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国产精品区牛牛影院 | 青青青国产精品国产精品久久久久 | 成人免费影 | 欧美亚洲视频在线观看 | 99任你躁精品视频 | 国产在线精品亚洲第一区香蕉 | 久久久久综合 | 操碰91| 亚洲香蕉伊在人在线观看9 亚洲系列国产系列 | 成人精品一区二区三区 | 我与白丝同桌的故事h文 | 国产亚洲一级精品久久 | 波多野结衣在线观看中文字幕 | 亚洲国产精品婷婷久久久久 | 亚洲无限 | 亚洲精品国产精品国自产观看 | 九九在线精品视频 | 男女小视频在线观看 | 免费免费啪视频在线观播放 | 亚洲视频999 | 三极片在线观看 | 国产精品永久免费视频观看 | 国内永久第一免费福利视频 | 日本一区二区不卡久久入口 | xxx黑人又大粗又长 xxxx性欧美极品另类 | 五月天色综合 | 美女被爆操 | 2021国产精品视频 | 性色视频免费 | 日本卡一卡2卡3卡4精品卡无人区 | 日本人交换乱理伦片 | 精品91| 黄漫免费观看 | 国产大片视频免费观看 | 我要看黄色毛片 | 亚洲美女啪啪 | 国产一区二区精品 | 四虎影视在线影院在线观看 | 国产精品毛片高清在线完整版 | 极品在线| 视频在线观看一区二区 |