说说shiro的登陆校验吧,shiro的功能就是校验用户的身份和授权用户能访问哪些功能。如果不用shiro我们是如何去实现这个功能呢,一般我们都是在页面让用户输入用户密码,后台控制层接收前台传过来的用户密码,然后根据用户名去库里把对应的密码查出来与用户输的密码进行比较,如果一致,就认为校验通过了。
只要我们知道上面的原理就行了,shiro也是一样的,我们在jsp页面输入用户密码提交到后台控制层,看看下面的代码
这里最主要的是user.login(token);这里有一个参数token,这个token就是用户输入的用户密码,我们平时可能会用一个对象user来封装用户名和密码,shiro用的是token,这个是控制层的代码,还没到shiro,当调用user.login(token)后,就交给shiro去处理了,接下shiro应该是去token中取出用户名,然后根据用户去查数据库,把数据库中的密码查出来。这部分代码一般都是要求我们自定义实现,自定义一个realm,重写doGetAuthenticationInfo方法
这个自定义的realm其实挺简单的,就是查数据库里,然后构造一个Authentication,这个Authentication是什么东东,可以这样理解,前面的token是用户输入的用户密码,这个Authentication就是从库里查出来的用户密码,到时两个一比较就能验证登陆成不成功了。主要代码就是SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,userFormMaps.get(0).get("password"),ByteSource.Util.bytes(username + "" + userFormMaps.get(0).get("credentialsSalt")),getName());这个构造一个Authentication,里面参数有点多,解释一下,第一个是用户名,第二个是密码,第三个是盐,第四个是realm。第一二个参数不用说了,第三个盐,是加密用的,我们存用户的密码不可能存明文,一般都会加密,有用md5的,银行等金融行业会用加密机加密。这个盐是md5的一个参数,如果不用这个参数你想,如果你的密码是123456,别人的也是123456,md5加密后都是同一个密文,别人很容易猜你的蜜码,加了这个后,同样是123456的明文,加密后的密文也不一样,加密机也有这东西,叫加密因子。第四个参数就是你自定义的realm。
到现在其实还没进行用户身份验证,只是准备好了数据,接下来就是校验这两个身份了。
我们这里是定义一个继承HashedCredentialsMatcher的RetryLimitHashedCredentialsMatcher,
这里关键代码:boolean matches = super.doCredentialsMatch(token, info);就是把token跟Authentication进行比较。其实我们知道token里面是用户输入的用户名密码,那个密码没经过任何处理,还是明文,而authentication是数据库里的,是密文,所以要经过处理才能进行校验,这个处理是shiro内部去处理的,里面的源码其实很简单,他就是判断这个authentication是不是SaltedAuthenticationInfo,如果是,他就是SaltedAuthenticationInfo里把盐取出来,再对token中的密码进行加密。我们看看现在这个是authentication接口的子类图
[img]http://dl2.iteye.com/upload/attachment/0127/1742/272fafdc-5918-375b-a12e-38d9a71a6d13.jpg[/img]
上面这段是shiro的原码,他是判断如果是SaltedAuthenticationInfo。其实我们很多时间不一定会用shiro的加密方式,如果不用他的密方式,我们就不用调shiro的验证方法了,在RetryLimitHashedCredentialsMatcher这里我们已经拿到token和authentication,我们把密码拿出来自己比较就可以了,token的明文调用自定义的加密就好。
这里没有加密,如果有加密就把tokenpwd加一下密跟databasepwd比较就可以。这样整个用户身份校验就完成了。
只要我们知道上面的原理就行了,shiro也是一样的,我们在jsp页面输入用户密码提交到后台控制层,看看下面的代码
@RequestMapping(value = "login", method = RequestMethod.POST, produces = "text/html; charset=utf-8")
public String login(String username, String password, HttpServletRequest request) {
try {
Subject user = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
user.login(token);
} catch (LockedAccountException lae) {
token.clear();
request.setAttribute("error", "用户已经被锁定不能登录,请与管理员联系!");
return "/login";
} catch (ExcessiveAttemptsException e) {
token.clear();
request.setAttribute("error", "账号:" + username + " 登录失败次数过多,锁定10分钟!");
return "/login";
} catch (AuthenticationException e) {
token.clear();
request.setAttribute("error", "用户或密码不正确!");
return "/login";
}
request.removeAttribute("error");
} catch (Exception e) {
e.printStackTrace();
request.setAttribute("error", "登录异常,请联系管理员!");
return "/login";
}
return "redirect:index.shtml";
}
这里最主要的是user.login(token);这里有一个参数token,这个token就是用户输入的用户密码,我们平时可能会用一个对象user来封装用户名和密码,shiro用的是token,这个是控制层的代码,还没到shiro,当调用user.login(token)后,就交给shiro去处理了,接下shiro应该是去token中取出用户名,然后根据用户去查数据库,把数据库中的密码查出来。这部分代码一般都是要求我们自定义实现,自定义一个realm,重写doGetAuthenticationInfo方法
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
//查询数据库
UserFormMap userFormMap = new UserFormMap();
userFormMap.put("accountName", "" + username + "");
List<UserFormMap> userFormMaps = userMapper.findByNames(userFormMap);
if (userFormMaps.size() != 0) {
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,userFormMaps.get(0).get("password"),ByteSource.Util.bytes(username + "" + userFormMaps.get(0).get("credentialsSalt")),getName());
return authenticationInfo;
} else {
throw new UnknownAccountException();// 没找到帐号
}
}
这个自定义的realm其实挺简单的,就是查数据库里,然后构造一个Authentication,这个Authentication是什么东东,可以这样理解,前面的token是用户输入的用户密码,这个Authentication就是从库里查出来的用户密码,到时两个一比较就能验证登陆成不成功了。主要代码就是SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,userFormMaps.get(0).get("password"),ByteSource.Util.bytes(username + "" + userFormMaps.get(0).get("credentialsSalt")),getName());这个构造一个Authentication,里面参数有点多,解释一下,第一个是用户名,第二个是密码,第三个是盐,第四个是realm。第一二个参数不用说了,第三个盐,是加密用的,我们存用户的密码不可能存明文,一般都会加密,有用md5的,银行等金融行业会用加密机加密。这个盐是md5的一个参数,如果不用这个参数你想,如果你的密码是123456,别人的也是123456,md5加密后都是同一个密文,别人很容易猜你的蜜码,加了这个后,同样是123456的明文,加密后的密文也不一样,加密机也有这东西,叫加密因子。第四个参数就是你自定义的realm。
到现在其实还没进行用户身份验证,只是准备好了数据,接下来就是校验这两个身份了。
我们这里是定义一个继承HashedCredentialsMatcher的RetryLimitHashedCredentialsMatcher,
@Override
public boolean doCredentialsMatch(AuthenticationToken authcToken,
AuthenticationInfo info) {
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
String username = (String) token.getPrincipal();
boolean matches = super.doCredentialsMatch(token, info);
if (matches) {
// clear retry count
passwordRetryCache.remove(username);
}
return matches;
}
这里关键代码:boolean matches = super.doCredentialsMatch(token, info);就是把token跟Authentication进行比较。其实我们知道token里面是用户输入的用户名密码,那个密码没经过任何处理,还是明文,而authentication是数据库里的,是密文,所以要经过处理才能进行校验,这个处理是shiro内部去处理的,里面的源码其实很简单,他就是判断这个authentication是不是SaltedAuthenticationInfo,如果是,他就是SaltedAuthenticationInfo里把盐取出来,再对token中的密码进行加密。我们看看现在这个是authentication接口的子类图
[img]http://dl2.iteye.com/upload/attachment/0127/1742/272fafdc-5918-375b-a12e-38d9a71a6d13.jpg[/img]
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());
}
上面这段是shiro的原码,他是判断如果是SaltedAuthenticationInfo。其实我们很多时间不一定会用shiro的加密方式,如果不用他的密方式,我们就不用调shiro的验证方法了,在RetryLimitHashedCredentialsMatcher这里我们已经拿到token和authentication,我们把密码拿出来自己比较就可以了,token的明文调用自定义的加密就好。
String tokenpwd=String.valueOf(token.getPassword());
String databasepwd= String.valueOf(info.getCredentials());
boolean matches =tokenpwd.equals(databasepwd);
if (matches) {
// clear retry count
passwordRetryCache.remove(username);
}
return matches;
这里没有加密,如果有加密就把tokenpwd加一下密跟databasepwd比较就可以。这样整个用户身份校验就完成了。