Spring Security 進階-加密篇

在 Spring Security 中加密是一個很簡單卻又不能忽略的模塊,數據只有加密起來才更安全,這樣就散算據庫密碼泄漏也都是密文。本文分析對應的版本是 5.14。

概念

Spring Security 爲咱們提供了一套加密規則和密碼比對規則,org.springframework.security.crypto.password.PasswordEncoder 接口,該接口裏面定義了三個方法。前端

public interface PasswordEncoder {

    //加密(外面調用通常在註冊的時候加密前端傳過來的密碼保存進數據庫)
    String encode(CharSequence rawPassword);

    //加密先後對比(通常用來比對前端提交過來的密碼和數據庫存儲密碼, 也就是明文和密文的對比)
    boolean matches(CharSequence rawPassword, String encodedPassword);

    //是否須要再次進行編碼, 默認不須要
    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

該接口實現類有下面這幾個

clipboard.png

其中經常使用到的分別有下面這麼幾個web

  • BCryptPasswordEncoder:Spring Security 推薦使用的,使用BCrypt強哈希方法來加密。
  • MessageDigestPasswordEncoder:用做傳統的加密方式加密(支持 MD五、SHA-一、SHA-256...)
  • DelegatingPasswordEncoder:最經常使用的,根據加密類型id進行不一樣方式的加密,兼容性強
  • NoOpPasswordEncoder:明文, 不作加密
  • 其餘

使用示例

MessageDigestPasswordEncoder

構造的時候須要傳入算法字符串,例如 "MD5"、"SHA-1"、"SHA-256"...算法

String password = "123";

MessageDigestPasswordEncoder encoder = new MessageDigestPasswordEncoder("MD5");
String encode = encoder.encode(password);
System.out.println(encode);
System.out.println(encoder.matches(password,encode) == true ? "相等" : "不相等");

輸出spring

{EUjIxnT/OVlk5J54s3LaJRuQgwTchm1gduFHTqI0qjo=}4b40375c57c285cc56c7048bb114db23
相等

調用 encode(..) 加密方法每次都會隨機生成鹽值,因此對相同的明文進行屢次加密,每次結果也是不同的。
從上面輸出部分結合源碼能夠的出:加密的最終結果分爲兩部分,鹽值 + MD5(password+鹽值),調用 matches(..) 方法的時候先從密文中獲得鹽值,用該鹽值加密明文和最終密文做對比。數據庫

BCryptPasswordEncoder

構造的時候能夠傳入哈希強度(strength),強度越大計算量就越大,也就意味着越安全,strength 取值區間[4-31],系統默認是10。緩存

String password = "123";

BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String encode = encoder.encode(password);
System.out.println(encode);
System.out.println(encoder.matches(password, encode) == true ? "相等" : "不相等");

輸出安全

$2a$10$lxPfE.Zvat6tejB8Q1QGYu3M9lXUUpiWFYzboeyK64kbfgN9v7iBq
相等

調用 encode(..) 方法加密跟上面同樣,每次都會隨機生成鹽值,密文也分爲兩部分,鹽值和最終加密的結果,最終對比的時候從密文裏面拿出鹽值對明文進行加密,比較最終加密後的結果。ide

DelegatingPasswordEncoder

這是 Spring Security 推出的一套兼容方案,根據加密類型id字符串(idForEncode)去自身緩存的全部加密方式中(idToPasswordEncoder)取出對應的加密方案對象對明文進行加密和對應密文的對比,只是其密文前面都加上了加密方案id的字符串,具體的我們看下面代碼演示。oop

其初始化 Spring Security 提供了一個工廠構造方法ui

public class PasswordEncoderFactories {

    @SuppressWarnings("deprecation")
    public static PasswordEncoder createDelegatingPasswordEncoder() {
        String encodingId = "bcrypt";
        Map<String, PasswordEncoder> encoders = new HashMap<>();
        encoders.put(encodingId, new BCryptPasswordEncoder());
        encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
        encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
        encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
        encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
        encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
        encoders.put("scrypt", new SCryptPasswordEncoder());
        encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
        encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
        encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());

        return new DelegatingPasswordEncoder(encodingId, encoders);
    }
}

這個工廠的靜態構造方法把經常使用的幾種方案都注入到緩存中,可是注入的 idForEncode 對應的倒是 BCryptPasswordEncoder,這樣系統就能夠達到在新存儲密碼可使用 BCryptPasswordEncoder 加密方案進行加密,可是對於數據庫裏面之前用其餘方式加密的密碼也支持比對。

String password = "123";

PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
String encode = encoder.encode(password);
System.out.println(encode);
System.out.println(encoder.matches(password, encode) == true ? "相等" : "不相等");

輸出

{bcrypt}$2a$10$Bh23zGZ2YPOsORNexoowb.fX4QH18GEh13eVtZUZvbe2Blx0jIVna
相等

從結果中能夠看出,相比原始的 BCryptPasswordEncoder 密文前面多了加密方式的id。

固然也能夠自定義構造方法,來制定 DelegatingPasswordEncoder 用其餘的方案進行加密。

接下來咱們將其指定使用 MD5 方式來加密密碼看看結果

Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
DelegatingPasswordEncoder encoder = new DelegatingPasswordEncoder("MD5", encoders);
String encode = encoder.encode(password);
System.out.println(encode);
System.out.println(encoder.matches(password, encode) == true ? "相等" : "不相等");

輸出

{MD5}{XYwuzP8/lL/a3ASzA9UVM4rFs8lbsLvEoa5ydKER844=}d7f919bfd94554150f8ab3a809209ee3
相等

相比原始的 MessageDigestPasswordEncoder也是密文前面多了加密方式的id。

應用

先示範下使用系統的 UserDetailsManager 來演示下簡單的注入

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("liuchao")
                    .password("{bcrypt}$2a$10$S.hMD3oV60YRIj38lHRhP.e3DAu3OwmssE/u/p2GLqqZ3SVsZA77W")
                    .roles("admin","user")
                    .and()
                .passwordEncoder(passwordEncoder);
    }

    @Bean(value = "passwordEncoder")
    public PasswordEncoder delegatingPasswordEncoder() {
        //構造 DelegatingPasswordEncoder 加密方案
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Autowired
    private PasswordEncoder passwordEncoder;
}
相關文章
相關標籤/搜索