Shiro学习
什么是Shiro
Shiro是一个安全框架,可以用于认证(Authentication),鉴权(Authorizetion),加密和会话管理。
Shiro的三大核心组件
- Subject :当前用户的操作
- SecurityManager:用于管理所有的Subject
- Realms:用于进行权限信息的验证
Subject:即当前用户,在权限管理的应用程序里往往需要知道谁能够操作什么,谁拥有操作该程序的权利,shiro中则需要通过Subject来提供基础的当前用户信息,Subject 不仅仅代表某个用户,也可以是第三方进程、后台账户(Daemon Account)或其他类似事物。
SecurityManager:即所有Subject的管理者,这是Shiro框架的核心组件,可以把他看做是一个Shiro框架的全局管理组件,用于调度各种Shiro框架的服务。
Realms:Realms则是用户的信息认证器和用户的权限人证器,我们需要自己来实现Realms来自定义的管理我们自己系统内部的权限规则。
Shiro的登录认证流程
使用的subject.login(usernamePasswordToken)完成登录。完成登录后,subject虽然为Shiro库里的实现,
但是subject.getPrincipal()返回的数据类型却为用户的自定义类型UserInfo2?
下面为业务侧登录代码:
@PostMapping("/loginUser")
public String loginUser(@RequestParam("login_name") String login_name,
@RequestParam("password") String password
, HttpSession session, Model model) {
try {
System.out.println("loginUser: " + login_name + ":" + password);
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(login_name, password);
Subject subject = SecurityUtils.getSubject();
subject.login(usernamePasswordToken); //完成登录
UserInfo2 user = (UserInfo2) subject.getPrincipal();
session.setAttribute(Constant.CURRENT_USER, user);
return "redirect:index";
} catch (Exception e) {
model.addAttribute("result", "用户名或密码错误");
return "html/login";
}
}
Shiro的认证流程源代码查看
在前面登录时,会传入认证令牌token(传入的为UsernamePasswordToken), 再看UsernamePasswordToken类的实现,token.getPrincipal()返回的为username。 之后产生新对象SimpleAuthenticationInfo
, 传入的第一个参数为UserInfo2的对象,作为principal存储在内部,所以上面subject.getPrincipal()
获取的对象为UserInfo2对象,SimpleAuthenticationInfo
中保存的是数据库中正确的user和password。并返回新产生的authenticationInfo。那么登录的用户名密码和数据库的用户名密码是在哪里比对的呢?
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String loginname = (String) token.getPrincipal();
UserInfo2 user = userService.findByLoginName(loginname);
if (user == null) {
throw new UnknownAccountException();//没找到帐号
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, //用户
user.getPassword(), //密码
getName() //realm name
);
return authenticationInfo;
}
在AuthenticatingRealm中, 有doGetAuthenticationInfo
函数上层函数调用流程
代码:
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;
}
继续跟进代码,在assertCredentialsMatch
内部有调用HashedCredentialsMatcher::doCredentialsMatch
。
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenHashedCredentials = hashProvidedCredentials(token, info);
Object accountCredentials = getCredentials(info);
return equals(tokenHashedCredentials, accountCredentials);
}
在这里可以看到根据数据库查询结果info和页面传入值token对比。
在Shiro配置代码中有配置密码比对管理器
@Bean(name = "hashedCredentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
log.info("hashedCredentialsMatcher()");
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");// 散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(1024);// 散列的次数,比如散列两次,相当于
// md5(md5(""));
return hashedCredentialsMatcher;
}