一、项目说明
项目环境:jdk1.7+tomcat7+idea2018+maven+shiro1.3.2
源代码github地址:https://github.com/tmAlj/shiro/tree/master/ssms
实现目标:完成从前台到控制器,以及自定义realm和logout过滤器的应用等实现整个认证过程
综合实例:基于shiro的按钮级别的权限管理系统
二、认证流程
三、常用异常
授权异常:
(1)UnauthenticatedException :授权异常
(2)HostUnauthorizedException:没有访问权限
(3)HostUnauthorizedException:没有访问权限
(4)AuthorizationException:授权异常父类
认证异常:
(1)UnsupportedTokenException:身份令牌异常,不支持的身份令牌
(2)UnknownAccountException:未知账户/没找到帐号,登录失败
(3)LockedAccountException:帐号锁定
(4)DisabledAccountException:账户禁用
(5)ConcurrentAccessException:用户多次登录异常
(6)AccountException:账户异常
(7)ExpiredCredentialsException:过期的凭据异常
(8)IncorrectCredentialsException:错误的凭据异常
(9)CredentialsException: 凭据异常
(10)AuthenticationException:认证的父类
四、Realm类继承关系
五、认证流程实现
注:此节的代码实现是基于shiro学习笔记三:shiro与spring集成实现
(1)创建登录页面login.jsp
注:使用了bootstrap进行了样式的简单美化,action="shiro/login"地址需要在spring-shiro-config.xml的过滤器中配置/login.jsp=anon
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>登录</title>
<link rel="stylesheet" type="text/css" href="statics/boostrap/css/bootstrap.css"/>
<style>
.tm-container{
width: 600px;
margin: 20px auto;
}
</style>
</head>
<body>
<div class="tm-container">
<form class="form-horizontal" role="form" action="shiro/login" method="post">
<div class="form-group">
<label for="firstname" class="col-sm-2 control-label">用户姓名</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="firstname" name="userName" placeholder="请输入姓名">
</div>
</div>
<div class="form-group">
<label for="lastname" class="col-sm-2 control-label">用户密码</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="lastname" name="password" placeholder="请输入密码">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox">请记住我
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-danger">登录</button>
</div>
</div>
</form>
</div>
<script type="text/javascript" src="statics/jquery/jquery-3.2.1.js"></script>
<script type="text/javascript" src="statics/boostrap/js/bootstrap.js"></script>
</body>
</html>
(2)配置登录cotroller
package com.wsd.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* Created by tm on 2018/8/17.
* shiro认证Controller
*/
@Controller
@RequestMapping("shiro")
public class ShiroController {
private static final transient Logger log = LoggerFactory.getLogger(ShiroController.class);
/**
* 登录
* @param userName 用户名称
* @param password 用户密码
* @return
*/
@RequestMapping("/login")
public String login(@RequestParam("userName") String userName, @RequestParam("password") String password){
// 得到一个Subject对象,该对象为访问系统任意用户
Subject currentUser = SecurityUtils.getSubject();
// 通过Subject对象的isAuthenticated判断该用户是否已经验证
if (!currentUser.isAuthenticated()) {
// 如果该用户未验证,得到用户的账号和密码,使用UsernamePasswordToken的方式生成一个该用户的token凭证
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
// 开启记住我的功能,这里可以通过获取用户的提交的信息,判断是否选择记住我来决定开启或关闭
token.setRememberMe(true);
try {
// 通过Subject对象的login来验证用户的身份
currentUser.login(token);
// 如果用户身份验证不通过会抛出相应的异常,可以通过抛出的异常来设置返回给前台的信息
} catch (UnknownAccountException uae) {
// UnknownAccountException账号不存在异常
log.info("===============账号不存在异常=>"+token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
// IncorrectCredentialsException密码错误异常
log.info("===============密码错误异常=>"+token.getPrincipal());
} catch (LockedAccountException lae) {
// LockedAccountException账户被锁定异常
log.info("===============账户被锁定异常=>"+token.getPrincipal());
} catch (AuthenticationException ae) {
// AuthenticationException即验证不通过异常,为前面几个异常的父类
}
}
//认证成功后跳转,需配置过滤器
return "redirect:/welcom.jsp";
}
}
(3)自定义Realm
package com.wsd.shiro;
import org.apache.shiro.authc.*;
import org.apache.shiro.realm.AuthenticatingRealm;
/**
* Created by tm on 2018/8/17.
* 用户自定义认证Realm
*/
public class JdbcRealm extends AuthenticatingRealm {
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//token类型转化
UsernamePasswordToken upToken = (UsernamePasswordToken)authenticationToken;
//获取前台用户信息
String username = upToken.getUsername();
// TODO 从数据库中获取用户信息,这里先模拟从数据库获取数据
// 使用数据库中的用户信息与前台传入的用户信息比对,如果用户不存在,抛出异常
if("wsd".equals(username)) {
throw new UnknownAccountException("==================用户不存在");
}
// 用户被锁定是,抛出异常
if("wsd1".equals(username)) {
throw new LockedAccountException("==================用户被锁定");
}
// TODO 从数据库中获取用户信息,这里先模拟从数据库获取数据
Object principal = username; //从数据中获取用户名称
Object credentials = "123456"; //从数据中获取用户密码
String realmName = "jdbcRealm"; //自定义
return new SimpleAuthenticationInfo(principal, credentials, realmName);
}
}
(4)在spring-shiro-config.xml配置Realm以及过滤器
<!-- 配置shiro的核心securityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager" ref="cacheManager"/>
<!--<property name="sessionMode" value="native"/>-->
<property name="realm" ref="jdbcRealm"/>
</bean>
<!-- 配置shiro的重要的元素Reaml(验证的数据源),可自定义 -->
<bean id="jdbcRealm" class="com.wsd.shiro.JdbcRealm">
</bean>
<property name="filterChainDefinitions">
<!-- 静态资源需要设置为anon,否则找不到 -->
<value>
/statics/** = anon
/login.jsp = anon
/welcom.jsp = user
/logout = logout
</value>
</property>
(5)退出登录
说明:需要在退出登录的页面添加<a>标签,指定到退出过滤器匹配的url,同时在shiro配置文件中配置退出过滤器即可。如果不退出登录,返回到login.jsp后输入错误的账户密码仍能访问登录成功的页面。
(6)测试举例
说明:
(1)由于还没有使用数据库持久化,测试用户的密码在自定义的realm定义为123456,如用wsd和wsd1登录则会在控制台打印出相应的异常
(2)如果登录成功后进入welcom.jsp,没有点击退出登录按钮,返回到login.jsp输入错误的密码后点击登录仍然可以登录