在上一篇:shiro整合SpringMVC中,我们已经将shiro和springmvc进行了整合,所以我们在其基础上进行修改。
一、实现原理
使用FormAuthenticationFilter过虑器实现 ,原理如下:
将用户没有认证时,请求loginurl进行认证,用户身份和用户密码提交数据到loginurl
FormAuthenticationFilter拦截住取出request中的username和password(两个参数名称是可以配置的)
FormAuthenticationFilter调用realm传入一个token(username和password)
realm认证时根据username查询用户信息(在Activeuser中存储,包括 userid、usercode、username、menus)。
如果查询不到,realm返回null,FormAuthenticationFilter向request域中填充一个参数(记录了异常信息)
二、自定realm
2.1修改doGetAuthenticationInfo方法
首先我们从token获取用户输入的username,根据用户输入的username去数据库中查找用户的信息,如果查不到用户信息直接返回null,然后进入loginController,因为没有查询到用户信息,就会跳转到登录页面;如果能到查到用户的信息,那么我们就获取用户的密码和盐(shiro的散列算法),最后我们会实例化出一个activeUser对象用来存储用户的菜单和权限信息,然后返回认证信息。
public class CustomRealm extends AuthorizingRealm {
@Resource
private SysService sysService;
@Override
public void setName(String name) {
// TODO Auto-generated method stub
super.setName("customRealm");
}
//用于授权
//Authorization 授权的意思
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//从 principals获取主身份信息
//将getPrimaryPrincipal方法返回值转为真实身份类型(在上边的doGetAuthenticationInfo认证通过填充到SimpleAuthenticationInfo中身份类型),
ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
// 根据身份信息获取权限信息
// 从数据库获取到权限数据
List permissionList = null;
try {
permissionList = sysService.findPermissionListByUserId(activeUser.getUserid());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 单独定一个集合对象
List permissions = new ArrayList();
if (permissionList != null) {
for (SysPermission sysPermission : permissionList) {
// 将数据库中的权限标签 符放入集合
permissions.add(sysPermission.getPercode());
}
}
// 查到权限数据,返回授权信息(要包括 上边的permissions)
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// 将上边查询到授权信息填充到simpleAuthorizationInfo对象中
simpleAuthorizationInfo.addStringPermissions(permissions);
return simpleAuthorizationInfo;
}
//用户认证
//Authentication 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1.第一步从token中取出用户名
String userCode = (String) token.getPrincipal();
//2.根据用户名查询用户
SysUser sysUser = null;
try {
sysUser = sysService.findSysUserByUserCode(userCode);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//如果查询不到返回null
if(sysUser == null){
return null;
}
//获得密码
String password = sysUser.getPassword();
//盐
String salt = sysUser.getSalt();
// 实例化对象
ActiveUser activeUser = new ActiveUser();
activeUser.setUserid(sysUser.getId());
activeUser.setUsercode(sysUser.getUsercode());
activeUser.setUsername(sysUser.getUsername());
// 通过server取菜单
List menus = null;
try {
menus = sysService.findMenuListByUserId(sysUser.getId());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
activeUser.setMenus(menus);
// 如果找到了返回认证信息Authentication
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(activeUser, password,
ByteSource.Util.bytes(salt),this.getName());
return simpleAuthenticationInfo;
}
}
2.2ActiveUser实体类
public class ActiveUser implements java.io.Serializable {
private String userid;//用户id(主键)
private String usercode;// 用户账号
private String username;// 用户名称
private List menus;// 菜单
private List permissions;// 权限
2.3修改shiro的配置文件
配置shiro的散列,并将器注入得到realm中(完整的配置文件参考下边)
三、修改登录的jsp页面
FormAuthenticationFilter进行拦截的时候,默认情况下他从request中拿到的登录信息是,username和password这两个,如下图
FormAuthenticationFilter拦截住取出request中的username和password,所以我们需要保证jsp页面中请求的参数也要是这样
3.1.1.修改shiro的配置文件(在我们设置的凭证器下边加入)
在shiroFilter中加入
3.1.2创建一个MyFormAuthenticationFilter
public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
return super.onAccessDenied(request, response);
}
}
四、看一下现在shiro的配置文件(本篇讲到的所有配置文件)
我们在shiro的过滤连中配置了/logout.action = logout,就可以实现了退出功能
/images/** = anon
/js/** = anon
/styles/** = anon
/logout.action = logout
/** = authc
因为已经将登录的验证交给了FormAuthenticationFilter,如果登录信息没问题他会直接跳转到我们配置的登录成功页面(如果不进行配置,他会直接跳到上一个请求页面),如果登录过程中出现错误,他会获取错误信息然后进行返回
LoginController用于显示登录表单页面,其中shiro authc拦截器进行登录,登录失败的话会把错误存到shiroLoginFailure属性中,在该控制器中获取后来显示相应的错误信息
/**
* 此方法只处理登录失败,如果登录成功shiro会自动跳转到上一个请求路径
* @param request
* @return
* @throws Exception
*/
@RequestMapping("login.do")//这个路径要和shiro.xml配置的路径要一样
public String login(HttpServletRequest request) throws Exception{
//如果登录不成功
String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
//根据shiro返回的异常类路径判断,抛出异常
if(exceptionClassName!=null){
if(UnknownAccountException.class.getName().equals(exceptionClassName)){
throw new CustomException("账号不存在");
}else if(IncorrectCredentialsException.class.getName().equals(
exceptionClassName)){
throw new CustomException("用户名或密码错误");
}else if("randomCodeError".equals(exceptionClassName)){
throw new CustomException("验证码错误");
}else{
throw new Exception();
}
}
//登录失败跳进这个页面
return "login";
}
4.2登录成功跳转到首页
@Controller
public class MainController {
/**
* 跳转到首页
* @param model
* @return
*/
@RequestMapping("index.do")
public String goIndex(Model model){
//从shiro的session中获取ActiveUser
Subject subject = SecurityUtils.getSubject();
//获取身份信息
ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
//通过model传到页面中
model.addAttribute("activeUser", activeUser);
return "/first";
}
}
六、退出
/logout.action = logout