流程
- 获取当前的 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完成对密码的比对。
实现
SpringMVC Handler
@Controller
@RequestMapping("/shiro")
public class ShiroHandler {
@RequestMapping("/login")
public String login(@RequestParam("username") String username, @RequestParam("password") String password) {
Subject currentUser = SecurityUtils.getSubject();
if (!currentUser.isAuthenticated()) {
// 把用户名和密码封装为 UsernamePasswordToken 对象
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// rememberme
token.setRememberMe(true);
try {
System.out.println("1:" + token.hashCode());
// 执行登录.
currentUser.login(token);
}
// ... catch more exceptions here (maybe custom ones specific to your
// application?
// 所有认证时异常的父类.
catch (AuthenticationException ae) {
// unexpected condition? error?
System.out.println("登录失败: " + ae.getMessage());
}
}
return "redirect:/list.jsp";
}
}
Realm
Realm的作用就是获取输入的username和password,根据username从数据库取出用户名对应实体的信息,构建相对应的信息,如查询成功则构建正常的AuthenticationInfo对象,失败则根据信息抛出对应的异常。
doGetAuthenticationInfo(AuthenticationToken token)
中的token对象就是Handler中Subject.login()中的token对象。
public class ShiroRealm extends AuthenticatingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("doGetAuthenticationInfo: " + token.hashCode());
// 1. 把AuthenticationToken 转换为UsernamePasswordToken
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
// 2. 把UsernamePasswordToken 中来获取username
String username = upToken.getUsername();
// 3. 调用数据库的方法,从数据库中查询username 对应的用户记录.
System.out.println("从数据库中获取username:" + username + " 所对应的用户信息.");
// 4.若用户不存在,则可以抛出UnknownAccountException 异常
if ("unknown".equals(username)) {
throw new UnknownAccountException("用户不存在");
}
// 5.根据用户信息的情况,决定是否需要抛出其他的AuthenticationException 异常.
if ("monster".equals(username)) {
throw new LockedAccountException("用户被锁定");
}
// 6.根据用户的情况,来构建AuthenticationInfo 对象并返回.
// 通常使用的实现类是SimpleAuthenticationInfo
// 以下信息是从数据库中获取的.
// (1)principal:认证的实体信息,可以是username,也可以是数据表对应的用户的实体类对象
Object principal = username;
// (2)credentials:密码.
Object credentials = "123456";
// (3)realmName:当前realm 对象的name,调用父类的getName()方法
String realmName = getName();
AuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, realmName);
return info;
}
}
密码比对
通过AuthenticatingRealm
的credentialsMatcher
属性来进行的密码的比对!
MD5加密
1、如何把一个字符串加密成MD5
2、替换当前Realm的credentialsMatcher
属性,直接使用HashedCredentialsMatcher
对象,并设置加密算法即可。
在applicationContext中,为Realm添加property属性,设置属性即可。
<bean id="jdbcRealm" class="com.shen.shiro.realms.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>
为其设置一个参数,类对象为HashedCredentialsMatcher
,并为该类设置参数【加密算法,加密次数】。
MD5盐值加密
问题:为什么使用MD5 盐值加密?
即使两个人密码相同,也希望数据库存储的密码也不相同,如何做到?
(1)在doGetAuthenticationInfo
方法返回值创建SimpleAuthenticationInfo
对象的时候,需要使用SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName)
这个构造器。
(2)使用ByteSource.Util.bytes()
来计算盐值。
(3)盐值需要唯一:一般使用随机字符串或user id
(4)使用new SimpleHash(algorithmName, source, salt, hashIterations);
来计算盐值加密后的密码的值。
多Realm
(1)与第一个Realm类似,编写第二个Realm。
(2)在applicationContext.xml中,1)增加第二个Realm的Bean;2)增加一个org.apache.shiro.authc.pam.ModularRealmAuthenticator
的Bean,负责配置多Realm,其中有一个属性为realms
,使用list
标签将多个Realm配置进去。3)将2中的authenticator配置到securityManager
中。
<!-- 3)将认证器配置到securityManager的属性中去 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="cacheManager"/>
<property name="authenticator" ref="authenticator"></property>
</bean>
<!-- 2)认证器的bean -->
<bean id="authenticator"
class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<property name="realms">
<list>
<ref bean="jdbcRealm"/>
<ref bean="secondRealm"/>
</list>
</property>
</bean>
<!-- 1)多个Realm的bean -->
<bean id="jdbcRealm" class="com.shen.shiro.realms.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="secondRealm" class="com.shen.shiro.realms.SecondShiroRealm">
<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>
多Realm的认证策略
共有三种策略:
(1)FirstSuccessfulStrategy:只要有一个Realm验证成功即可,只返回第一个Realm身份验证成功的认证信息,其他的忽略。
(2)AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,它将返回所有Realm身份验证成功的认证信息;
(3)AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。
注:ModularRealmAuthenticator默认是AtLeastOneSuccessfulStrategy策略。
更改认证策略
在applicationContext.xml中的认证器
设置一个属性
<bean id="authenticator"
class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<property name="realms">
<list>
<ref bean="jdbcRealm"/>
<ref bean="secondRealm"/>
</list>
</property>
<!-- 设置认证策略 -->
<property name="authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"/>
</property>
</bean>