在核心类AuthenticatingRealm的getAuthenticationInfo(AuthenticationToken token)方法中完成登录表单提交的密码校验。
shiro类方法调用示意图
1.getAuthenticationInfo核心方法
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //传入的AuthenticationToken token为表单提交参数封装的类,存储了用户名、明文密码、记住我我、主机IP。 AuthenticationInfo info = getCachedAuthenticationInfo(token); if (info == null) { //从自定义的UserRealm中获取AuthenticationInfo(一般是数据库),当作一个比较标本,用途把token生成的密文是与这个标本的密码、密文进行比较,若一致则算登录成功。 //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; }
2.doGetAuthenticationInfo,自定义realm,根据token中的用户名,从数据库中获得密文、salt、realm名封装成info
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String)token.getPrincipal(); User user = userService.findByUsername(username); if(user == null) { throw new UnknownAccountException();//没找到帐号 } if(Boolean.TRUE.equals(user.getLocked())) { throw new LockedAccountException(); //帐号锁定 } //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( user.getUsername(), //用户名 user.getPassword(), //密码 ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt getName() //realm name ); return authenticationInfo; }
3.assertCredentialsMatch,凭证匹配器,提交的凭证和存储的凭证进行匹配比较
//Asserts that the submitted AuthenticationToken's credentials match the stored account AuthenticationInfo's credentials //我的理解翻译是提交的凭证和存储的凭证进行匹配比较 protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException { //返回设定的凭证匹配器(匹配规则),包含了hashAlgorithmName(加密方式名如md5)、hashIterations(加密次数)、storedCredentialsHexEncoded(密文进行16进制存储)、hashSalted(默认值false)、passwordRetryCache(密码重试缓存)5个属性。 CredentialsMatcher cm = getCredentialsMatcher() if (cm != null) { //执行密文匹配,在此时此地,token中的密码还是明文的,未加密 if (!cm.doCredentialsMatch(token, info)) { //not successful - throw an exception to indicate this: String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials."; throw new IncorrectCredentialsException(msg); } } else { throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " + "credentials during authentication. If you do not wish for credentials to be examined, you " + "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance."); } }
4.doCredentialsMatch,密码错误次数后父类HashedCredentialsMatcher的doCredentialsMatch
//在自定义的RetryLimitHashedCredentialsMatcher的doCredentialsMatch方法中加入密码错误次数判断后进入父类HashedCredentialsMatcher的doCredentialsMatch。 //父类的doCredentialsMatch @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { Object tokenHashedCredentials = hashProvidedCredentials(token, info); Object accountCredentials = getCredentials(info); return equals(tokenHashedCredentials, accountCredentials); }
5.hashProvidedCredentials,从info中取出salt,和表单提交的密码一个需认证的Hash
//取出盐 protected Object hashProvidedCredentials(AuthenticationToken token, AuthenticationInfo info) { Object salt = null; if (info instanceof SaltedAuthenticationInfo) { salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt(); } else { //retain 1.0 backwards compatibility: if (isHashSalted()) { salt = getSalt(token); } } return hashProvidedCredentials(token.getCredentials(), salt, getHashIterations()); } //生成hash //Object credentials 用户名 //Object salt 盐 //int hashIterations hash次数 protected Hash hashProvidedCredentials(Object credentials, Object salt, int hashIterations) { //算法名,如md5 String hashAlgorithmName = assertHashAlgorithmName(); //创建用户账号时生成的密文也是生成一个SimpleHash,构造参数多一个hashIterations return new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); }
6.getCredentials,从存储的info中提取密文,转换成一个hash对象
// protected Object getCredentials(AuthenticationInfo info) { Object credentials = info.getCredentials(); byte[] storedBytes = toBytes(credentials); if (credentials instanceof String || credentials instanceof char[]) { //account.credentials were a char[] or String, so //we need to do text decoding first: if (isStoredCredentialsHexEncoded()) { storedBytes = Hex.decode(storedBytes); } else { storedBytes = Base64.decode(storedBytes); } } AbstractHash hash = newHashInstance(); hash.setBytes(storedBytes); return hash; } //两个hash对象进行比较
7.equals,两个hash对象进行比较
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); } }