笔记之"/>
shiro笔记之
公司项目中用的是shiro做安全认证框架,从代码中看到了判断验证码的,也看到了判断用户名是否存在的,就是没有发现判断密码是否正确的,后从网上文章以及查看源码才大概了解shiro对于密码验证的流程。
自定义的shiroRealm
public class ShiroRealm extends AuthorizingRealm {@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// 第一步从token中取出用户名String userName = (String) token.getPrincipal();// 第二步:根据用户输入的userName从数据库查询User user = userService.findByUsername("userName"); if(user==null){return null;//用户不存在}//第三步 从数据库取该用户的passwString password = user.getPassword();// 第四步 加盐String salt = userCode;.......其他判断逻辑......// 第五步 创建SimpleAuthenticationInfoSimpleAuthenticationInfo 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*/@Overridepublic 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;}
更多推荐
shiro笔记之
发布评论