类Shiro权限校验框架的设计和实现
前言:
之前简单集成了springmvc和shiro用于后台管理平台的权限控制, 设计思路非常的优美, 而且编程确实非常的方便和简洁. 唯一的不足, 我觉得配置稍有些繁琐. 当时我有个小想法, 觉得可以写个更小巧版的shiro, 用于权限控制.
因为shiro本身不涉及权限的数据模型, 而且权限控制这块也只是它的一小部分功能点. 因此剥离后的工作量, 预计不是很大.
相关文章列表:
1. springmvc简单集成shiro
2. 利用Aspectj实现Oval的自动参数校验
设计目标
首先来讲讲定位吧, Shiro本身是一个安全组件, 可以复用. 但是我这个版本, 倾向于半成品. 它属于一种设计思路, 多少和业务有些耦合, 它有一定的借鉴作用, 其他简易项目可以拿去copy/paste, 然后修改下代码, 就能使用上的, ^_^.
列一下设计目标吧:
1. 只支持权限校验, 不涉及认证
2. 支持role/permission的注解校验(包括简单的与或操作)
就这么简单了, ^_^.
实践:
整个小框架, 是基于springmvc环境下, 其数据存储于http session中, 但除此以外, 和外界再无瓜葛, ^_^.
它的核心就一个工具类, 一个切面类, 两个注解, 非常的简洁.
权限注解类的引入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | // *) 逻辑与或 public enum MyLogic { AND, OR; } // *) 定义权限校验注解 @Target ({ElementType.TYPE, ElementType.METHOD}) @Retention (RetentionPolicy.RUNTIME) public @interface MyRequiresPermissions { String[] value(); MyLogic logic() default MyLogic.AND; } // *) 定义角色校验注解 @Target ({ElementType.TYPE, ElementType.METHOD}) @Retention (RetentionPolicy.RUNTIME) public @interface MyRequiresRoles { String[] value(); MyLogic logic() default MyLogic.AND; } |
工具类引入:
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | @Getter public class MyAuthorizeInfo { private Set<String> roles = new TreeSet<String>(); private Set<String> permissions = new TreeSet<String>(); public void addRole(String role) { roles.add(role); } public void addPermission(String permission) { permissions.add(permission); } } public class MyShiroHelper { private static final String MY_SHIRO_AUTHRIZE_KEY = "my_shiro_authorize_key" ; // 授权权限列表 public static void authorize(MyAuthorizeInfo authorizeInfo) { HttpSession session = getSession(); session.setAttribute(MY_SHIRO_AUTHRIZE_KEY, authorizeInfo); } // 验证角色 public static boolean validateRoles(String[] roles, MyLogic logic) throws Exception { if ( roles == null || roles.length == 0 ) { return true ; } try { HttpSession session = getSession(); MyAuthorizeInfo authorizeInfo = (MyAuthorizeInfo)session .getAttribute(MY_SHIRO_AUTHRIZE_KEY); if ( authorizeInfo == null ) { return false ; } return evaluateExpression(roles, logic, authorizeInfo.getRoles()); } catch (Exception e) { throw new Exception( "permission invalid state" ); } finally { } } // 验证权限 public static boolean validatePermssion(String[] permissions, MyLogic logic) throws Exception { if ( permissions == null || permissions.length == 0 ) { return true ; } try { HttpSession session = getSession(); MyAuthorizeInfo authorizeInfo = (MyAuthorizeInfo)session .getAttribute(MY_SHIRO_AUTHRIZE_KEY); if ( authorizeInfo == null ) { return false ; } return evaluateExpression(permissions, logic, authorizeInfo.getPermissions()); } catch (Exception e) { throw new Exception( "permission invalid state" ); } finally { } } // *) 与或操作, 布尔表达式的计算 public static boolean evaluateExpression(String[] values, MyLogic logic, Set<String> sets) { if ( MyLogic.AND == logic ) { for ( String val : values ) { if ( !sets.contains(val) ) { return false ; } } return true ; } else if ( MyLogic.OR == logic ) { for ( String val : values ) { if ( sets.contains(val) ) { return true ; } } return false ; } return true ; } // *) 获取Http Session private static HttpSession getSession() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()) .getRequest(); return request.getSession(); } } |
最后是切面类的引入:
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 | @Aspect @Component public class MyShiroAdvice { /** * 定义切点, 用于权限的校验 */ @Pointcut ( "@annotation(com.springapp.mvc.myshiro.MyRequiresPermissions)" ) public void checkPermissions() { } /** * 定义切点, 用于角色的校验 */ @Pointcut ( "@annotation(com.springapp.mvc.myshiro.MyRequiresRoles)" ) public void checkRoles() { } @Before ( "checkPermissions()" ) public void doCheckPermission(JoinPoint jp) throws Exception { // *) 获取对应的注解 MyRequiresPermissions mrp = extractAnnotation( (MethodInvocationProceedingJoinPoint)jp, MyRequiresPermissions. class ); try { if ( !MyShiroHelper.validatePermssion(mrp.value(), mrp.logic()) ) { throw new Exception( "access disallowed" ); } } catch (Exception e) { throw new Exception( "invalid state" ); } } @Before ( "checkRoles()" ) public void doCheckRole(JoinPoint jp) throws Exception { // *) 获取对应的注解 MyRequiresRoles mrp = extractAnnotation( (MethodInvocationProceedingJoinPoint)jp, MyRequiresRoles. class ); try { if ( !MyShiroHelper.validateRoles(mrp.value(), mrp.logic()) ) { throw new Exception( "access disallowed" ); } } catch (Exception e) { throw new Exception( "invalid state" ); } } // *) 获取注解信息 private static <T extends Annotation> T extractAnnotation( MethodInvocationProceedingJoinPoint mp, Class<T> clazz) throws Exception { Field proxy = mp.getClass().getDeclaredField( "methodInvocation" ); proxy.setAccessible( true ); ReflectiveMethodInvocation rmi = (ReflectiveMethodInvocation) proxy.get(mp); Method method = rmi.getMethod(); return (T) method.getAnnotation(clazz); } } |
这边为了简洁, 对异常类没有做细分处理.
实战:
那如何集成, 并使用上述的小shiro框架呢?
首先激活Aspectj, 同时把注解类纳入到spring容器中.
1 2 | < aop:aspectj-autoproxy proxy-target-class="true"/> < context:component-scan base-package="com.xxx.myshiro"/> <!-- 切面类所在的package --> |
而在具体的实战中, 可以写个简单的测试RestController类, 来演示下整个流程.
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 37 38 39 40 41 42 | @Controller @RequestMapping ( "/" ) public class HelloController { @RequestMapping (value= "/login" , method={RequestMethod.POST, RequestMethod.GET}) @ResponseBody public String login() { // 1) 完成登陆验证 // TODO // 2) 查询权限信息 // TODO // 3) 注册权限信息 MyAuthorizeInfo authorizeInfo = new MyAuthorizeInfo(); authorizeInfo.addRole( "admin" ); authorizeInfo.addPermission( "blog:write" ); authorizeInfo.addPermission( "blog:read" ); // *) 授权赋予 MyShiroHelper.authorize(authorizeInfo); return "ok" ; } @RequestMapping (value = "/test1" , method = {RequestMethod.GET, RequestMethod.POST}) @ResponseBody @MyRequiresPermissions (value={ "blog:write" , "blog:update" }, logic=MyLogic.AND) public String test1() { return "test1" ; } @RequestMapping (value = "/test2" , method = {RequestMethod.GET, RequestMethod.POST}) @ResponseBody @MyRequiresPermissions (value={ "blog:write" , "blog:update" }, logic=MyLogic.OR) public String test2() { return "test2" ; } } |
注: 关注下login接口, 这里完成了登陆和授权工作.
这个小框架, 把用户登陆和权限数据模型的设计获取交给了业务开发者, 只保留了权限校验.
测试:
在完成登陆login之后
1. 调用/test1, 访问受限(需要blog:write && blog:update, 但是只有blog:write权限)
2. 调用/test2, 访问通过(需要blog:write || blog:update, 有blog:write权限)
总结:
在抛离认证和账号管理之后, 其实这个权限校验小框架, 其实非常的漂亮和实用. 真正在业务开发时, 遇到小的项目, 其实可以直接使用, 而并非要选择较重的shiro.
当然在权限校验这块, 都是针对静态资源的权限校验, 若遇到动态权限校验(比如用户对自己编辑的文章有编辑权限, 其他用户没), 还是有所不足的.
不过, 如果有复杂的权限表达式计算需求的, 这个不算什么难点, 可以借助Groovy轻松实现.
posted on 2018-07-20 14:32 mumuxinfei 阅读(602) 评论(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语句:使用策略模式优化代码结构