springmvc简单集成shiro
前言:
有天和同事聊天, 谈起权限管理, 他说他有个同事用shiro用的很溜. 正好现在有个管理平台项目, 有权限控制的需求, 因此想借此机会研究一番.
本文主要简单讲解一下对shiro的一些认识, 比较浅显, 同时用一个小的演示demo, 来简单实践一下.
shiro简介:
shiro是apache旗下的一个开源安全组件, 它比spring security更容易集成和使用.
官网地址为: Shiro.
其整个架构图如下:
我觉得核心的一下几个概念:
Subject:主题, 可以理解为用户的超级session, 通过其可以进行认证/授权/权限验证.
SecurityManager:他是 Shiro 的心脏;所有组件都交互都通过SecurityManager进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。
Realm:它维护了认证/授权的具体实现.
至于SessionManager, CacheManager, 这些都属于更进一步的研究, 暂时放放.
通过权限系统设计思想:
其实权限系统发展到现在, 由两种主流的做法: 基于角色的权限控制/基于资源的权限控制.
数据模型图大致如下:
通常把资源和权限整合为'新权限', 数据模型图如下:
注: 该图来源于博文: 从权限到shiro框架.
而shiro显然也顺从这种设计思路, 它的权限校验, 主要有role/permission两种方式.
1 2 3 4 | // 1) 校验资源权限 SecurityUtils.getSubject().checkPermission( "blog:write" ); // 2) 校验角色 SecurityUtils.getSubject().checkRole( "admin" ); |
整合实践:
整合的springmvc demo参考自官网文档 https://shiro.apache.org/spring.html.
添加maven依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version> 1.4 . 0 </version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version> 1.4 . 0 </version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version> 1.4 . 0 </version> </dependency> |
编写AuthorizingRealm的自定义实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public class DemoRealm extends AuthorizingRealm { // *) 校验权限时, 被回调 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { // 可以添加业务代码 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // *) 添加role info.addRole( "admin" ); // *) 添加权限(资源+权限) info.addStringPermission( "blog:write" ); info.addStringPermission( "blog:read" ); return info; } // *) 认证时被调用 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { // 可以添加业务代码 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo( authenticationToken.getPrincipal(), authenticationToken.getCredentials(), "simple" ); return info; } } |
在web.xml中添加filter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <!-- 添加spring相关的配置文件&属性, 和shiro无关, 但需确保容器的相关bean优于shiroFilter之前创建 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/conf/applicationContext*.xml</param-value> </context-param> <listener> <listener- class >org.springframework.web.context.ContextLoaderListener</listener- class > </listener> <!-- shiro默认的filter --> <filter> <description>shiro</description> <filter-name>shiroFilter</filter-name> <filter- class >org.springframework.web.filter.DelegatingFilterProxy</filter- class > <init-param> <param-name>targetFilterLifecycle</param-name> <param-value> false </param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
然后在applicationContext.xml添加shiro的相关bean, 这个最简化的情形.
1 2 3 4 5 6 7 8 9 10 11 12 13 | <bean id= "shiroFilter" class = "org.apache.shiro.spring.web.ShiroFilterFactoryBean" > <property name= "securityManager" ref= "securityManager" /> </bean> <!-- 配置进行授权和认证的 Realm --> <bean id= "myRealm" class = "com.springapp.mvc.shiro.DemoRealm" /> <!-- 配置 Shiro 的 SecurityManager Bean. --> <bean id= "securityManager" class = "org.apache.shiro.web.mgt.DefaultWebSecurityManager" > <property name= "realm" ref= "myRealm" /> </bean> <bean id= "lifecycleBeanPostProcessor" class = "org.apache.shiro.spring.LifecycleBeanPostProcessor" /> |
编写测试用例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | @Controller @RequestMapping ( "/" ) public class HelloController { @RequestMapping (value= "/login" , method={RequestMethod.POST, RequestMethod.GET}) @ResponseBody public String login( @RequestParam ( "username" ) String username, @RequestParam ( "password" ) String password ) { // 登陆, 既完成shiro的认证流程 Subject subject = SecurityUtils.getSubject(); AuthenticationToken token = new UsernamePasswordToken( "username" , "password" ); subject.login(token); return "ok" ; } @RequestMapping (value = "/test1" , method = {RequestMethod.GET, RequestMethod.POST}) @ResponseBody public String test1() { // *) 权限存在, 校验会回调授权实现 SecurityUtils.getSubject().checkPermission( "blog:write" ); return "test1" ; } @RequestMapping (value = "/test2" , method = {RequestMethod.GET, RequestMethod.POST}) @ResponseBody public String test2() { // *) 权限不存在, 校验会回调授权实现 SecurityUtils.getSubject().checkPermission( "blog:write1" ); return "test2" ; } } |
测试:
就是单纯的测试shiro的认证什么时候进行, 授权又是什么时候被回调.
case 1: 测试认证过程
直接rest api调用/login, 观察到自定义的DemoRealm的doGetAuthenticationInfo方法被回调, 此时认证成功.
case 2: 不经过认证, 访问受权限保护的资源
无论调用/test1, /test2都返回UnauthenticatedException的异常.
case 3: 经过认证后, 访问受权限保护的资源
访问/test1, pass
访问/test2, nopass
结果符合预期, 而且观察到DemoRealm的doGetAuthorizationInfo方法被回调, 用户的权限列表默认没缓存(官方推荐ehcache做缓存).
引入注解:
除了硬编码, 权限资源的check, 还可以引入注解来实现.
在之前的applicationContext.xml中, 添加如下配置, 使得注解生效.
1 2 3 4 | < 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 > |
然后在测试代码中, 添加如下测试接口(注解@RequiresPermissions/@RequiresRoles).
1 2 3 4 5 6 7 8 9 10 11 12 13 | @RequestMapping(value = "/test3", method = {RequestMethod.GET, RequestMethod.POST}) @ResponseBody @RequiresPermissions("blog:write3") public String test3() { return "test3"; } @RequestMapping(value = "/test4", method = {RequestMethod.GET, RequestMethod.POST}) @ResponseBody @RequiresRoles({"admin4"}) public String test4() { return "test4"; } |
测试的结果都符合预期, 都被完美的拦截下来了.
更进一步:
对于面向rest api服务的权限控制需求, 引入注解后, 代码基本上显得非常漂亮了, 简单的配置注解即可完美进行资源访问控制.
为了更好的屏蔽异常, 可以引入ControllerAdvice/Interceptor, 用于拦截内部异常, 转换为友好的信息提示.
具体可参看之前写过的文章:
1. REST API的异常处理.
2. Interceptor机制和实践.
遇到的问题记录:
在具体集成springmvc和shiro的过程中, 也遇到了一些坑.
1. 遇到shiroFilter实例(ShiroFilterFactoryBean)创建失败, 导致servlet层面的Filter(ShiroFilter)初始化失败.
1 | No bean named 'shiroFilter' is defined |
主要原因是要保证ShiroFilter(Serlvet层面)的实例要晚于spring容器的实例初始化(既通过listener, 优先加载applicationContext.xml).
后记:
以前后端管理平台是基于MVC的架构构建(即后端也处理路由和页面渲染), 现在流行架构是MVVM(前后端完全分离, 前端做路由和渲染, 后端纯数据服务), 因此对shiro的使用和研究, 不再涉及页面标签这块.
posted on 2018-07-19 22:24 mumuxinfei 阅读(498) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
2014-07-19 shell 脚本实战笔记(11)--Mysql在linux下的安装和简单运维