Java-Security(三):加密的⽤法、PasswordEncoder类源码分析
在第⼀篇⽂章,我们展⽰了⼀个demo,其中讲到了对⽤户的密码进⾏了明⽂展⽰的⽤法,其实那么做是不安全的,在实际项⽬中往往会采⽤各种加密⽅法(⽐如:bcrypt,md5,sha1,sha2等)来实现对密码的保护。
本⽚⽂章将会主要讲解如何在Spring Security实现对密码加密的各种⽤法,以及对BCrypt的⽤法进⼀步分析。
概念
Spring Security 为我们提供了⼀套加密规则和密码⽐对规则,org.pto.password.PasswordEncoder 接⼝,该接⼝⾥⾯定义了三个⽅法。
public interface PasswordEncoder {
//加密(外⾯调⽤⼀般在注册的时候加密前端传过来的密码保存进数据库)
String encode(CharSequence rawPassword);
//加密前后对⽐(⼀般⽤来⽐对前端提交过来的密码和数据库存储密码, 也就是明⽂和密⽂的对⽐)
boolean matches(CharSequence rawPassword, String encodedPassword);
//是否需要再次进⾏编码, 默认不需要
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
在Spring Security下 PasswordEncoder 的实现类包含:
其中常⽤到的分别有下⾯这么⼏个:
BCryptPasswordEncoder:Spring Security 推荐使⽤的,使⽤BCrypt强哈希⽅法来加密。
MessageDigestPasswordEncoder:⽤作传统的加密⽅式加密(⽀持 MD5、SHA-1、)
DelegatingPasswordEncoder:最常⽤的,根据加密类型id进⾏不同⽅式的加密,兼容性强
NoOpPasswordEncoder:明⽂,不做加密
其他
Spring Security中加密的⽤法:
使⽤bcrypt bean
<bean id="secureRandom" class="java.security.SecureRandom"/>
<bean id="bCryptPasswordEncoder" class="org.pto.bcrypt.BCryptPasswordEncoder">
<constructor-arg name="version" value="$2A"/><!-- salt随机⽣成版本默认$2A-->
<constructor-arg name="strength" value="10"/><!-- 使⽤salt进⾏加密迭代次数,默认10-->
<constructor-arg name="random" ref="secureRandom"/><!-- 随机算法 -->
</bean>
java加密方式有哪些<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="user" password="$2a$10$LCe6jsoHUrEvWI1KURrqbu/xfuPU5aZj2RkPTVS0d7MUJiT55Lt/y"
authorities="ROLE_USER"/>
<security:user name="admin" password="$2a$10$BR3Np37NbmtWHqpSZE6AMeCMG4Rm.UOUEZ3dYrW3oUXHNuSBXjDwi"
authorities="ROLE_USER, ROLE_ADMIN"/>
</security:user-service>
<security:password-encoder ref="bCryptPasswordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
说明:
1)需要配置 bCryptPasswordEncoder的bean,在该bean配置时,可以指定其构造函数相关参数:
version:salt随机⽣成版本,默认:采⽤ BCryptVersion.$2A.getVersion();
strength:使⽤salt进⾏加密迭代次数,默认:10;
random:随机算法,默认:new SecureRandom()。
2)需要在<authentication-provider>标签下的<password-encoder ref=''/>指定该bean。
密码加密⽤法:
// BCrypt加密与验证,内部默认:
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
System.out.println("passwordEncoder 123456:" + de("123456"));
System.out.println("passwordEncoder 123456:" + de("123456"));
// BCrypt密⽂解析
//参数解释
//1)2a:加密算法版本号。
//2)10:加密轮次,默认为10,数值越⼤,加密时间和越难破解呈指数增长。可在BCryptPasswordEncoder构造参数传⼊。
//3)密码加密:前⾯的内容是盐,后⾯的内容才是真正的密⽂。
//以下⽅式可以更清晰的看出盐和全⽂。
String salt = salt(BCryptPasswordEncoder.BCryptVersion.$2A.getVersion(), 10, new SecureRandom());
String result = BCrypt.hashpw("123456", salt);//全⽂
System.out.println("salt:" + salt + ",salt's length:" + salt.length()); // salt长度是29
System.out.println("result:" + result);
在对密码加密时,可以采⽤上边这3种⽅法:
1)BCryptPasswordEncoder的实例,直接调⽤ encode⽅法,此时version,strlength,random都采⽤默认值。
2)也可以使⽤BCrypt来实现,实际上上边BCypt的操作就是BCryptPasswordEncoder#encode内部的⽅法实现。
3)另外,也可以直接在代码中引⼊l中的md5 bean到代码中 @Resources("bCryptPasswordEncoder") private PasswordEncoder bCryptPasswordEncoder;。
使⽤md5 bean
<bean id="md5" class="org.pto.password.MessageDigestPasswordEncoder">
<constructor-arg name="algorithm" value="MD5"/>
<property name="iterations" value="10"/>
</bean>
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="user" password="{sBNW6rB991DqeGbH6ikVJcTe6XwPoHtPW/iyWkwbrF4=}38dee1075a2eaa458bc3fb7e7a945ef8"
authorities="ROLE_USER"/>
<security:user name="admin" password="{sBNW6rB991DqeGbH6ikVJcTe6XwPoHtPW/iyWkwbrF4=}38dee1075a2eaa458bc3fb7e7a945ef8"
authorities="ROLE_USER, ROLE_ADMIN"/>
</security:user-service>
<security:password-encoder ref="md5"/>
</security:authentication-provider>
</security:authentication-manager>
说明:
1)需要配置md5 bean,在配置bean时,必须指定MessageDigestPasswordEncoder的构造函数参数:algorithm:指定算法类型,这⾥是MD5;
2)另外,md5#iterations参数:迭代次数如果不指定,默认为1,这⾥指定为10;
2)需要在<authentication-provider>标签下的<password-encoder ref=''/>指定该bean。
密码加密⽤法:
MessageDigestPasswordEncoder md5 = new MessageDigestPasswordEncoder("MD5");
md5.setIterations(10);
md5Password = "{MD5}" + de("password");
System.out.println("MD5密码:" + md5Password);
System.out.println("MD5密码对⽐:" + passwordEncoder.matches("password", md5Password));
在对密码加密时,可以采⽤上边⽅法:
1)MessageDigestPasswordEncoder的实例,可以设置其迭代次数。
2)另外,也可以直接在代码中引⼊l中的md5 bean到代码中 @Resources("md5") private PasswordEncoder md5;。
缺省password-encoder(DelegatingPasswordEncoder)
当缺省<security:password-encoder ref="xxx"/>时,Spring Security会使⽤系统内置的DelegatingPasswordEncoder,⾃动动适配 PasswordEncoder。
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<!-- Instance()-->
<security:user name="user" password="{noop}userpwd" authorities="ROLE_USER"/>
<security:user name="admin" password="{noop}adminpwd" authorities="ROLE_USER, ROLE_ADMIN"/>
<!-- bcrypt new BCryptPasswordEncoder() -->
<security:user name="user1" password="{bcrypt}$2a$10$LCe6jsoHUrEvWI1KURrqbu/xfuPU5aZj2RkPTVS0d7MUJiT55Lt/y"
authorities="ROLE_USER"/>
<security:user name="admin1" password="{bcrypt}$2a$10$BR3Np37NbmtWHqpSZE6AMeCMG4Rm.UOUEZ3dYrW3oUXHNuSBXjDwi"
authorities="ROLE_USER, ROLE_ADMIN"/>
<!-- MD5 new MessageDigestPasswordEncoder("MD5") -->
<security:user name="user2" password="{MD5}{sBNW6rB991DqeGbH6ikVJcTe6XwPoHtPW/iyWkwbrF4=}38dee1075a2eaa458bc3fb7e7a945ef8"                              authorities="ROLE_USER"/>
<security:user name="admin2" password="{MD5}{sBNW6rB991DqeGbH6ikVJcTe6XwPoHtPW/iyWkwbrF4=}38dee1075a2eaa458bc3fb7e7a945ef8"                              authorities="ROLE_USER, ROLE_ADMIN"/>
</security:user-service>
<security:password-encoder ref="md5"/>
</security:authentication-provider>
</security:authentication-manager>
1)如果在<security:authentication-provider>下指定了<security:password-encoder ref="xxx"/>就不需要在<security:user name="xxx"
password="yyy"authorities="zzz"/>中的password前边加上加密类型({noop}、{bcrypt}、{MD5}等),否则会导致密码验证失败;
2)如果在<security:authentication-provider>下未指定<security:password-encoder ref="xxx"/>就必须要在<security:user name="xxx" password="yyy" authorities="zzz"/>中的password前边加上加密类型({noop}、{bcrypt}、{MD5}等),否则会导致密码验证失败。因为此时验证密码是否成功,会调⽤org.pto.password.DelegatingPasswordEncoder.java中的#encode⽅法、#matches⽅法,⽽DelegatingPasswordEncoder中查密码加密对应的PasswordEncoder时,会根据密码前缀的加密类型查:如果查失败,会导致查不到delegate,也就是delegate为null。
密码加密、解密代码⽰例:
PasswordEncoder passwordEncoder = ateDelegatingPasswordEncoder();
// 此时encode内部使⽤的就是 BCryptPasswordEncoder
String encode = de("password");
System.out.println("bcrypt密码对⽐:" + passwordEncoder.matches("password", encode));
// 不带salt,迭代
String md5NoSaltPassword = "{MD5}" + DigestUtils.md5DigestAsHex("password".getBytes());
System.out.println("MD5(不含salt、iterations)密码对⽐:" + passwordEncoder.matches("password", md5NoSaltPassword));
// 待salt,迭代
MessageDigestPasswordEncoder md5SaltIterationsPassword = new MessageDigestPasswordEncoder("MD5");
md5SaltIterationsPassword.setIterations(1);
String md5Password = "{MD5}" + de("password");
System.out.println("MD5(包含salt、iterations)密码对⽐:" + passwordEncoder.matches("password", md5Password));
String noopPassword = "{noop}password";
System.out.println("noop密码对⽐:" + passwordEncoder.matches("password", noopPassword));
输出结果:
bcrypt密码对⽐:true
MD5(不含salt、iterations)密码对⽐:true
MD5(包含salt、iterations)密码对⽐:true
noop密码对⽐:true
DelegatingPasswordEncoder类讲解
构造函数初始化
DelegatingPasswordEncoder本⾝就是继承了 PasswordEncoder 类,因此也可以在l中定义为bean,在<security:authentication-provider>下指定<security:password-encoder ref="xxx"/>的ref为该bean。
但是,实际上这么做是没有意义,因为在<security:authentication-provider>下不指定<security:passw
ord-encoder ref="xxx"/>时,系统会缺省的采⽤DelegatingPasswordEncoder作为PasswordEncoder的实现。
public DelegatingPasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder) {
if (idForEncode == null) {
throw new IllegalArgumentException("idForEncode cannot be null");
} else if (!ainsKey(idForEncode)) {
throw new IllegalArgumentException("idForEncode " + idForEncode + "is not found in idToPasswordEncoder " + idToPasswordEncoder);
} else {
Iterator var3 = idToPasswordEncoder.keySet().iterator();
while(var3.hasNext()) {
String id = (();
if (id != null) {
if (id.contains("{")) {
throw new IllegalArgumentException("id " + id + " cannot contain " + "{");
}
if (id.contains("}")) {
throw new IllegalArgumentException("id " + id + " cannot contain " + "}");
}
}
}
this.idForEncode = idForEncode;
this.passwordEncoderForEncode = ((idForEncode);
this.idToPasswordEncoder = new HashMap(idToPasswordEncoder);
}
}
说明:
1)调⽤DelegatingPasswordEncoder#constructor()的类是PasswordEncoderFactories.java:
public class PasswordEncoderFactories {
public static PasswordEncoder createDelegatingPasswordEncoder() {
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("ldap", new LdapShaPasswordEncoder());
encoders.put("MD4", new Md4PasswordEncoder());
encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
encoders.put("noop", Instance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
encoders.put("sha256", new StandardPasswordEncoder());
encoders.put("argon2", new Argon2PasswordEncoder());
return new DelegatingPasswordEncoder(encodingId, encoders);
}
private PasswordEncoderFactories() {
}
}
上边代码是spring security系统中唯⼀⽤来初始化DelegatingPasswordEncoder的地⽅。
1)在Spring Security系统将AuthenticationProvider的bean初始化到Spring容器时,会调
⽤PasswordEncoderFactories#createDelegatingPasswordEncoder()⽅法初始化DelegatingPasswordEncoder;
2)这个过程也就是给AutheticationProvider#passwordEncoder赋值的触发点;
3)当然如果在<security:authentication-provider>下指定<security:password-encoder ref="xxx"/>的ref 不为DelegatingPasswordEncoder时,也将不会调⽤PasswordEncoderFactories#createDelegatingPasswordEncoder()⽅法。
2)idToPasswordEncoder属性:DelegatingPasswordEncoder是⼀个能适配多种PasswordEncoder的委托类,其内部定义了⼀个
Map<String,PasswordEncoder>集合:
key为:PasswordEncoder的别名;
value为:PasswordEncoder的具体实现类。
private final Map<String, PasswordEncoder> idToPasswordEncoder;
idToPasswordEncoder⽤来托管PassswordEncoder的实现,这个类是在DelegatingPasswordEncoder#constructor中被传递初始化的。
3)idForEncode属性:通过PasswordEncoderFactories#createDelegatingPasswordEncoder()中初始化DelegatingPasswordEncoder的代码,可以知
道idForEncode的值是“bcrypt”;
4)passwordEncoderForEncode属性:就是BCryptPasswordEncoder对象。
encode加密
public String encode(CharSequence rawPassword) {
return "{" + this.idForEncode + "}" + de(rawPassword);
}
说明:
1)通过上边DelegatingPasswordEncoder#constructor()代码可以知道:passwordEncoderForEncode属性就是BCryptPasswordEncoder对象;
2)DelegatingPasswordEncoder#encode()⽅法:实际上就是"bcrypt"加密算法。这点⼗分重要,往往也是其特殊之处,需要使⽤者牢记。
3)rawPassword参数:待加密密码明⽂。
matches匹配密码
public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
if (rawPassword == null && prefixEncodedPassword == null) {
return true;
} else {
// 根据密⽂前缀查 delegate
String id = actId(prefixEncodedPassword);
PasswordEncoder delegate = (PasswordEncoder)(id);
if (delegate == null) { // delegate查失败
return this.defaultPasswordEncoderForMatches.matches(rawPassword, prefixEncodedPassword);
} else {
String encodedPassword = actEncodedPassword(prefixEncodedPassword);
return delegate.matches(rawPassword, encodedPassword);
}
}
}
说明:
1)rawPassword参数:密码明⽂;
2)prefixEncodedPassword参数:带有加密类型的密码密⽂,必须带有使⽤的PasswordEncoder类型
(PasswordEncoderFactories#createDelegatingPasswordEncoder()中map#key);
格式举例:
{noop}password
{bcypt}$2a$10$IK/02aEUVRBaeoQsvN.VluPLqNKZ2ZwwTRmAAWXmlnCU5DAjmjtRC
{MD5}5f4dcc3b5aa765d61d8327deb882cf99
{MD5}{L5M7tjEyGdBtyFCyk0pBXOLLFi3AOMEBZqdRDTAwV6c=}c05b48c699659f56462bbed387485cc6
3)当没有指定密码加密类型({bcypt}等)时,会抛出异常:
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
org.pto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:250)    org.pto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:198)
org.springframework.security.authentication.dao.DaoAuthenticationProvider.additionalAuthenticationChecks(DaoAuthenticationProvider.java:90)
org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:166)    org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:175)
。。。
BCryptPasswordEncoder类讲解
属性:
private Pattern BCRYPT_PATTERN;
private final Log logger;
private final int strength;
private final BCryptPasswordEncoder.BCryptVersion version;
private final SecureRandom random;
说明:
1)BCRYPT_PATTERN:bcrypt密⽂格式验证正则表达式;

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。