shiro学习04-登录校验(2)
用户的信息多都在Realm中,所以我们先从realm开始看源码吧。
我们在上一篇中用到了AuthenticationRealm,这个就是Realm接口的实现类,我们具体看这个类的源码以及在校验时所用到的方法。
先从这个类的无参构造方法开始
public AuthenticatingRealm() { this(null, new SimpleCredentialsMatcher()); }其中第一个参数是CacheManager,是一个null,第二个是CredentialsMatcher。AuthenticationRealm是CachingRealm的实现类,CachingRealm允许对将用户已经获得AuthenticationIfon进行缓存,方便下一次使用。但是这个缓存默认是关闭的,AuthenticationRealm构造方法中的null表示没有传入CaqcheManager,我们在实际开发中都是关闭缓存,改用redis来实现对用户信息的缓存。
第二个参数CredentialsMatcher很重要,他用来将用户通过表单提交的密码和在数据库中的密码进行比对,判断是不是一直,默认提供的实现类是SimpleCredentialsMatcher,这个类的对比方式如下:
protected boolean equals(Object tokenCredentials, Object accountCredentials) { if (log.isDebugEnabled()) { log.debug("Performing credentials equality check for tokenCredentials of type [" + tokenCredentials.getClass().getName() + " and accountCredentials of type [" + accountCredentials.getClass().getName() + "]"); } if (isByteSource(tokenCredentials) && isByteSource(accountCredentials)) { if (log.isDebugEnabled()) { log.debug("Both credentials arguments can be easily converted to byte arrays. Performing " + "array equals comparison"); } byte[] tokenBytes = toBytes(tokenCredentials); byte[] accountBytes = toBytes(accountCredentials); return Arrays.equals(tokenBytes, accountBytes); } else { return accountCredentials.equals(tokenCredentials); } }我们一般采用字符串作为密码,假设这样的话,会将字符串转化为数组,采用的办法是
String.getBytes("UTF-8")然后调用Arrays.equals的方法。从这里我们看出,SimpleCredentialsMatcher没有涉及任何的加密算法,只适合于我们的普通测试字符串是否相同,如果是加密的我们需要自己定义自己的加密算法和使用加密算法进行对比的CredentialMatcher,但是在实际中我们更倾向于这样做:
在数据库中存储的是经过加密的密码,将用户提交的密码在进行对比之前我们自己使用相同的办法将其进行加密,然后仍然使用SimpleCredentialsMatcher,这样就避免了创建CredentialMatcher的麻烦了。在接触shiro前我的加密算法都是采用的md5 + base64,幸运的是shiro也提供了这两种加密算法,我推荐搭建使用shiro自带的加密类,然后将其封装成一个工具类,我的代码如下:
import org.apache.shiro.crypto.hash.Md5Hash; public class EncryptUtil { public String toMd5(String pwd){ return new Md5Hash(pwd,"salt2016",111).toBase64(); } }shiro提供了Hash接口,表示对明文加密,其中Md5Hash只是个例(这个不是重点,也可以使用Md2Hash) 具体解释如下:org.apache.shiro.crypto.hash.Md5Hash.Md5Hash(Object source, Object salt, int encryptionTime) 第一个参数表示要加密的密码,第二个表示加密过程中加的盐,第三个表示加密的次数,也就是明文加密一次后再对新生成的暗纹继续加密,还有很多重载的Md5Hash构造方法,并且可以不用加盐,也不用指定加密的次数。最后toBase64是用来格式化显示的,因为调用md5加密算法之后会有一些不友好的字符,通过base64将其完全转化为键盘上的字符,这样更加友好。(也可以toHex,只是算法不同,意思一样)
继续我们的AuthenticatingRealm 在另一个构造方法中 AuthenticatingRealm(CacheManager, CredentialsMatcher),我们看一下源码
public AuthenticatingRealm(CacheManager cacheManager, CredentialsMatcher matcher) { authenticationTokenClass = UsernamePasswordToken.class; //retain backwards compatibility for Shiro 1.1 and earlier. Setting to true by default will probably cause //unexpected results for existing applications: this.authenticationCachingEnabled = false; int instanceNumber = INSTANCE_COUNT.getAndIncrement(); this.authenticationCacheName = getClass().getName() + DEFAULT_AUTHORIZATION_CACHE_SUFFIX; if (instanceNumber > 0) { this.authenticationCacheName = this.authenticationCacheName + "." + instanceNumber; } if (cacheManager != null) { setCacheManager(cacheManager); } if (matcher != null) { setCredentialsMatcher(matcher); } }
其中指定了默认支持的AuthenticationTokenClass是UsernamePassowrdToken,所以我们在上一节的代码中使用了UsernamePasswordToken,如果不适用这个的话必须改对AuthenticationReal进行设置,否则会不支持的。从这个代码中发现默认是不支持缓存的,所以关于缓存的我们就可以放心了。然后在这个构造方法中设置了CredentialsMather,就是上面介绍的SimpleCredentialsMatcher。
当某个用户登录时,通过dubug发现调用的是AuthenticatingRealm的getAuthenticationInfo方法,就是通过传入的UsernamePasswordToken,将源码拿出来
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { AuthenticationInfo info = getCachedAuthenticationInfo(token); if (info == null) { //otherwise not cached, perform the lookup: info = doGetAuthenticationInfo(token); log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info); if (token != null && info != null) { cacheAuthenticationInfoIfPossible(token, info); } } else { log.debug("Using cached authentication info [{}] to perform credentials matching.", info); } if (info != null) { assertCredentialsMatch(token, info); } else { log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token); } return info; }上面代码的 大致意思是先从以前缓存的获取,因为我们不做缓存,所以忽略这个,如果缓存中没有的话调用的是doGetAuthenticationInfo(token)方法,这个就是我们上一节中覆写的方法,用来从数据库中获得用户的信息。最后assertCredentialsMatcher调用之前设置的CredentialMatcher进行对比传递进来的token和最后从数据库中获取的info是否一致,我们看一下assertCredentialsMatcher方法的源码: