使用shiro拦截器链实现权限管理
来源:互联网 发布:java gbk转utf8 编辑:程序博客网 时间:2024/05/21 09:43
在开篇之前,先介绍一下shiro,那么
什么是shiro呢?
Apache Shiro(发音为“shee-roh”,日语“堡垒(Castle)”的意思)是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理功能,可为任何应用提供安全保障 - 从命令行应用、移动应用到大型网络及企业应用。
Shiro为解决下列问题(我喜欢称它们为应用安全的四要素)提供了保护应用的API:
认证 - 用户身份识别,常被称为用户“登录”;
授权 - 访问控制;
密码加密 - 保护或隐藏数据防止被偷窥;
会话管理 -每用户相关的时间敏感的状态。Shiro还支持一些辅助特性,如Web应用安全、单元测试和多线程,它们的存在强化了上面提到的四个要素
由上面介绍可知,shiro可以实现认证及授权,这也是本篇的重点,使用shiro实现认证和授权。
使用shiro实现权限管理
首先看一下我的项目结构:
图 1.项目结构
项目代码地址:GitHub地址
我们跳过无关紧要的内容,直接讲最关建的shiro部分。
shiro
我们先看一下项目中和shiro有关的文件,java文件三个,分别是:MyRealm.java
、ShiroConfig.java
、ShiroManager.java
;
在spring-mvc.xml
文件中,我们对shiro的bean注解开启了配置
<context:component-scan base-package="shiro"/>
在web.xml
文件中,我们配置了shiro的过滤器
<!-- shiro过滤器定义 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 --> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/a/*</url-pattern> </filter-mapping>
由此,我们开始整理shiro的实现脉路。
shiro拦截器链的实现原理
首先我们的请求通过web.xml的时候被shiro的拦截器过滤了一波,也就是
<!-- shiro过滤器定义 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 --> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/a/*</url-pattern> </filter-mapping>
在起效,这里看一看到所有的/a
开头的url都需要经过shiro的过滤器。 spring-mvc.xml
中的注解开启配置就不说了,主要是shiro的java代码中涉及了比较多的注解配置,所以这条配置是必须的
<context:component-scan base-package="shiro"/>
接下来,我们看shiro的主体部分,也是本篇的重点
首先是MyRealm.java
package shiro;import model.Role;import model.User;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.apache.shiro.authc.*;import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;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.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import service.RoleService;import service.UserService;import util.SplitUtil;import java.util.HashSet;import java.util.List;import java.util.Set;/** * Created by yubotao on 2017/12/03. */@SuppressWarnings("ALL")@Componentpublic class MyRealm extends AuthorizingRealm{ private static final Log log = LogFactory.getLog(MyRealm.class); public MyRealm(){ super(new AllowAllCredentialsMatcher()); setAuthenticationTokenClass(UsernamePasswordToken.class); //FIXME:暂时禁用Cache setCachingEnabled(false); } @Autowired UserService userService; @Autowired RoleService roleService; //验证时调用 //此方法调用subject.hasRole("admin")或subject.isPermitted("admin"); //自己去调用这个是否有什么角色或有什么权限 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){ User user = (User) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); User user1 = null; /*新建角色与权限的set*/ Set<String> shiroPermissions = new HashSet<>(); Set<String> roleSet = new HashSet<>(); try{ /*1.通过userName获取userId*/ user1 = userService.getUserByName(user.getName()); if(user1 != null){ Role role = roleService.getRoleById(user.getRole()); log.info("role permission : " + role.getPermission()); SplitUtil splitUtil = new SplitUtil(); List<Integer> permissionList = splitUtil.stringToIntegerList(role.getPermission()); log.info("permisson list : " + permissionList); for (int i = 0; i < permissionList.size(); i++){ shiroPermissions.add(permissionList.get(i).toString()); } authorizationInfo.setStringPermissions(shiroPermissions); log.info("shiroPermissions : " + shiroPermissions); return authorizationInfo; } }catch (Exception e){ e.printStackTrace(); } return null; } //登陆时调用 //调用Subject currentUser = SecurityUtils.getSubject(); // currentUser.login(token); @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token){ String username = (String) token.getPrincipal(); log.info("username : " + username); User user = null; String password = null; try{ /*通过username获取User*/ user = userService.getUserByName(username); log.info("user : " + user); password = new String((char[]) token.getCredentials()); log.info("password : " + password); //账号不存在 if(user == null){ throw new UnknownAccountException("账号不正确"); } //密码错误 //简单起见,没有加密,应该加上 if(!password.equals(user.getPassword())){ throw new UnknownAccountException("密码错误"); } }catch (Exception e){ throw new AuthenticationException(); } SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,password,getName()); return info; }}
在讲解该部分代码前,我们首先介绍一下Realm这个shiro的组件。
Realm
此部分内容参考该blog:Realm的介绍
Realm 是一个能够访问应用程序特定的安全数据(如用户、角色及权限)的组件。
Realm 通常和数据源是一对一的对应关系,如关系数据库,LDAP 目录,文件系统,或其他类似资源。Realm
实质上就是一个特定安全的DAO。因为这些数据源大多通常存储身份验证数据(如密码的凭证)以及授权数据(如角色或权限),每个Realm能够执行身份验证和授权操作。
由这篇blog我们可以整理出如下信息:
1.shiro的认证最后是交给Realm的,调用的是doGetAuthenticationInfo()
方法;
2.如果验证通过,会返回一个非空的AuthenticationInfo
实例来代表来自该数据源的Subject
账户信息。
为了便于理解,这里首先介绍俩个词:
Authorization —— 授权
Authentication —— 认证
然后我们看一下我们自定义的MyRealm的内容,首先是构造函数
public MyRealm(){ super(new AllowAllCredentialsMatcher()); setAuthenticationTokenClass(UsernamePasswordToken.class); //FIXME:暂时禁用Cache setCachingEnabled(false); }
这里借用一张图:该图源自对于shiro有比较深入的介绍
这张图展示了shiro的认证匹配接口结构图,这里面就有我们提到的AllowAllCredentialsMatcher
,它的含义是:只要该用户名存在即可,不用去验证密码是否匹配。
然后我们在构造函数中将Cache设为暂不使用。
接下来是Realm中的认证内容
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){ User user = (User) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); User user1 = null; /*新建角色与权限的set*/ Set<String> shiroPermissions = new HashSet<>(); Set<String> roleSet = new HashSet<>(); try{ /*1.通过userName获取userId*/ user1 = userService.getUserByName(user.getName()); if(user1 != null){ Role role = roleService.getRoleById(user.getRole()); log.info("role permission : " + role.getPermission()); SplitUtil splitUtil = new SplitUtil(); List<Integer> permissionList = splitUtil.stringToIntegerList(role.getPermission()); log.info("permisson list : " + permissionList); for (int i = 0; i < permissionList.size(); i++){ shiroPermissions.add(permissionList.get(i).toString()); } authorizationInfo.setStringPermissions(shiroPermissions); log.info("shiroPermissions : " + shiroPermissions); return authorizationInfo; } }catch (Exception e){ e.printStackTrace(); } return null; }
这里可以看到,该代码块的主要逻辑就是,获取用户对应角色所拥有的权限,然后将这些权限放到SimpleAuthorizationInfo
的权限认证书中,这个地方是为了之后的shiro权限认证所做的铺垫。
这里使用了PrincipalCollection
,这个接口是干什么用的呢,经过查阅,发现该值是唯一的,在所有用户帐户中唯一的字符串用户名。所以代码中的
User user = (User) principals.getPrimaryPrincipal();
使用它来获取当前subject中的唯一用户。
到此,我们已经完成了授权,接下来就就是认证了。
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token){ String username = (String) token.getPrincipal(); log.info("username : " + username); User user = null; String password = null; try{ /*通过username获取User*/ user = userService.getUserByName(username); log.info("user : " + user); password = new String((char[]) token.getCredentials()); log.info("password : " + password); //账号不存在 if(user == null){ throw new UnknownAccountException("账号不正确"); } //密码错误 //简单起见,没有加密,应该加上 if(!password.equals(user.getPassword())){ throw new UnknownAccountException("密码错误"); } }catch (Exception e){ throw new AuthenticationException(); } SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,password,getName()); return info; }
这部分的代码块主要逻辑是:通过token.getPrincipal()
方法获取到Token中的用户名,然后根据用户名查找数据库,找出数据库中记录并进行对比,如果确实有该用户,并且帐密无误,那么就会通过
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,password,getName());
生成一个认证信息。
到此,我们的自定义Realm内容就完毕了。
ShiroManager
首先看一下ShiroManager.java
的代码:
package shiro;/** * Created by yubotao on 2017/12/03. */import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.spring.LifecycleBeanPostProcessor;import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.DependsOn;/** * shiro Config Manager * */public class ShiroManager { /** * 保证了实现shiro内部的lifecycle函数的bean执行 * */ @Bean(name = "lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){return new LifecycleBeanPostProcessor();} @Bean(name = "defaultAdvisorAutoProxyCreator") @DependsOn("lifecycleBeanPostProcessor") public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultSecurityManager securityManager){ AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor(); aasa.setSecurityManager(securityManager); return aasa; }}
首先这个ShiroManager的名字就暴露出,这部分是管理shiro的一些bean的,那么我们先来看第一个代码块:
@Bean(name = "lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){return new LifecycleBeanPostProcessor();}
查看shiro的官方文档我们发现,这个LifecycleBeanPostProcessor
是shiro和Spring整合的一个生命周期bean,自动使用init和destroy,无需我们进行管理;
接下来第二个代码块
@Bean(name = "defaultAdvisorAutoProxyCreator") @DependsOn("lifecycleBeanPostProcessor") public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; }
这里关键的类时DefaultAdvisorAutoProxyCreator
,该类时spring-aop包下的类,具体功能为:自动创建代理,它会搜寻所有的Advisor,并自动将Advisor应用到符合的PointCuts(切点)物件上。
通俗讲就是我们生成了一个可以自动帮我们实现aop代理的bean,并且对相应的切点使用已有的Advisor。
接下来我们看第三个代码块
@Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultSecurityManager securityManager){ AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor(); aasa.setSecurityManager(securityManager); return aasa; }
这个类可以在shiro官方文档中查看,我们通过查看可以看到这个AuthorizationAttributeSourceAdvisor
就是我们刚才提到的Advisor,并且它实际上使用了AopAllianceAnnotationsAuthorizingMethodInterceptor
,而该类的官方文档解释为:
Allows Shiro Annotations to work in any AOP Alliance specific implementation environment (for example, Spring).
使shiro注解能够在任意的特定AOP联合环境工作,比如我们现在使用的Spring。
至此,ShiroManager
的讲解也完毕了。
ShiroConfig
这里是整个shiro拦截链的关键。 ShiroConfig.java
package shiro;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.apache.shiro.cache.CacheManager;import org.apache.shiro.cache.MemoryConstrainedCacheManager;import org.apache.shiro.mgt.DefaultSecurityManager;import org.apache.shiro.realm.Realm;import org.apache.shiro.spring.web.ShiroFilterFactoryBean;import org.apache.shiro.web.mgt.DefaultWebSecurityManager;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.DependsOn;import org.springframework.context.annotation.Import;import java.util.LinkedHashMap;/** * Created by yubotao on 2017/12/03. */@Configuration@Import(ShiroManager.class)public class ShiroConfig { private static final Log log = LogFactory.getLog(ShiroConfig.class); @Bean(name = "realm") @DependsOn("lifecycleBeanPostProcessor") public Realm realm(){return new MyRealm();} /** * 用户授权信息Cache * */ @Bean(name = "shiroCacheManager") public CacheManager cacheManager(){return new MemoryConstrainedCacheManager();} @Bean(name = "securityManager") public DefaultSecurityManager securityManager(){ //这里注意,需要实现DefaultWebSecurityManager DefaultSecurityManager sm = new DefaultWebSecurityManager(); sm.setCacheManager(cacheManager()); return sm; } /** * shiro核心,拦截器链,顺序执行 * */ @Bean(name = "shiroFilter") @DependsOn("securityManager") public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultSecurityManager securityManager,Realm realm){ securityManager.setRealm(realm); ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(securityManager); shiroFilter.setLoginUrl("/a/login/fail");//未登录跳转 shiroFilter.setUnauthorizedUrl("/a/login/fail");//未授权跳转 LinkedHashMap<String,String> filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/a/u/first","perms[1]"); log.info("第一链"); filterChainDefinitionMap.put("/a/u/second","perms[2]"); log.info("第二链"); filterChainDefinitionMap.put("/a/u/third","perms[3]"); log.info("第三链"); filterChainDefinitionMap.put("/a//**","anon"); log.info("执行顺序 : " + filterChainDefinitionMap); shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilter; }}
和之前一样,我们还是逐个击破。
首先这里使用了注解@Import(ShiroManager.class)
,也就是引用了我们刚才配置的ShiroManager
,也就是在这个类里,完成了所有shiro配置的大汇合。
先看第一个代码块
@Bean(name = "realm") @DependsOn("lifecycleBeanPostProcessor") public Realm realm(){return new MyRealm();}
这里我们依赖了刚才配置的生命周期bean,并且配置出了我们自定义的MyRealm。
然后是第二个代码块
/** * 用户授权信息Cache * */ @Bean(name = "shiroCacheManager") public CacheManager cacheManager(){return new MemoryConstrainedCacheManager();}
这里我们将使用用户授权信息的Cache。
第三个代码块
@Bean(name = "securityManager") public DefaultSecurityManager securityManager(){ //这里注意,需要实现DefaultWebSecurityManager DefaultSecurityManager sm = new DefaultWebSecurityManager(); sm.setCacheManager(cacheManager()); return sm; }
这里需要注意的是需要实现DefaultWebSecurityManager
,否则会在使用shiro拦截链的时候报错,一开始的时候我在这里没有注意,少写了个Web,然后就出错了,一定要特别注意。
那么为什么这个类这么重要呢?
我们可以参考该文章:对于DefaultWebSecurityManager的介绍
大概解释如下
DefaultWebSecurityManager类主要定义了设置subjectDao,获取会话模式,设置会话模式,设置会话管理器,是否是http会话模式等操作,它继承了DefaultSecurityManager类,实现了WebSecurityManager接口
也就是说,如果没有使用这个类,我们无法验证会话中登陆的用户,也就无法进行登陆、校验、登出等操作;主要和Subject有关。
接下来就是本文的核心,shiro的拦截器链:
/** * shiro核心,拦截器链,顺序执行 * */ @Bean(name = "shiroFilter") @DependsOn("securityManager") public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultSecurityManager securityManager,Realm realm){ securityManager.setRealm(realm); ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(securityManager); shiroFilter.setLoginUrl("/a/login/fail");//未登录跳转 shiroFilter.setUnauthorizedUrl("/a/login/fail");//未授权跳转 LinkedHashMap<String,String> filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/a/u/first","perms[1]"); log.info("第一链"); filterChainDefinitionMap.put("/a/u/second","perms[2]"); log.info("第二链"); filterChainDefinitionMap.put("/a/u/third","perms[3]"); log.info("第三链"); filterChainDefinitionMap.put("/a//**","anon"); log.info("执行顺序 : " + filterChainDefinitionMap); shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilter; }
相信注解已经很清楚了,这里的逻辑就是:新建shiro的拦截链,然后设置未登录和未授权的两种跳转,这里为了简便,我将两种跳转设置为一个了;然后向拦截链里添加规则,最后返回shiro的拦截链信息。
成果展示
经过如上的讲解,我们检验一下效果。
当我们处于未登录状态的时候,请求权限url会跳转至之前设定的失败页面
当我们登陆后有对应权限时
当我们登陆后无权限访问时
至此整个shiro的拦截器链实现权限管理讲解结束,如有疏漏或错误,还请不吝赐教。
- 使用shiro拦截器链实现权限管理
- Shiro QuickStart使用shiro.ini静态数据源实现权限管理
- shiro实现动态权限管理
- 使用拦截器或者AOP实现权限管理(OA系统中实现权限控制)
- 基于Shiro 拦截URL,实现权限控制
- 通过struts2拦截器实现权限管理
- 自定义拦截器实现权限管理
- springmvc拦截器实现权限管理
- 使用shiro进行权限管理
- 使用shiro进行权限管理
- 使用shiro进行权限管理
- 使用shiro进行权限管理
- shiro 拦截器链
- shiro拦截器链
- Shiro实际使用(实现各种实用的拦截器)
- 使用拦截器实现权限控制
- 基于SSH实现员工管理系统登录权限的过滤器与拦截器的综合使用
- Springmvc集成Shiro实现权限管理
- Linux中vim经典使用手册
- Excel VBA 单元格格式
- SRS 代码分析【HLS切片】
- CSI Tool的实验环境搭建
- 适配ios11-导航栏titleView宽度变窄,无法点击
- 使用shiro拦截器链实现权限管理
- 单片机实现洗浴服务机器人的控制系统设计---凯利讯半导体
- 编程之路小细节-jar包和a标签的浅析
- b2b b2c o2o分布式电子商务云平台 需要准备哪些技术?
- php 在线读取PDF文件
- 树的拷贝
- 25 设备树里直接提供gpio口的中断号
- Activemq
- Windows下Mysql5.7开启binlog步骤及注意事项