四大基石:密码学 会话管理 登陆 授权
目录
3.applicationContext-shiro.xml的配置
1.Shiro集成Spring导包
<!-- shiro的支持包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.4.0</version>
<type>pom</type>
</dependency>
<!-- shiro与Spring的集成包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
2.Web.xml的配置
<!-- Spring与shiro集成:需要定义一个shiro过滤器(这是一个代理过滤器,它会到spring的配置中找一个名称相同的真实过滤器) -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3.applicationContext-shiro.xml的配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- 1.配置apache的管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 配置一个realm,到数据库中获取权限数据 -->
<property name="realm" ref="jpaRealm"/>
</bean>
<!-- 2.我们可以自定义一个realm【暂时未实现功能】这个必需实现org.apache.shiro.realm.Realm接口 -->
<bean id="jpaRealm" class="cn.itsource.wy.shiro.realm.JpaRealm">
<!--加密-->
<property name="credentialsMatcher">
<!--设置加密匹配方案-->
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!-- 编码的方式使用:md5 -->
<property name="hashAlgorithmName" value="MD5"/>
<!-- 编码的次数:10 -->
<property name="hashIterations" value="10"/>
</bean>
</property>
</bean>
<!-- 3.lifecycleBeanPostProcessor:可以自动调用在Spring Ioc窗口中 Shiro bean的生成周期方法 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 4.启动ioc容器中使用 shiro的注解,但是必需配置在Spring Ioc容器中Shiro bean的生成周期方法 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!-- 5.shiro的真实过滤器(注:这个名称必需和web.xml的代表过滤器【DelegatingFilterProxy】名称一样) -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!-- 登录的url,如果没有登录,你访问的路径会跳到这个页面 -->
<property name="loginUrl" value="/login"/>
<!-- 登录成功的url,如果登录成功,会跳转到这个页面 -->
<property name="successUrl" value="/main"/>
<!-- 没有权限时跳转到这个位置 -->
<property name="unauthorizedUrl" value="/s/unauthorized.jsp"/>
<!--
配置哪些资源被保护,哪些资源需要权限
anon:不需要登录也可以访问相应的权限
authc:需要权限才能访问
/** :所有文件及其子文件
-->
<!-- <property name="filterChainDefinitions">
<value>
/s/login.jsp = anon
/** = authc
</value>
</property>-->
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>
</bean>
<!-- 这个bean是帮助咱们获取相应的值:它会到一个工厂bean中通过对应的方法拿到相应的值 -->
<bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapBuilder" factory-method="createFilterChainDefinitionMap"></bean>
<!-- 配置可以创建 -->
<bean id="filterChainDefinitionMapBuilder" class="cn.itsource.wy.shiro.realm.FilterChainDefinitionMapBuilder"></bean>
</beans>
4.创建JpaRealm
public class JpaRealm extends AuthenticatingRealm{
@Autowired
private IEmployeeService employeeService;
//AuthenticationInfo:认证; 身份验证; 证明
//登录的时候就会调用这个方法来做验证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//身份认证(用户名)
// 1.拿到用户名
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)token;
String username = usernamePasswordToken.getUsername();
// 2.根据用户名到数据库拿到用户
Employee loginUser = employeeService.findByUsername(username);
if(loginUser==null){
return null;
//该用户名不存在
}
//从数据库中拿到密码
Object credentials = loginUser.getPassword();
//加盐的数据
ByteSource byteSource = ByteSource.Util.bytes("itsource");
return new SimpleAuthenticationInfo(loginUser,credentials,byteSource,getName());
}
}
5.Spring配置文件中引入shiro的配置文件
<!-- 引入shiro的配置文件-->
<import resource="applicationContext-shiro.xml"/>
6.密码设置
1.准备一个加密算法
public class MD5Util {
// String algorithmName, Object source, Object salt, int hashIterations
//设置盐值
public static final String SALT = "itsource";
//设置遍历次数
public static final int HASHITERATIONS = 10;
//传入一个字符串,返回一个md5编码的值
public static String createMd5Str(String str){
SimpleHash hash = new SimpleHash("MD5",str,SALT,HASHITERATIONS);
return hash.toString();
}
}
2.通过算法加密数据库密码
public class MD5Test extends BaseTest {
@Autowired
private IEmployeeService employeeService;
//密码加密
@Test
public void testUpdatePwd() throws Exception{
List<Employee> list = employeeService.findAll();
for (Employee employee : list) {
/* //密码初始化(与用户名一致)
employee.setPassword((employee.getUsername()));*/
employee.setPassword(MD5Util.createMd5Str(employee.getPassword()));
employeeService.save(employee); //注:save是添加与修改
}
}
}
3.添加员工时密码加密
EmployeeServiceImpl
@Override
public void save(Employee employee) {
//新增保存修改
if(employee.getId() == null){
employee.setPassword(MD5Util.createMd5Str(employee.getPassword()));
}
super.save(employee);
}
6.准备登陆页面
-
login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>源码智销系统</title>
<%@ include file="/WEB-INF/views/head.jsp"%>
<script type="text/javascript">
//登录页面处理过期问题
if(top != window){
top.location.href = "/login";
}
//登录代码
function submitForm(){
//提交表单
$("#loginForm").form('submit',{
url:"/login",
onSubmit:function(){
//提交之前的验证 验证成功在提交
return $(this).form('validate');
},
success:function(data){
//{success:true;msg:'信息'}
var result = $.parseJSON(data);
if(result.success){
//跳转到成功页面 js跳转页面
location.href = '/main';
}else{
//打印错误信息 提示给用户看
$.messager.show({
title:'温馨提示',
msg:'登录问题:'+result.msg,
timeout:5000,
showType:'slide'
});
}
}
})
}
//回车登录
$(function(){
//绑定事件
$(document.documentElement).on("keyup",function(event){
//event对象
var keyCode = event.keyCode;
if(keyCode === 13){
submitForm();
}
});
});
/*背景样式*/
</script>
<%-- <style type="text/css">
body {
background: linear-gradient(to left, powderblue, pink);
}
</style>--%>
</head>
<body>
<video src="media/ww.MP3" autoplay loop controls hidden></video>
<div align="center" style="margin-top: 100px;">
<div class="easyui-panel" title="智销系统用户登陆" style="width: 400px; height: 350px;">
<form id="loginForm" class="easyui-form" method="post">
<table align="center" style="margin-top: 15px;">
<tr height="20">
<td>用户名:</td>
</tr>
<tr height="10">
<td><input name="username" class="easyui-validatebox" required="true" value="admin" /></td>
</tr>
<tr height="20">
<td>密 码:</td>
</tr>
<tr height="10">
<td><input name="password" type="password" class="easyui-validatebox" required="true" value="0" /></td>
</tr>
<tr height="40">
<td align="center"><a href="javascript:" class="easyui-linkbutton" onclick="submitForm();">登陆</a> <a
href="javascript:;" class="easyui-linkbutton" onclick="resetForm();">重置</a></td>
</tr>
</table>
<%--验证码--%>
<div align="center">
验证码:<input type="text" name="code" style="margin-top: 30px" > <br>
<img src="/kaptcha" onclick="img(this)" width="150x" style="margin-top: 10px"><br>
<input type="button" value="看不清? 换一张" id="btn" style="margin-top: 5px">
</div>
</form>
</div>
</div>
</body>
<%--验证码点击切换下一张--%>
<script type="text/javascript">
document.getElementById("btn").onclick = function () {
document.getElementsByTagName("img")[0].src="/kaptcha?id="+Math.random();
}
</script>
</html>
2.LoginController跳转
@Controller
public class LoginController {
//用于完成跳转
@RequestMapping(value="/login",method = RequestMethod.GET)
public String index(){
return "login";
}
//用于完成登录
@RequestMapping(value="/login",method = RequestMethod.POST)
@ResponseBody
public JsonResult login(String username, String password, String code, HttpServletRequest req, HttpServletResponse resp){
/*验证码验证*/
/**
* 获取当前的 Subject. 调用 SecurityUtils.getSubject();
* 测试当前的用户是否已经被认证. 即是否已经登录. 调用 Subject 的 isAuthenticated()
* 若没有被认证, 则把用户名和密码封装为 UsernamePasswordToken 对象
* 执行登录: 调用 Subject 的 login(AuthenticationToken) 方法.
*/
String yzm = req.getParameter("code");
// 验证码未输入
if (yzm == null || "".equals(yzm)) {
// 抛出自定义异常(继承AuthenticationException), Shiro会捕获AuthenticationException异常
// 发现该异常时认为登录失败,执行登录失败逻辑,登录失败页中可以判断如果是CaptchaEmptyException时为验证码为空
return new JsonResult(false,"请输入验证码");
}
// 获取SESSION中的验证码
// Kaptcha在生成验证码时会将验证码放入SESSION中
// 默认KEY为KAPTCHA_SESSION_KEY, 可以在Web.xml中配置
String sessionCaptcha = (String) SecurityUtils.getSubject().getSession().getAttribute("KAPTCHA_SESSION_KEY");
// 比较登录输入的验证码和SESSION保存的验证码是否一致
if (!yzm.equals(sessionCaptcha)) {
// 抛出自定义异常(继承AuthenticationException), Shiro会捕获AuthenticationException异常
// 发现该异常时认为登录失败,执行登录失败逻辑,登录失败页中可以判断如果是CaptchaEmptyException时为验证码错误
return new JsonResult(false,"验证码错误");
}
//1.拿到访问的主体(当前登录用户)
Subject subject = SecurityUtils.getSubject();
//2.判断这个用户是否已经登录(通过验证)
if(!subject.isAuthenticated()){
//3.如果没有验证,就要完成登录
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
try{
//4.根据toke完成登录功能
subject.login(token);
}catch (UnknownAccountException e){
System.out.println("用户名不存在!!");
e.printStackTrace();
return new JsonResult(false,"账号或者密码出错!");
}catch (IncorrectCredentialsException e){
System.out.println("密码不存在!");
e.printStackTrace();
return new JsonResult(false,"账号或者密码出错!");
}catch (AuthenticationException e){
System.out.println("登录出错!");
e.printStackTrace();
return new JsonResult(false,"程序发生未知错误!");
}
}
return new JsonResult();
}
//登出方法
@RequestMapping("/logout")
public String logout(){
//登出
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/login";
}
}
3.静态资源放行
public class FilterChainDefinitionMapBuilder {
public Map<String,String> createFilterChainDefinitionMap(){
Map<String, String> filterChainDefinitionMap = new LinkedHashMap();
//注:对于一些不登录也可以放行的设置(大家可以根据实际情况添加)
filterChainDefinitionMap.put("/login","anon");
filterChainDefinitionMap.put("*.js","anon");
filterChainDefinitionMap.put("*.css","anon");
filterChainDefinitionMap.put("/css/**","anon");
filterChainDefinitionMap.put("/js/**","anon");
filterChainDefinitionMap.put("/easyui/**","anon");
filterChainDefinitionMap.put("/images/**","anon");
filterChainDefinitionMap.put("/media/**","anon");
filterChainDefinitionMap.put("/kaptcha","anon");
//这个值之后从数据库中查询到【用户-角色-权限-资源】
//filterChainDefinitionMap.put("/s/permission.jsp","perms[user:*]");
//filterChainDefinitionMap.put("/s/employee.jsp","perms[employee:*]");
filterChainDefinitionMap.put("/**","authc");
return filterChainDefinitionMap;
}
}
8.完成登陆功能
1.完善Service层
//根据用户名查询用户
public Employee findByUsername(String username);
@Override
public Employee findByUsername(String username) {
return employeeRepository.findByUsername(username);
}
2.JsonResult返回数据的封装
//返回数据的封装
public class JsonResult {
private boolean success = true;
private String msg ;
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public JsonResult(){}
public JsonResult(boolean success,String msg){
this.success = success;
this.msg = msg;
}
}
9.扩展.
1.回车登陆
//回车登录
$(function(){
//绑定事件
$(document.documentElement).on("keyup",function(event){
//event对象
var keyCode = event.keyCode;
if(keyCode === 13){
submitForm();
}
});
});
2.Main.jsp在资源中可以找到标签配置
方案一:
<div style="float:right" >
欢迎
<span style="color:#ff5766">
<shiro:user>
<shiro:principal property="name"/>
</shiro:user>
</span>
登录,<a href="/logout">退出</a>
</div>
方案二:
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
...
<shiro:user>
欢迎[ <shiro:principal property="username" />]登录,
<a href="${pageContext.request.contextPath}/logout">退出</a>
</shiro:user>
3.登陆过期
//登出方法
@RequestMapping("/logout")
public String logout(){
//登出
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/login";
}
//登录页面处理过期问题
if(top != window){
top.location.href = "/login";
}
4.Shiro的配置问题
<!--设置加密匹配方案-->
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">