1. 获取当前的 Subject. 调用 SecurityUtils.getSubject();
2. 测试当前的用户是否已经被认证. 即是否已经登录. 调用 Subject 的 isAuthenticated()
3. 若没有被认证, 则把用户名和密码封装为 UsernamePasswordToken 对象
1). 创建一个表单页面
2). 把请求提交到 SpringMVC 的 Handler
3). 获取用户名和密码.
4. 执行登录: 调用 Subject 的 login(AuthenticationToken) 方法.
5. 自定义 Realm 的方法, 从数据库中获取对应的记录, 返回给 Shiro.
1). 实际上需要继承 org.apache.shiro.realm.AuthenticatingRealm 类
2). 实现 doGetAuthenticationInfo(AuthenticationToken) 方法.
6. 由 shiro 完成对密码的比对.
package com.atguigu.shiro.handlers;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
@RequestMapping("/shiro")
public class ShiroHandler {
@RequestMapping("/login")
public String login(@RequestParam("username")String username,
@RequestParam("password")String password){
//获取Subkect
Subject subject = SecurityUtils.getSubject();
if(!subject.isAuthenticated()){
//把用户名和密码封装成UsernamePasswordToken对象
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
//记住我
token.setRememberMe(true);
try {
//执行登陆
subject.login(token);
} catch (AuthenticationException e) {
// TODO: handle exception
System.out.println("登录失败:"+e.getMessage());
}
}
return "redirect:/list.jsp";
}
}
数据库中的密码是经过MD5加盐值加密1024次后的结果所以认证时,前台传过来的密码也要在shiro中进行加密。在applicationContext.xml中配置加密方法及次数
<bean id="jdbcRealm" class="com.atguigu.shiro.realms.ShiroRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!-- 指定加密算法为MD5 -->
<property name="hashAlgorithmName" value="MD5"></property>
<!-- 指定加密1024次 -->
<property name="hashIterations" value="1024"></property>
</bean>
</property>
</bean>
subject.login(token)之后会进入自定义的Realm的doGetAuthenticationInfo方法
package com.atguigu.shiro.realms;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.util.ByteSource;
public class ShiroRealm extends AuthenticatingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("doGetAuthenticationInfo:"+token);
//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.根据用户信息的情况,决定是否抛出其他的异常
if("master".equals(username)){
throw new LockedAccountException("用户被锁定");
}
//6. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
//以下信息是从数据库中获取的.
//1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象.
Object principal = username;
//2). credentials: 密码. 注册时经过MD5加上盐值username加密1024次后的字符串
Object credentials = null; //"fc1709d0a95a6be30bc5926fdb7f22f4";
if("admin".equals(username)){
credentials = "038bdaf98f2037b31f1e75b5b4c9b26e";
}else if("user".equals(username)){
credentials = "098d2c478e9c11555ce2823231e02ec1";
}
//3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可
String realmName = getName();
//4).盐值 将盐值也传给shiro
ByteSource credentialsSalt = ByteSource.Util.bytes(username);
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
return info;
}
}
前台传进来的username和password封装到UsernamePasswordToken中传给shiro
从数据库中获取实体类并将username或该实体类、密码、盐值、realmname封装进SimpleAuthenticationInfo返回(传给shiro)
在shiro内部进行登录比较
用户信息可能保存在不同的数据库,加密算法也可能不同,所以需要不同的Realm,
在applicationContext.xml中添加Realm配置
<bean id="secondRealm" class="com.atguigu.shiro.realms.SecondRealm">
<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>
将该secondRealm添加到SecurityManager中
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="cacheManager"/>
<property name="authenticator" ref="authenticator"/>
<property name="realms">
<list>
<ref bean="jdbcRealm"/>
<ref bean="secondRealm"/>
</list>
</property>
</bean>
同时配置shiro的认证策略
<bean id="authenticator"
class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<!-- 认证策略
1.AtLeastOneSuccessfulStrategy只要一个Realm认证成功就可以
2.AllSuccessfullStrategy需要所有的Realm都认证成功
-->
<property name="authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
</property>
</bean>
再添加一个新的继承AuthenticatingRealm的类,实现doGetAuthenticationInfo(),认证完成。