1.基础介绍以及demo演示网址:
2.Spring集成Shiro【大部分情况下我们使用Shiro都会和Spring集成的】
- 把Shiro中的重要对象交给Spring管理
2.1导包(之前文档中已经导过)
<!-- 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.2 web.xml中添加Shiro过滤器("/*" 你的每一次请求都会经过过滤器) --- 直接拷贝即可【固定写法】
- 他是没有任何功能的,他是一个代理。(他不做工作,把工作委托给别人去做)
注:名字必须和真实过滤器的名字一样
shiro-root-1.4.0-RC2\samples\spring\src\main\webapp\WEB-INF\web.xml
<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>
2.3 新建application-shiro.xml【在Shiro官方文档中有Spring集成好的改改,也可直接复制我这个改好的】
<?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">
<!--
配置权限的核心管理器
-->
<!--拿到SecurityManager对象 DefaultSecurityManager-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--把Realm放到SecurityManager对象中去-->
<property name="realm" ref="jdbcRealm"/>
</bean>
<!--
这里要放一个自定义Realm
-->
<!--class 就是你的自定义Realm的完全限定名-->
<bean id="jdbcRealm" class="cn.zx.aisell.web.shiro.AisellShiro">
<property name="name" value="jdbcRealm"/>
<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>
<!--
这个暂时不用但是还是要留着 以防以后看到要用到就可以回来看这个笔记了
这三个bean,就是支持权限注解判断的。 我们这里不用注解的形式, 如果要用的话要加这个配置
-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<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>
<bean id="secureRemoteInvocationExecutor" class="org.apache.shiro.spring.remoting.SecureRemoteInvocationExecutor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!--
shiro真实的过滤器,就是通过这个来完成拦截功能
注:这里的id一定要和web.xml里面的代理过滤器id一样 不然的话就完成不了拦截功能
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!--如果没有登录,就会调到这个路径(一般放登录页面)login[登录]-->
<property name="loginUrl" value="/WEB-INF/views/login"/>
<!--登录成功进入的路径(一般放主页面)-->
<property name="successUrl" value="/WEB-INF/views/index"/>
<!--没有权限的时候进入的页面(一般专门写一个提示没有权限的页面)-->
<property name="unauthorizedUrl" value="/WEB-INF/views/unauthorized"/>
<!--
路径 = anon : 不需要登录也可以访问的路径
路径 = perms[user:*] :你必需要有相应的权限才能访问对应的路径
/** = authc : 需要登录才能访问
-->
<!--这个顺序是一定要注意的!顺序不对是会出问题的。按上面注解排序-->
<property name="filterChainDefinitions">
<value>
/favicon.ico = anon
/logo.png = anon
/shiro.css = anon
/s/login = anon
/*.jar = anon
/** = authc
</value>
</property>
</bean>
</beans>
2.3.1 在application.xml中引入Spring集成shiro的xml
<!--引入shiro.xml【Spring集成Shiro的xml】-->
<import resource="classpath:applicationContext-shiro.xml"/>
2.4 自定义Realm(他是抽象的 要继承Aut 必须要实现两个功能(授权/认证))
/**
* 自定义Realm --- 获取数据
*/
public class AisellShiro extends AuthorizingRealm{//继承这个类会会实现两个方法,一个授权/一个登陆验证
//授权功能
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//1.拿到主体(登录的时候传过来
String username = (String) principalCollection.getPrimaryPrincipal();
//2.根据主体拿到数据库的角色和权限 。(现在在下面拿 因为源码是要返回Set)
Set<String> roles = this.getRoles(username);
Set<String> perms = this.getPerms(username);
//3.创建AuthenticationInfo对象(把角色和权限都放进去)
//这里注意:一定要选SimpleAuthorizationInfo。。。差点就选错了选成SimpleAuthenticationInfo
//一些快了 就不注意了 要小心
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(roles); // 角色
authorizationInfo.setStringPermissions(perms); //权限
return authorizationInfo;
}
//这里提供给上面角色和权限的数据。 以后是没用的
public Set<String> getRoles(String username){//roles角色
Set<String> roles = new HashSet<String>();
roles.add("admin");
roles.add("nimda");
return roles;
}
//权限
public Set<String> getPerms(String username){
Set<String> perms = new HashSet<>();
//perms.add("*");//这就是代表所有权限
perms.add("employee:*");//这就代表员工里面的所有权限
return perms;
}
//登陆认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.要强转用户名密码令牌
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//2.拿到用户名
String username = token.getUsername();
//3.通过用户名获得密码
String password = this.login(username);
//4.如果密码为空,就代表用户不存在 ; 返回空
if (password == null){
return null;
}
//5.准备盐值
ByteSource salt = ByteSource.Util.bytes("zx");//salt --- 盐
//6.SimpleAuthenticationInfo是Shiro准备的一个对象(当前用户名称,密码,realm的名称)
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,password,salt,getName());
return authenticationInfo;
}
/**
* 因为学习是有方法一步一步来的 所以目前这里目前咋先吧数据写死,后续会从数据库提取
* 123+迭代10次+盐值(itsource):d5a3fedf6c59c2ecbe7f7a6c1a22da37
*
* 这里先将上面的数据密码传到上面,使他们能查到密码
*/
public String login(String username) {
if ("admin".equals(username)){
return "d5a3fedf6c59c2ecbe7f7a6c1a22da37";
}else if ("xu".equals(username)){
return "123";
}
return null;
}
}
2.4.1 自定义完Realm之后把完全限定名拷贝到application-shiro.xml中
上面代码已经把我自己的自定义Realm的完全限定名搞上去了。以后大家注意!
2.5 .测试
3. 登录页面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>梦幻登录</title>
<style type="text/css">
* {
margin: 0;
padding: 0;
list-style: none;
}
body {
overflow: hidden;
}
#bg_wrap {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
overflow: hidden;
}
#bg_wrap div {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
opacity: 0;
/* 设置透明度 */
transition: opacity 3s;
}
/* nth-of-type(1) *筛选选择器选择第一个*/
#bg_wrap div:nth-of-type(1) {
opacity: 1;
}
#Login {
width: 272px;
height: 300px;
margin: 200px auto;
}
#Login .move {
position: absolute;
top: -100px;
z-index: 999;
}
#Login h3 {
width: 270px;
font-size: 30px;
font-weight: 700;
color: #fff;
font-family: '微软雅黑';
text-align: center;
margin-bottom: 30px;
cursor: move;
}
#Login input.text {
width: 270px;
height: 42px;
color: #fff;
background: rgba(45, 45, 45, 0.15);
border-radius: 6px;
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 1.0) inset;
text-indent: 10px;
}
#Login input.btn {
/* top: 280px; */
background: #ef4300;
width: 272px;
height: 44px;
border-radius: 6px;
color: #fff;
box-shadow: 0 15px 30px 0 rgba(255, 255, 255, 0.25) inset, 0 2px 7px 0 rgba(0, 0, 0, 0.2);
border: 0;
text-align: center;
}
input::-webkit-input-placeholder {
color: #fff;
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
</style>
<%@ include file="/WEB-INF/views/head.jsp"%>
<script>
// 检查自己是否是顶级页面 /login
if (top != window) {// 如果不是顶级
//把子页面的地址,赋值给顶级页面显示
window.top.location.href = window.location.href;
}
//var url = "/login";
//就是点击登录后成功跳转的页面
function submitForm(){
// alert("ss");
// loginForm 登录表单
$('#loginForm').form('submit', {
url:"/login",
//url:url,
onSubmit: function(){
return $(this).form('validate');
},
success:function(data){
//把一个json字符串转成一个json对象
var result = JSON.parse(data);
//登录成功跳到主页面,登录失败给出提示
if(result.success){
//js的跳转(BOM:浏览器对象模型)
window.location.href = "/main";
}else{
$.messager.alert('警告',result.msg,"info");
}
}
});
}
//回车登录
$(document.documentElement).on("keyup", function(event) {
//console.debug(event.keyCode);
var keyCode = event.keyCode;
console.debug(keyCode);
if (keyCode === 13) { // 捕获回车
submitForm(); // 提交表单
}
});
</script>
</head>
<body>
<%--图片--%>
<div id="bg_wrap">
<div><img src="/images/head/1.jpg" width="100%" height="100%"></div>
<div><img src="/images/head/2.jpg" width="100%" height="100%"></div>
<div><img src="/images/head/3.jpg" width="100%" height="100%"></div>
<div><img src="/images/head/4.jpg" width="100%" height="100%"></div>
<div><img src="/images/head/5.jpg" width="100%" height="100%"></div>
<div><img src="/images/head/6.jpg" width="100%" height="100%"></div>
<div><img src="/images/head/8.jpg" width="100%" height="100%"></div>
<div><img src="/images/head/9.jpg" width="100%" height="100%"></div>
<div><img src="/images/head/10.jpg" width="100%" height="100%"></div>
<div><img src="/images/head/11.jpg" width="100%" height="100%"></div>
<div><img src="/images/head/12.jpg" width="100%" height="100%"></div>
<div><img src="/images/head/13.jpg" width="100%" height="100%"></div>
</div>
<div id="Login">
<h3 id="title" class="move">來啦 小老弟~~</h3>
<form id="loginForm" method="post" action="/#">
<input type="text" placeholder="账号" class="text move" name="username" id="username">
<input type="password" placeholder="密码" class="text move" name="password" id="password">
<input type="button" value="走 妳" class="btn move" onclick="submitForm()">
</form>
</div>
<script type="text/javascript">
/*背景渐变*/
/*function(){} 匿名函数
()() IIFE匿名函数立刻执行,函数自执行体*/
//主要是图片变浅等
(function() {
var timer = null; //声明定时器
var oImg = document.querySelectorAll('#bg_wrap div') //h5最新元素获取写法获取到的是一组元素
//querySelector获取单个元素的 兼容ie8
var len = oImg.length; //3
var index = 0;
timer = setInterval(function() {
oImg[index].style.opacity = 0;
index++;
// if(index>=3){
// index=0;
// }
index %= len; //index=index%len求模取余 0%3=0; 1%3=1; 2%3=2; 3%3=0;
oImg[index].style.opacity = 1;
}, 3000);
})();
// 重力模拟弹跳系统
(function() {
/*
改变定位元素的top值
达到指定位置之后进行弹跳一次
多个元素一次运动
动画序列*/
var oMove = document.querySelectorAll('.move');
var oLen = oMove.length;
var timer = null;
var timeout = null;
var speed = 3; //移动距离
move(oLen - 1);
function move(index) {
if (index < 0) {
clearInterval(timer); //清除循环定时器
clearTimeout(timeout); //清除延时定时器
return; //终止函数
}
var endTop = 150 + (index * 60); //根据下标计算endTop值
timer = setInterval(function() {
speed += 3;
var T = oMove[index].offsetTop + speed; //设置每一次的top值
if (T > endTop) {
T = endTop;
speed *= -1 //取反,让移动距离变为负数
speed *= 0.4;
//慢慢停下来
}
oMove[index].style.top = T + 'px';
}, 20);
timeout = setTimeout(function() {
clearInterval(timer);
index--;
console.log(9);
move(index);
console.log(index);
}, 900) //过900毫秒之后再执行方法里的代码
}
})()
</script>
</body>
</html>
3.1 在Controller中创建LoginController
package cn.zx.aisell.web.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class LoginController extends BaseController{
@RequestMapping("/login")
public String login(String username,String password){
//拿到当前用户
Subject subject = SecurityUtils.getSubject();
//如果没有登录让他登录
//获得令牌 ---- 判断
//④.如果没有登录,让他登录(需要令牌)
if(!subject.isAuthenticated()){
try {
//用户名密码令牌
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
//登录功能
subject.login(token);
} catch (UnknownAccountException e) {//Unknown(未知)Account(账号)Exception
//就是用户名错误
e.printStackTrace();
System.out.println("账号或者密码出现错误");
} catch (IncorrectCredentialsException e) {//Incorrect(不正确的)Credentials(凭证;证书)Exception
//就是密码错误
e.printStackTrace();
System.out.println("账号或者密码出现错误!");
}catch (AuthenticationException e){
//就是其他错误
e.printStackTrace();
System.out.println("出现一个神迷的错误!!!!");
}
}
//重定向
return "redirect:/main";
}
}
3.2 登录方法要放行 在【PermissionMapFactory】
permissionMap.put("/login","anon" );
permissionMap.put("/login.jsp","anon" );
//在这里把你所有需要放行放进去
permissionMap.put("*.js","anon");
permissionMap.put("*.css","anon");
permissionMap.put("/css/**","anon");
permissionMap.put("/js/**","anon");
permissionMap.put("/easyui/**","anon");
permissionMap.put("/images/**","anon");
applicationController-shiro.xml
3.3 测试
4.权限
- 目前是通过登录页面进来之后,是没有权限的,只要是登录进来,是可以为所欲为的。还没有达到需求(只能看员工或者这能看部门等)
4.1这个权限也是在applicationController-shiro.xml中
4.2.把拦截权限配置从xml中移到后台去
- 我们现在是写死在xml的肯定是不行的,项目中那么多,怎么写。以后肯定是要从数据库获取的。上面目前都是假数据。所以最好在java代码中设置这种描述。
4.2.1创建Map的工厂:PermissionMapFactory
注:里面的顺序 是不可以乱改的,是有先后循序的
public class PermissionMapFactory {
//搞一个方法 返回Map
private Map<String,String> createPermissions(){
//因为这里我们权限是有顺序的,无序会出错的,所以我们用LinkedHashMap;
Map<String,String> PermissionMap = new LinkedHashMap<>();
//anon ,不要登录就能访问
PermissionMap.put("/s","anon" );
//需要对应的权限才可以访问
PermissionMap.put("/WEB-INF/views/department.jsp","perms[department:index]" );
//其他拦截 【拦截所有-- /**】都需要登录才可访问
PermissionMap.put("/**","authc");
return PermissionMap;
}
}
4.2.2 然后在applicationController-shiro.xml修改一下
- 用工厂创建bean,在用这个bean创建
<!--
shiro真实的过滤器,就是通过这个来完成拦截功能
注:这里的id一定要和web.xml里面的代理过滤器id一样 不然的话就完成不了拦截功能
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--这里需要Map-->
<property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"/>
</bean>
<!--配置一个bean,它是从工厂的方法中返回的对象-->
<bean id="filterChainDefinitionMap" factory-bean="permissionMapFactory" factory-method="createPermissions"/>
<!--配置我们刚刚写的工厂【PermissionMapFactory】 -->
<bean id="permissionMapFactory" class="cn.zx.aisell.web.shiro.PermissionMapFactory"/>
注:修改之后记得重启!!!