公司项目中用的是shiro做安全认证框架,从代码中看到了判断验证码的,也看到了判断用户名是否存在的,就是没有发现判断密码是否正确的,后从网上文章以及查看源码才大概了解shiro对于密码验证的流程。

自定义的shiroRealm

public class ShiroRealm extends AuthorizingRealm {
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
	// 第一步从token中取出用户名
		String userName = (String) token.getPrincipal();
	// 第二步:根据用户输入的userName从数据库查询
	User user = userService.findByUsername("userName");		
	if(user==null){
		return null;//用户不存在
	}
	//第三步  从数据库取该用户的passw
	String password = user.getPassword();
	// 第四步  加盐
		String salt = userCode;
		.......其他判断逻辑......
		// 第五步  创建SimpleAuthenticationInfo
		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,password,ByteSource.Util.bytes(salt), this.getName());
		//第六步 返回
		return simpleAuthenticationInfo;//  return的过程完成 password的验证
}

}

注意:最后的return simpleAuthenticationInfo 的时候就会触发password验证。
我们要知道一个继承关系
shiroRealm----->AuthorizingRealm---->AuthenticatingRealm

当执行"return simpleAuthenticationInfo"之后,会调用AuthenticatingRealm的getAuthenticationInfo()方法
 

 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) {
        	//验证token,info的数据
            assertCredentialsMatch(token, info);
        } else {
            log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
        }

        return info;
    }

上面代码中又调用了assertCredentialsMatch(token, info);

    protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
        CredentialsMatcher cm = getCredentialsMatcher();
        if (cm != null) {
        //判断验证是否通过,如果不通过则抛出异常(这个异常将在LoginController中捕获并处理)
            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.");
        }
    }

继续看doCredentialsMatch()的源码
调用的是类HashedCredentialsMatcher的方法

 public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        Object tokenHashedCredentials = hashProvidedCredentials(token, info);//这里将得到页面传递来的password通过加密后的结果
        Object accountCredentials = getCredentials(info);//这里得到是数据库的passwrod通过加密后的结果
        return equals(tokenHashedCredentials, accountCredentials);
    }

到这里就可看到password验证的大致流程,
如果返回true,那么验证就通过了。
如何返回false,那么上面的AuthenticatingRealm.assertCredentialsMatch()方法会抛出 IncorrectCredentialsException异常
在我们的LoginController中可以看到捕获shiro中异常的代码
 

@Controller
public class LoginController{
	@RequestMapping(value="login")
	public String login(HttpServletRequest request)throws Exception{
	//如果登陆失败从request中获取认证异常信息,shiroLoginFailure就是shiro异常类的全限定名
		String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
		//根据shiro返回的异常类路径判断,抛出指定异常信息
		if(exceptionClassName!=null){
			if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
				//最终会抛给异常处理器
				isSucCode = 1;
				errInfo = "账号不存在或已作废";
				/*throw new CustomException("账号不存在");*/
			} else if (AuthenticationException.class.getName().equals(exceptionClassName)){
				//最终会抛给异常处理器
				isSucCode = 1;
				errInfo = "账号不存在或已作废";
			}else if (IncorrectCredentialsException.class.getName().equals(
					exceptionClassName)) { 
				isSucCode = 2;
				errInfo = "密码错误";
				/*throw new CustomException("密码错误");*/
			} else if("randomCodeError".equals(exceptionClassName)){
				isSucCode = 3;
				errInfo = "验证码错误";
				/*throw new CustomException("验证码错误");*/
			}else {
				isSucCode = 4;
				errInfo = "未知错误,请联系管理员";
				/*throw new CustomException("未知错误,请联系管理员");*/
			}
		}
}

}

可以看到获取IncorrectCredentialsException异常后,提示密码错误给前段页面.

 

补充:

1.上面进行密码校验用到了盐,并且自己定义了密码校验的配置文件,如下:

public class BigScreenMatcher  extends HashedCredentialsMatcher {

    public BigScreenMatcher() {
    }

    /**
     * 密码校验
     * @param token  页面传过来的密码
     * @param info  查询出的数据库密码
     * @return
     */
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        PasswordHelper passwordHelper = new PasswordHelper();
        BigScreenToken bigScreenToken = (BigScreenToken)token;
        return info.getCredentials().toString().equals(passwordHelper.encryptPassword(bigScreenToken.getPassword(), bigScreenToken.getSalt()));
    }
}

2.如果不加盐的话,可以直接使用下面的配置进行密码校验,不用自己再定义上面的类来校验:

/**
     * 凭证匹配器
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
     * 所以我们需要修改下doGetAuthenticationInfo中的代码;
     * )
     * 可以扩展凭证匹配器,实现 输入密码错误次数后锁定等功能,下一次
     */
    @Bean(name = "credentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        //散列的次数,比如散列两次,相当于 md5(md5(""));
        hashedCredentialsMatcher.setHashIterations(2);
        //storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return hashedCredentialsMatcher;
    }

 

更多推荐