Shiro笔记(四)编码/加密
一、编码和解码
1 //base64编码、解码 2 @Test 3 public void testBase64(){ 4 String str="tang"; 5 byte[] encode = Base64.encode(str.getBytes()); 6 System.out.println("Encode result:"+encode); 7 System.out.println("The decode result:"+Base64.decodeToString(encode)); 8 } 9 10 //16进制字符串编码,解码 11 @Test 12 public void testHex(){ 13 String str="Java"; 14 char[] encode = Hex.encode(str.getBytes()); 15 System.out.println("encode result:"+encode); 16 System.out.println("decode result:"+new String(Hex.decode(encode))); 17 }
二、散列算法
散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如MD5,SHA等。
1 //MD5算法 2 @Test 3 public void testMD5(){ 4 String str="king"; 5 String salt="salt1"; 6 String s = new Md5Hash(str, salt).toString(); 7 System.out.println("MD5 code: "+s); 8 } 9 10 //SHA算法 11 @Test 12 public void testSHA(){ 13 String str="king"; 14 String salt="salt1"; 15 String s = new Sha256Hash(str, salt).toString(); 16 System.out.println("SHA code: "+s); 17 }
三、PasswordService/CredentialMatcher
Shiro 提供了 PasswordService 及 CredentialsMatcher 用于提供加密密码及验证密码服务。
1 public interface PasswordService { 2 3 //输入明文密码得到密文密码 4 String encryptPassword(Object plaintextPassword) throws IllegalArgumentException; 5 6 /** 7 * @param submittedPlaintext a raw/plaintext password submitted by an end user/Subject. 8 * @param encrypted the previously encrypted password known to be associated with an account. 9 * This value is expected to have been previously generated from the 10 * {@link #encryptPassword(Object) encryptPassword} method (typically 11 * when the account is created or the account's password is reset). 12 * @return {@code true} if the {@code submittedPlaintext} password matches the existing {@code saved} password, 13 * {@code false} otherwise. 14 * @see ByteSource.Util#isCompatible(Object) 15 */ 16 boolean passwordsMatch(Object submittedPlaintext, String encrypted); 17 }
1 public interface CredentialsMatcher { 2 3 //匹配用户输入的 token 的凭证(未加密)与系统提供的凭证(已加密) 4 boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info); 5 6 }
Shiro 默认提供了 PasswordService 实现 DefaultPasswordService;CredentialsMatcher 实现
PasswordMatcher 及 HashedCredentialsMatcher(更强大)。
应用实例:
1 public class MyRealm extends AuthorizingRealm{ 2 3 private PasswordService passwordService; 4 5 public void setPasswordService(PasswordService passwordService) { 6 this.passwordService = passwordService; 7 } 8 9 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 10 return null; 11 } 12 13 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 14 return new SimpleAuthenticationInfo("tang", 15 passwordService.encryptPassword("123456"),getName()); 16 } 17 }
HashedCredentialsMatcher应用:
1 public class MyRealm2 extends AuthorizingRealm { 2 3 @Override 4 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 5 return null; 6 } 7 8 @Override 9 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 10 String username = "liu"; //用户名及salt1 11 String salt2 = "0072273a5d87322163795118fdd7c45e"; 12 String password = "be320beca57748ab9632c4121ccac0db"; //加密后的密码 13 SimpleAuthenticationInfo ai = new SimpleAuthenticationInfo(username, password, getName()); 14 ai.setCredentialsSalt(ByteSource.Util.bytes(username+salt2)); //盐是用户名+随机数 15 return ai; 16 } 17 }
四、密码重试次数限制
如在 1 个小时内密码最多重试 5 次,如果尝试次数超过 5 次就锁定 1 小时,1 小时后可再
次重试,如果还是重试失败,可以锁定如 1 天,以此类推,防止密码被暴力破解。我们通
过继承 HashedCredentialsMatcher,且使用 Ehcache 记录重试次数和超时时间。
1 public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher { 2 3 private Ehcache passwordRetryCache; 4 5 public RetryLimitHashedCredentialsMatcher() { 6 CacheManager cacheManager = CacheManager.newInstance(CacheManager.class.getClassLoader().getResource("ehcache.xml")); 7 passwordRetryCache = cacheManager.getCache("passwordRetryCache"); 8 } 9 10 @Override 11 public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { 12 String username = (String)token.getPrincipal(); 13 //retry count + 1 14 Element element = passwordRetryCache.get(username); 15 if(element == null) { 16 element = new Element(username , new AtomicInteger(0)); 17 passwordRetryCache.put(element); 18 } 19 AtomicInteger retryCount = (AtomicInteger)element.getObjectValue(); 20 if(retryCount.incrementAndGet() > 5) { 21 //if retry count > 5 throw 22 throw new ExcessiveAttemptsException(); 23 } 24 25 boolean matches = super.doCredentialsMatch(token, info); 26 if(matches) { 27 //clear retry count 28 passwordRetryCache.remove(username); 29 } 30 return matches; 31 } 32 }
1 <?xml version="1.0" encoding="UTF-8"?> 2 <ehcache name="es"> 3 4 <diskStore path="java.io.tmpdir"/> 5 6 7 8 <!-- 登录记录缓存 锁定10分钟 --> 9 <cache name="passwordRetryCache" 10 maxEntriesLocalHeap="2000" 11 eternal="false" 12 timeToIdleSeconds="3600" 13 timeToLiveSeconds="0" 14 overflowToDisk="false" 15 statistics="true"> 16 </cache> 17 18 </ehcache>