shiro+spring+springmvc 整合
- shiro+spring+springmvc 整合
- 创建一个工程
- 导入项目maven依赖包:
- web.xml中配置shiro过滤器
- spring-shiro.xml配置
- 认证登录
- 登录原理
- 登录页面
- 登录代码的实现
- 认证拦截过滤器
- 测试认证代码
- 认证信息在页面显示
- 认证成功以后的首页
- 退出登录
- 使用LogoutFilter
- 自定义退出登录跳转的页面
- shiro的过虑器
- 设置凭证匹配器
- 授权
- 在自定义Realm中授权
- 使用PermissionsAuthorizationFilter 方式
- 创建/unauthorized.jsp
- 问题总结
- 支持注解配置授权
- 在controller方法中添加注解
- jsp标签 授权
- 标签介绍
- index.jsp页面标签控制权限
- 授权测试
- shiro缓存
- 缓存流程
- 使用ehcache
- 添加Ehcache的依赖包
- 配置cacheManager
- shiro-ehcache.xml
- Session会话管理器
- 记住我
- 用户身份实现java.io.Serializable接口
- 配置rememeberMeManager
- 登陆页面
- 自定义FormAuthenticationFilter
- 配置rememberMe的input名称
- 测试
- 使用UserFilter
- 总集成代码
shiro+spring+springmvc 整合
一般我们企业开发都是用ssm框架来完成,shiro框架有专门对spring集成的实现。
创建一个工程
导入项目maven依赖包:
4.0.0
cn.zj
spring-shiro
0.0.1-SNAPSHOT
war
4.3.2.RELEASE
1.6.6
1.2.17
1.2.3
org.aspectj
aspectjweaver
1.7.4
org.springframework
spring-webmvc
${spring.version}
org.springframework
spring-jdbc
${spring.version}
org.springframework
spring-tx
${spring.version}
log4j
log4j
${log4j.version}
org.slf4j
slf4j-api
${slf4j.version}
org.slf4j
slf4j-log4j12
${slf4j.version}
com.alibaba
druid
1.1.10
org.mybatis
mybatis
3.4.4
org.mybatis
mybatis-spring
1.3.2
javax.servlet
javax.servlet-api
3.1.0
provided
javax.servlet.jsp
javax.servlet.jsp-api
2.2.1
provided
org.apache.shiro
shiro-core
${shiro.version}
org.apache.shiro
shiro-web
${shiro.version}
org.apache.shiro
shiro-spring
${shiro.version}
org.apache.shiro
shiro-quartz
${shiro.version}
org.apache.shiro
shiro-spring
${shiro.version}
mysql
mysql-connector-java
5.1.26
org.apache.maven.plugins
maven-surefire-plugin
2.22.1
true
web.xml中配置shiro过滤器
在web系统中,shiro也通过filter进行拦截。filter拦截后将操作权交给spring中配置的filterChainDefitions
shiro提供很多filter。
shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
targetFilterLifecycle
true
targetBeanName
shiroFilter
shiroFilter
/*
spring-shiro.xml配置
在spring-shiro.xml 中配置web.xml中fitler对应spring容器中的bean。
/images/**=anon
/js/**=anon
/css/**=anon
/login.jsp = anon
/logout.do=logout
/** = authc
认证登录
登录原理
用户点击登录提交到 的 /user/login.do 匹配到shiro的 /**
使用的表单认证过滤器 FormAuthenticationFilter ,此过滤器内部会获取当前用户提交的表单的
账号和密码(注意:账号表单名称一定叫做username,密码表单名称一定叫password),封装到token令牌中,并且调用自定义的Realm中的doGetAuthenticationInfo
方法,进行认证,
1.如果认证通过,直接跳转到
后台首页
2.如果认证失败,跳转到
并且把认证失败的错误异常类型,设置HttpServletRequest请求对象中,作用域的名称就是
FormAuthenticationFilter 中 shiroLoginFailure
我们可以在/user/login.do中注入一个HttpServletRequest接收这个错误信息
登录页面
由于FormAuthenticationFilter的用户身份和密码的input的默认值(username和password),修改页面的账号和密码 的input的名称为username和password
登录代码的实现
package cn.zj.shiro.controller;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public String login(HttpServletRequest request,Model m) {
//获取认证失败的错误信息,在Shiro框架的 FormAuthenticationFilter 过滤器中共享
// 共享的属性名称 shiroLoginFailure
// 共享的 shiro 异常的字节码 类型
String shiroLoginFailure = (String) request.getAttribute("shiroLoginFailure");
System.out.println("异常类型 :"+shiroLoginFailure);
if(shiroLoginFailure !=null) {
if(UnknownAccountException.class.getName().equals(shiroLoginFailure)) {
m.addAttribute("errorMsg", "亲。账号不存在");
}else if(IncorrectCredentialsException.class.getName().equals(shiroLoginFailure)) {
m.addAttribute("errorMsg", "亲。密码错误");
}
}
return "forward:/login.jsp";
}
}
认证拦截过滤器
在spring-shiro.xml中配置-
红色背景部分
/images/**=anon
/js/**=anon
/css/**=anon
/login.jsp = anon
/logout.do=logout
/** = authc
测试认证代码
登录一个认证过程,在自定义的realm中开发者自己完成认证操作
认证思路
1.根据账号查询数据库对应的用户信息并封装成对象
2.获取用户对象对应的密码,和登录操作提交的用户密码进行(加盐+散列)后就进行比对
3.返回认证对象
(1)如果认证成功,会跳转到成功页面一般是首页
(2)如果认证失败,继续认证
package cn.zj.shiro.realm;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
public class CustomRealm extends AuthorizingRealm {
/**
* 认证方法,开发者自己实现具体认证操作 如果认证成功,返回一个 AuthenticationInfo 封装有认证对信息的对象
*
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 1.获取token的身份信息(账号)--用户输入
String principal = (String) token.getPrincipal();// 当事人(当前登录的账号)
UsernamePasswordToken uToken = (UsernamePasswordToken) token;
// 认证思路
// --1.根据账号从数据库中查询出是否有此账号,有封装成一个对象,没有返回null
// --2.如果有的对象的话,把对象的密码作为品质传递到下面认证对象中进行认证操作
// 模拟从数据库中查询的的(凭证)密码
String hashedCredentials = "662b46d0168aa5353771f45084378881";
String stla = "query";
// 3.创建返回认证信息的对象
ByteSource credentialsSalt = ByteSource.Util.bytes(stla);
//数据库 身份 账号
String principal = "admin";
System.out.println("principals :"+principal);
if("admin".equals(username)) {
//加密后的 认证信息对象
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, hashedCredentials, credentialsSalt, this.getName());
return authenticationInfo;
}
return null;
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
}
认证信息在页面显示
认证后用户菜单在首页显示
认证后用户的信息在页头显示
认证成功以后的首页
一般认证成功以后,都跳转到首页,首页一般显示菜单用户登录信息,和用户操作其他模块相关界面
package cn.zj.shiro.controller;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class IndexController {
@RequestMapping("/index.do")
public String list(HttpServletRequest req, Model model) {
return "index";
}
}
退出登录
使用LogoutFilter
不用我们去实现退出,只要去访问一个退出的url,由LogoutFilter拦截住,清除session。并且默认跳转重定向到项目的根目录 : /
在spring-shiro.xml配置LogoutFilter
/images/**=anon
/js/**=anon
/css/**=anon
/login.jsp = anon
/logout.do=logout
/** = authc
自定义退出登录跳转的页面
Shiro的退出登录过滤器 :logout 默认退出跳转到项目根目录 : / , 但是一般可能我们需要直接跳转到 登录页面 /login.jsp
我们可以在Spring中并重新配置org.apache.shiro.web.filter.authc.LogoutFilter 并且通过属性设置退出登录跳转的默认页面
/lib/**=anon
/static/**=anon
/login.jsp=anon
/logout.do=logout
/** = authc
shiro的过虑器
Shiro针对不同的过滤规则,编写不同的过滤器(内置),开发者可以根据不同开发场景选择配置
| Filter Name | Class |
|---|---|
| anon | org.apache.shiro.web.filter.authc.AnonymousFilter |
| authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
| authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
| logout | org.apache.shiro.web.filter.authc.LogoutFilter |
| noSessionCreation | org.apache.shiro.web.filter.session.NoSessionCreationFilter |
| perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
| port | org.apache.shiro.web.filter.authz.PortFilter |
| rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
| roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
| ssl | org.apache.shiro.web.filter.authz.SslFilter |
| user | org.apache.shiro.web.filter.authc.UserFilter |
1、anon:例子/admins/**=anon 没有参数,表示可以匿名使用。
2、authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,FormAuthenticationFilter是表单认证,没有参数
3、perms:
(1) 例子/admins/user/=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/=perms["user:add:,user:modify:"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
4、user:例如/admins/user/**=user没有参数表示必须存在用户, 身份认证通过或通过记住我
设置凭证匹配器
数据库中存储到的md5的散列值,在realm中需要设置数据库中的散列值它使用散列算法 及散列次数,让shiro进行散列对比时和原始数据库中的散列值使用的算法 一致。
授权
在自定义Realm中授权
授权思路,从当前登录用户信息中获取用户的所有权限,并添加到 SimpleAuthorizationInfo对象中
如果shiro配置的授权表达式匹配到了,授权成功,放行。如果没有匹配,跳转到没有权限访问提示页面
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//授权思路,从当前登录用户信息中获取用户的所有权限,并添加到 SimpleAuthorizationInfo对象中
//如果shiro配置的授权表达式匹配到了,授权成功,放行。如果没有匹配,跳转到没有权限访问提示页面
authorizationInfo.addStringPermission("user:list");
authorizationInfo.addStringPermission("role:list");
authorizationInfo.addStringPermission("permission:list");
return authorizationInfo;
}
使用PermissionsAuthorizationFilter 方式
在spring-shiro.xml中配置url所对应的权限。
1、在spring-shiro.xml中配置filter规则
(1)/user/update.do=perms[user:update]
(2)/user/list.do=perms[user:list]
2、用户在认证通过后,请求/user/list.do
3、被PermissionsAuthorizationFilter拦截,发现需要“user:list”权限
4、PermissionsAuthorizationFilter调用realm中的doGetAuthorizationInfo获取数据库中正确的权限
5、PermissionsAuthorizationFilter对user:list 和从realm中获取权限进行对比,如果“user:list”在realm返回的权限列表中,授权通过。
如果没有权限,就会跳转到一个没有权限访问的提示页面
创建/unauthorized.jsp
如果授权失败,跳转到/unauthorized.jsp 红色背景部分
/images/**=anon
/js/**=anon
/css/**=anon
/login.jsp = anon
/logout.do=logout
/** = authc
问题总结
1、在spring-shiro.xml中配置过虑器链接,需要将全部的url和权限对应起来进行配置,实际开发项目中项目有很多权限配置,非常的麻烦(不建议使用)
(1)解决方案,使用注解配置
2、每次授权都需要调用realm查询数据库,对于系统性能有很大影响,可以通过shiro缓存来解决。
(1)解决方案,使用shiro的缓存技术-ehcache
支持注解配置授权
在springmvc.xml中配置:
使用注解开发,首先在spring配置中开启shiro注解配置
/unauthorized.jsp
在controller方法中添加注解
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/list.do")
@RequiresPermissions("user:list")
public String list(HttpServletRequest req, Model model) {
return "index";
}
@RequestMapping("/update.do")
@RequiresPermissions("user:updae")
public String update(HttpServletRequest req, Model model) {
return "redirect:/user/list.do";
}
@RequestMapping("/delete.do")
@RequiresPermissions("user:delete")
public String delete(HttpServletRequest req, Model model) {
return "redirect:/user/list.do";
}
}
jsp标签 授权
标签介绍
Jsp页面添加:
<%@ tagliburi="http://shiro.apache.org/tags" prefix="shiro" %>
| 标签名称 | 标签条件(均是显示标签内容) |
|---|---|
| shiro:authenticated | 登录之后 |
| shiro:notAuthenticated | 不在登录状态时 |
| shiro:guest | 用户在没有RememberMe时 |
| shiro:user | 用户在RememberMe时 |
| 在有abc或者123角色时 | |
| 拥有角色abc | |
| 没有角色abc | |
| 拥有权限资源abc | |
| 没有abc权限资源 | |
| shiro:principal | 显示用户身份名称 |
| 显示用户身份中的属性值 |
index.jsp页面标签控制权限
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro"%>
Insert title here
首页
退出登录
用户管理
角色管理
权限管理
授权测试
如果当前登录用户没有授权,用户的界面就不会显示对应的菜单
如果用户强制访问某一个没有授权的页面,那么会跳转到没有授权提示页面
shiro缓存
问题:为什么要有缓存?
答:因为认证通过以后访问的页面,每访问一次都要进行一次授权,都会执行一次授权方法,授权相关的权限数据都是从数据库查询的,会造成频繁的查询数据库,影响系统性能
所以:针对上边授权频繁查询数据库,需要使用shiro缓存技术。
缓存流程
shiro中提供了对认证信息和授权信息的缓存。shiro默认是关闭认证信息缓存的,对于授权信息的缓存shiro默认开启的。主要研究授权信息缓存,因为授权的数据量大。
前提:用户认证通过(登录成功)。
用户第一次授权:调用realm查询数据库
用户第二次授权:不调用realm查询数据库,直接从缓存中取出授权信息(权限标识符)。
使用ehcache
Shiro推荐使用 ehcache第三方缓存
添加Ehcache的依赖包
org.apache.shiro
shiro-ehcache
1.2.3
net.sf.ehcache
ehcache-core
2.6.0
配置cacheManager
shiro-ehcache.xml
在 classpath 路径下面创建 ehcache缓存的配置文件
Session会话管理器
和shiro整合后,使用shiro的session管理,shiro提供sessionDao操作 会话数据。
配置sessionManager
记住我
Shiro框架认证成功以后,默认的信息是存储在Session中的,Session的缺点就是如果数据浏览器关闭,Session失效,下次必须再次登录。所以,Shiro提供了记住我的功能,记住我功能底层使用的Cookie技术
用户登陆选择“自动登陆”本次登陆成功会向cookie写身份信息,下次登陆从cookie中取出身份信息实现自动登陆
用户身份实现java.io.Serializable接口
向cookie记录身份信息需要用户身份信息对象实现序列化接口,如下:
public class User implements Serializable{
配置rememeberMeManager
使用记住我功能需要设置Shiro的rememberMeManage 管理器
登陆页面
记住我的表单的name默认是 remeberMe
自定义FormAuthenticationFilter
表单认证 使用的是 org.apache.shiro.web.filter.authc.FormAuthenticationFilter 中对应
那么这个过滤器接受的认证参数都是有规则的
认证的身份(账号)默认叫做 username
认证的凭证(密码)默认叫做 password
认证的记住我 默认叫做 rememberMe
所以,在开发者编写的认证(登录)表单的对应的名称必须和默认保持一致
但是开发者也可以自定这些认证的表单名称规则
但是开发者也可以自定义配置对应的名称
配置rememberMe的input名称
/images/**=anon
/js/**=anon
/css/**=anon
/login.jsp = anon
/logout.do=logout
/index.do=user
/** = authc
测试
自动登陆后,需要查看 cookei是否有rememberMe
使用UserFilter
如果设置记住我,下次访问某些url时可以不用登陆。将记住我即可访问的地址配置让UserFilter拦截。
/images/**=anon
/js/**=anon
/css/**=anon
/login.jsp = anon
/logout.do=logout
/index.do=user
/** = authc
总集成代码
总结构:
依赖包:
pom.xml代码:
4.0.0
cn.zj.shiro
Shiro-Spring-StringMvc
0.0.1-SNAPSHOT
war
4.3.2.RELEASE
1.6.6
1.2.17
1.2.3
org.aspectj
aspectjweaver
1.7.4
org.springframework
spring-webmvc
${spring.version}
org.springframework
spring-jdbc
${spring.version}
org.springframework
spring-tx
${spring.version}
log4j
log4j
${log4j.version}
org.slf4j
slf4j-api
${slf4j.version}
org.slf4j
slf4j-log4j12
${slf4j.version}
com.alibaba
druid
1.1.10
org.mybatis
mybatis
3.4.4
org.mybatis
mybatis-spring
1.3.2
javax.servlet
javax.servlet-api
3.1.0
provided
javax.servlet.jsp
javax.servlet.jsp-api
2.2.1
provided
org.apache.shiro
shiro-core
${shiro.version}
org.apache.shiro
shiro-web
${shiro.version}
org.apache.shiro
shiro-spring
${shiro.version}
org.apache.shiro
shiro-quartz
${shiro.version}
org.apache.shiro
shiro-spring
${shiro.version}
mysql
mysql-connector-java
5.1.26
org.apache.shiro
shiro-ehcache
1.2.3
net.sf.ehcache
ehcache-core
2.6.0
org.apache.maven.plugins
maven-surefire-plugin
2.22.1
true
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
shiroSecurityFilter
org.springframework.web.filter.DelegatingFilterProxy
targetFilterLifecycle
true
targetBeanName
shiroSecurityFilter
shiroSecurityFilter
/*
springDispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring*.xml
1
springDispatcherServlet
*.do
springshiro.xml:
<?xml version="1.0" encoding="UTF-8"?>
unauthorized
/image/**=anon
/js/**=anon
/css/**=anon
/login.jsp=anon
/logout.do=logout
/index.do=user
/**=authc
shiro-ehcache.xml:
controller:
package cn.zj.shiro.controller;
import javax.servlet.http.HttpServletRequest;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/login")
public String login(HttpServletRequest request, Model medol) {
//因为shiro框架将错误的信息存入到请求对象中,可以根据shiro框架的信息友好的提示
String attribute = (String) request.getAttribute("shiroLoginFailure");
System.out.println("attribute:"+attribute);
if(UnknownAccountException.class.getName().equals(attribute)) {
System.out.println("账号不存在");
medol.addAttribute("errorMsg", "账号不存在");
}
if(IncorrectCredentialsException.class.getName().equals(attribute)) {
System.out.println("密码错误");
medol.addAttribute("errorMsg", "密码错误");
}
//提示用户名密码错误
return "forward:/login.jsp";
}
@RequiresPermissions("user:list")
@RequestMapping("/list")
public String list() {
return "user_list";
}
}
MyFormAuthenticationFilter.java:
package cn.zj.shiro.filter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
public class MyFormAuthenticationFilter extends FormAuthenticationFilter{
//重写isAccessAllowed方法,将cookie中的数据存入到session中
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
//获取主体对象
Subject subject = getSubject(request, response);
//获取session
Session session = subject.getSession();
//判断是否已认证,没有认证就查看有没有cookie
if(!subject.isAuthenticated() && subject.isRemembered()) {
String principal = (String) subject.getPrincipal();
session.setAttribute("user", principal);
}
//当存入到session中,此时就通过了认证。
return subject.isAuthenticated() || subject.isRemembered();
}
}
MyRealm.java:
package cn.zj.shiro.realm;
import java.util.Arrays;
import java.util.List;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
public class MyRealm extends AuthorizingRealm {
/*
* 自定义的realm 有两个重写的方法, 一个是可以写自己的认证逻辑(doGetAuthenticationInfo),
* 还有一个是可以写授权的逻辑(doGetAuthorizationInfo)
*
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// System.out.println("doGetAuthenticationInfo");
/*
* 1.先获取令牌中的身份和凭证 2.对比身份是否正确,不正确返回null
* 3.返回AuthenticationInfo对象并将身份正确返回从数据库中查找到的密码和身份
*/
// 假设是从数据库中的数据
List list = Arrays.asList("lili", "lucy", "zhangsan", "lisi");
String principal = (String) token.getPrincipal();
// 没有找到用户名
if (!list.contains(principal)) {
return null;
}
// 返回AuthenticationInfo的对象
ByteSource credentialsSalt = ByteSource.Util.bytes("qwer");
Object hashedCredentials = "142a04d176b6960cf517c6b2bac95630";
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, hashedCredentials,
credentialsSalt, this.getName());
return authenticationInfo;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("=========角色授权=========");
//模拟数据库查询出来的权限
List permissions = Arrays.asList("user:list","user:insert","user:update","role:list");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//将权限的判断交给shiro框架
authorizationInfo.addStringPermissions(permissions);
// System.out.println("=========角色授权=========");
return authorizationInfo;
}
}