Shiro认证流程
一.shiro认证思路分析
- 获取当前的 Subject. 调用 SecurityUtils.getSubject();
- 测试当前的用户是否已经被认证. 即是否已经登录. 调用 Subject 的 isAuthenticated()
- 若没有被认证, 则把用户名和密码封装为 UsernamePasswordToken 对象
- 1). 创建一个表单页面
- 2). 把请求提交到 SpringMVC 的 Handler
- 3). 获取用户名和密码.
- 执行登录: 调用 Subject 的 login(AuthenticationToken) 方法.
- 自定义 Realm 的方法, 从数据库中获取对应的记录, 返回给 Shiro.
- 1). 实际上需要继承 org.apache.shiro.realm.AuthenticatingRealm 类
- 2). 实现 doGetAuthenticationInfo(AuthenticationToken) 方法.
- 由 shiro 完成对密码的比对.
二.实现认证流程
controller实现
@Controller
@RequestMapping("/shiro/")
public class ShiroController {
@RequestMapping("login")
public String login(String username , String password){
//获取subject
Subject currentUser = SecurityUtils.getSubject();
//判断用户是否登录
if(!currentUser.isAuthenticated()){
//将用户名和密码封装成token对象
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
//实现记住我功能
token.setRememberMe(true);
try {
System.out.println(token.hashCode());
//执行登录操作
currentUser.login(token);
} catch (AuthenticationException e) {
System.out.println("登录失败:"+e);
}
}
return "redirect:/list.jsp";
}
}
实现realm
- 当调用subject调用login(token),实际执行的是realm中的doGetAuthenticationInfo()方法
- realm实现流程
- 1.把AuthenticationToken转换为UsernamePasswordToken
- 2.从token汇总获取username
- 3.调用数据库的方法,从数据库中查询username的对应用户记录
- 4.若用户不存在,则可以抛出UnknownAccountException异常
- 5.根据用户信息情况, 决定是否抛出其他的异常
- 6.根据哦用户情况,来构建AuthenticationInfo对象并返回
public class SecoundRealm extends AuthenticatingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("secoundRealm");
//1.把AuthenticationToken转为UsernamePasswordToken
UsernamePasswordToken uptoken = (UsernamePasswordToken) token;
//2.从UsernamePasswordToken中获取username
String username = uptoken.getUsername();
//3.调用数据库方法,从数据库中取出username对应的用户信息
System.out.println("从数据库中取出username:"+username+"的所有用户信息");
//4.若用户不存在,则可以抛出UnknownAccountException异常
if("unknow".equals(username)){
throw new UnknownAccountException("用户不存在");
}
//5.根据用户信息的情况,决定是否需要抛出其他的AuthenticationExcaptin异常
if("monster".equals(username)){
throw new LockedAccountException("用户被锁定");
}
//6.根据用户的情况,来构建AuthenticationInfo对象并返回
//以下信息从数据库中获取
//1)principal:认证实体的信息 ,可以是username,也可以是数据表中对应的用户实体类对象
Object principal = username;
//2)credentials :密码
Object credentials = null;
if("admin".equals(username)){
credentials = "1b257f95ac34ef075aeb3c7b6c00f841a10268f2222";
}else if("user".equals(username)){
credentials = "c0ea2ff604cab89d32943d91e2510ce09b176f30";
}
//3)realmName:当前realm对象的name,调用父类的getname()方法即可
String realmName = getName();
//4)盐值
ByteSource credentialsSalt = ByteSource.Util.bytes(username);
SimpleAuthenticationInfo info = null;
info = new SimpleAuthenticationInfo("secoundRealm",credentials,credentialsSalt,realmName);
return info;
}
}
三.密码的比对
通过 AuthenticatingRealm 的 credentialsMatcher 属性来进行的密码的比对!
四.密码的盐值加密
- 为什么使用 MD5 盐值加密:
如何做到:
1). 在 doGetAuthenticationInfo 方法返回值创建 SimpleAuthenticationInfo 对象的时候, 需要使用
SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName) 构造器
2). 使用 ByteSource.Util.bytes() 来计算盐值.
3). 盐值需要唯一: 一般使用随机字符串或 user id
4). 使用 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); 来计算盐值加密后的密码的值.如何把一个字符串加密为 MD5
- 替换当前 Realm 的 credentialsMatcher 属性. 直接使用 HashedCredentialsMatcher 对象, 并设置加密算法即可.
- 盐值加密的放阿飞
//通过username来创建盐值
ByteSource credentialsSalt = ByteSource.Util.bytes(username);
//使用比较复杂的构造器,传入盐值 credentialsSalt
SimpleAuthenticationInfo info = null;
info = new SimpleAuthenticationInfo("secoundRealm",credentials,credentialsSalt,realmName);
- 在配置文件中配置加密的方式和加密的次数
<bean id="jdbcRealm" class="com.gdy.shiro.ShiroRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5"></property>
<!--指定加密的次数-->
<property name="hashIterations" value="1024"></property>
</bean>
</property>
</bean>
五.多realm验证
- 配置多个realm
<!--
3. 配置多个 Realm
3.1 直接配置实现了 org.apache.shiro.realm.Realm 接口的 bean
-->
<bean id="jdbcRealm" class="com.gdy.shiro.ShiroRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5"></property>
<!--指定加密的次数-->
<property name="hashIterations" value="1024"></property>
</bean>
</property>
</bean>
<bean id="SecoundRealm" class="com.gdy.shiro.SecoundRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="SHA1"></property>
<!--指定加密的次数-->
<property name="hashIterations" value="1024"></property>
</bean>
</property>
</bean>
- 在securityManager中声明多个realm
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="cacheManager"/>
<property name="authenticator" ref="authenticator"></property>
<property name="realms">
<list>
<ref bean="jdbcRealm"></ref>
<ref bean="SecoundRealm"></ref>
</list>
</property>
</bean>
- 配置认证策略
AuthenticationStrategy 接口的默认实现:
- FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第一个 Realm 身份验证成功的认证信息,其他的忽略;
- AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可, 和FirstSuccessfulStrategy 不同,将返回所有Realm身份验证成功的认证信
息;
- AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。
- ModularRealmAuthenticator 默认是 AtLeastOneSuccessfulStrategy策略
<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<!--认证策略-->
<property name="authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"></bean>
</property>
</bean>