基于Spring Boot AOP用户权限系统模块开发

2023-11-13

    公司项目需要涉及到用户权限的问题,每个用户都应该有自己的权限,而且权限应该是灵活可变的,系统的登陆模块因为涉及到分布式部署的问题以及前后端分离,不能采用传统的session作为登陆方式,而是采用JWT的方式实现,保证了接口的无状态性,但是这样的话也就让市面上的很多权限控制和登陆框架显得有些不太适合,比如:Spring Security、Apache Shiro,也许能将这些框架强行塞进系统里面,但是却可能不适应目前的这个系统需求,灵活性不够,也不好完美的适应现有的登录模块,忘了当时是怎么考虑的,仔细评估和分析之后还是放弃了现有的一些框架而选择自己重新开发这个权限模块。

    首先,我们看一下用户与模块、模块与接口的关系:


    在这张图里面,用户与模块,模块与接口的关系可以这么来说:

    一个用户可以同时拥有多个模块的权限,一个模块也可以同时被多个用户所拥有,而一个模块下面可以有多个接口,一个接口又可以属于多个模块。

    这样的话,就必须保证代码的松耦合,同时又要能完美的控制用户权限,保证系统的安全和权限职责的清晰。

    首先,利用Spring Tool Suite创建一个Spring Boot的Web工程,引入Mybatis、Druid、Spring AOP等工具。

    为了不影响现有功能,同时也必须要方便为接口添加权限控制,这里我们就需要涉及到面向切面的技术了,Spring的两大特征之一。这样的话,我们就可以保证权限模块与主程序之间的耦合降到最低,提供系统的可维护性和可操作性。

我们新建一个注解类PermissionModule,用来为接口添加注解,标识接口的所属模块,代码如下:

package org.opensource.pri.annotations;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import org.opensource.pri.enums.Module;



/**
 * 
 * 对方法实现模块权限控制
 * 
 * @author TanRq
 * @date 2017年11月14日
 * 
 * @param belong
 * 
 */
@Retention(RUNTIME)
@Target(METHOD)
public @interface PermissionModule {
	
	/**
	 * 执行方法所需要的权限,ALL表示所有权限都可以执行
	 * belong的类型为数组,方便为接口添加多个模块,即一个接口可以属于多个模块
	 * 同时也可以反映出一个模块可以包含多个接口的关系
	 * @return
	 */
	public Module[] belong() default {Module.ALL};
}

刚刚我们说了,我们需要利用到面向切面的技术,那么,在这里我们新建一个类专门用来配置切面,代码如下:

package org.opensource.pri.config;

import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.opensource.pri.annotations.PermissionModule;
import org.opensource.pri.auth.AuthContext;
import org.opensource.pri.auth.AuthStrategy;
import org.opensource.pri.enums.Module;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.fasterxml.jackson.databind.ObjectMapper;


/**
 * 权限认证配置,利用切面技术
 * @author TanRq
 *
 */
@Aspect
@Component
public class AuthConfig {
	
	@Autowired
	private AuthStrategy authStrategy;
	
	@Around("execution(* org.opensource.pri.controller..*(..)) and @annotation(org.springframework.web.bind.annotation.RequestMapping)")
	public Object executeAround(ProceedingJoinPoint jp) throws Throwable{
		//获取RequestAttributes
	    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();  
	    //从获取RequestAttributes中获取HttpServletRequest的信息  
	    HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
	    HttpServletResponse response=((ServletRequestAttributes)requestAttributes).getResponse();
	    
	    String userId= request.getHeader("userId");//获取请求头里面用户的ID,此处只做简单处理,实际应该有加密,否则将会导致用户信息泄露,无法保证安全
	    
	    Signature signature = jp.getSignature();    
	    MethodSignature methodSignature = (MethodSignature)signature;    
	    Method targetMethod = methodSignature.getMethod();
	    
	    Method realMethod = jp.getTarget().getClass().getDeclaredMethod(signature.getName(), targetMethod.getParameterTypes());
	    
	    Object  obj =null;
	    
	    if(isHasPermission(realMethod,userId)) {
	    	obj =  jp.proceed();//用户拥有该方法权限时执行方法里面的内容
	    }else {//用户没有权限,则直接返回没有权限的通知
	    	response.setHeader("Content-type","application/json; charset=UTF-8");
	    	OutputStream outputStream=response.getOutputStream();
	    	Map<String,String> resultMsg=new HashMap<String,String>();
	    	resultMsg.put("msg", "Not allowed to pass, you do not have the authority");
	    	outputStream.write(new ObjectMapper().writeValueAsString(resultMsg).getBytes("UTF-8"));
	    }
	    return obj;
	}
	
	/**
	 * 判断用户是否拥有权限
	 * @param realMethod
	 * @param userId
	 * @return
	 */
	private boolean isHasPermission(Method realMethod,String userId) {
		try {
			if(realMethod.isAnnotationPresent(PermissionModule.class)) {
				PermissionModule permissionModule=realMethod.getAnnotation(PermissionModule.class);
				Module[] modules= permissionModule.belong();
				//执行权限策略,判断用户权限
				return new AuthContext(authStrategy).execute(modules,userId);
			}
		}catch(Exception e) {
			System.out.println(e.getMessage());
			return false;
		}
		return false;
	}
}

上面的切面配置类中涉及到了权限策略,此处采用了策略模式进行代码的解耦,主要考虑到用户类型可能较多,控制权限的方式可能存在多样性,方便后期的维护,因为只需要切换策略就可以达到不同用户的策略处理方式。我们看一下涉及到权限策略的三个类是如何编写:

package org.opensource.pri.auth;

import org.opensource.pri.enums.Module;

/**
 * 权限策略上下文控制类
 * @author TanRq
 *
 */
public class AuthContext {
	
	private AuthStrategy authStrategy;
	
	public AuthContext(AuthStrategy strategy) {
		this.authStrategy=strategy;
	}
	
	/**
	 * 执行策略
	 * @param modules
	 * @param userId
	 * @return
	 * @throws Exception
	 */
	public boolean execute(Module[] modules,String userId) throws Exception {
		return this.authStrategy.executeAuth(modules,userId);
	}
	
}

package org.opensource.pri.auth;

import org.opensource.pri.enums.Module;

/**
 * 策略接口,所有策略都应该实现的接口
 * @author TanRq
 *
 */
public interface AuthStrategy {
	
	public boolean executeAuth(Module[] modules,String userId) throws Exception;
	
}

package org.opensource.pri.auth.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.opensource.pri.auth.AuthStrategy;
import org.opensource.pri.enums.Module;
import org.opensource.pri.service.AuthService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 
 * 用户权限控制策略算法实现类
 * @author TanRq
 *
 */
@Component
public class UserAuthStrategy implements AuthStrategy {

	@Autowired
	private AuthService authService;
	
	@Override
	public boolean executeAuth(Module[] modules,String userId) throws Exception {
		
		//表示标注的方式属于所有用户可执行
		if(ArrayUtils.contains(modules, Module.ALL)) {
			return true;
		}
		//用户ID为空,不允许通过,直接返回false
		if(StringUtils.isBlank(userId)) {
			return false;
		}
		
		List<Map<String,String>> permissionList = authService.getPermission(Integer.parseInt(userId));
		
		List<String> moduleList=new ArrayList<String>();
		
		for(Module module:modules) {
			moduleList.add(module.getModuleName());
		}
		
		List<String> hasList=new ArrayList<String>();
		
		for(Map<String,String> map:permissionList) {
			hasList.add(map.get("priName").toString());
		}
		//如果用户拥有该接口所属模块的任何一个模块的权限则返回true,否则false
		return hasList.removeAll(moduleList);
	}

}

通过上面的方式,我们基本就可以完成一个权限模块代码的编写,至于其他一些非主要的类将会提供下载连接方便下载demo并查看。

接下来就是验证功能的时候了,我们新建一个控制器类,然后通过postman来发送请求,测试权限功能是否能正常使用,控制器类如下:

package org.opensource.pri.controller;

import java.util.Map;

import org.opensource.pri.annotations.PermissionModule;
import org.opensource.pri.enums.Module;
import org.opensource.pri.service.HelloWorldService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * 一个简单的rest接口控制器
 * @author TanRq
 *
 */
@RestController
public class HelloWorldController {
	
	@Autowired
	HelloWorldService helloWorldService;
	
	/**
	 * Module.ALL类型表示所有用户都能访问该接口
	 * @return
	 */
	@PermissionModule(belong= {Module.ALL})
	@RequestMapping(value = "/sayhello",produces = "application/json; charset=UTF-8", method = RequestMethod.GET)
	public Map<String,Object> sayHello(){
		return helloWorldService.sayHello();
	}
	
	/**
	 * Module.TOTAL_EVERY_NO表示该接口只属于TOTAL_EVERY_NO所代表的模块,
	 * 如果用户没有TOTAL_EVERY_NO的权限,将无法访问该接口的内容
	 * @return
	 */
	@PermissionModule(belong= {Module.TOTAL_EVERY_NO})
	@RequestMapping(value = "/everyhello",produces = "application/json; charset=UTF-8", method = RequestMethod.GET)
	public Map<String,Object> sayEveryHello(){
		return helloWorldService.sayHello();
	}
	
	/**
	 * Module.TOTAL_PRE_NO表示该接口只属于TOTAL_PRE_NO所代表的模块,
	 * 如果用户没有TOTAL_PRE_NO的权限,将无法访问该接口的内容
	 * @return
	 */
	@PermissionModule(belong= {Module.TOTAL_PRE_NO})
	@RequestMapping(value = "/prehello",produces = "application/json; charset=UTF-8", method = RequestMethod.GET)
	public Map<String,Object> sayPreHello(){
		return helloWorldService.sayHello();
	}

}

测试结果如下:

sayhello接口测试结果:

sayhello接口因为权限为ALL,所以不需要userId都可以成功执行。


everyhello接口测试结果:

没有userId


有userId


prehello接口测试结果:


通过上面的方式,我们就可以轻松创建一个属于自己的权限控制模块,因为不需要传统session,而且采用的是AOP的方式进行开发,使得该功能更方便用于分布式系统中,而且没有很多繁琐的配置。

示例代码下载地址:http://download.csdn.net/download/u010520626/10267210



本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于Spring Boot AOP用户权限系统模块开发 的相关文章

随机推荐

  • autoReconnect及查看和连接时间有关的系统变量与通常产生的异常

    MySQL官方不推荐使用autoReconnect true 参见http bugs mysql com bug php id 5020 注意这里说的版本是3 0 14 production 需要另外找别的办法来解决超过8小时 链接断开的问
  • 【数据结构和算法】字符串操作

    作者 Linux猿 简介 CSDN博客专家 华为云享专家 Linux C C 云计算 物联网 面试 刷题 算法尽管咨询我 关注我 有问题私聊 关注专栏 数据结构和算法成神路 精讲 优质好文持续更新中 欢迎小伙伴们点赞 收藏 留言 目录 一
  • CAsyncSocket进行UDP通信

    CAsyncSocket进行UDP通信 客户端代码 CString m ServerIP CString m ClientIP int m ClientPort CString m ReceiveData UINT m ServerPort
  • 基于用户的协同过滤算法(及3种计算用户相似度的方法)

    本文参考 推荐系统实践 中基于用户的协同过滤算法内容 基于老师上课讲解 自己实现了其中的代码 了解了整个过程 UserCF算法实现 实现原理 模拟数据 两两用户之间计算 优化后的倒查表方式计算用户相似度 采用惩罚热门物品和倒查表方式计算用户
  • vue+websocket+express+mongodb实战项目(实时聊天)(一)

    vue websocket express mongodb实战项目 实时聊天 一 在原来基础上增加了多个聊天室以及发送图片 vue websocket express mongodb实战项目 实时聊天 二 http blog csdn ne
  • ARM中的程序状态寄存器(CPSR)

    31 30 29 28 27 8 7 6 5 4 3 2 1 0 N Z C V 保留 I F T M4 M3 M2 M1 M0 N Negative Less Than I IRQ disable Z Zero F FIQ disable
  • Caffe在Linux下的安装,编译,实验

    第一部分 Caffe 简介 caffe是有伯克利视觉和学习中心 BVLC 开发 作者是伯克利博士贾杨清 caffe是一个深度学习 deep learning 框架 其具有易读 快速和模块化思想 第二部分 Caffe安装与配置 2 1 配置环
  • 彻底卸载、devtools安装问题、扩展程序的使用

    目录 一 彻底卸载 MySQL Mongo数据库 二 vue devtools 三 扩展程序的使用 一 彻底卸载 MySQL Mongo数据库 停止服务1 cmd命令 net stop mysql mongo 停止服务2 如下图 这个好 控
  • ElasticSearch笔记整理(三):Java API使用与ES中文分词

    TOC pom xml 使用maven工程构建ES Java API的测试项目 其用到的依赖如下
  • Packing data with Python

    Packing data with Python 06 Apr 2016 Defining how a sequence of bytes sits in a memory buffer or on disk can be challeng
  • EasyAR 开发实例---AR礼物(简单demo)

    一个节日礼物效果 显示模型 在本次的案例中 我使用的是unity5 6 3版本 EasyAR 为2 0 用1 0的版本 在渲染那块有问题 导入SDK 到EasyAR官网 http www easyar cn view download ht
  • minitab数据处理软件

    下载地址 http www xue51 com soft 3430 html 1 介绍 Minitab软件是现代质量管理统计的领先者 全球六西格玛实施的共同语言 以无可比拟的强大功能和简易的可视化操作深受广大质量学者和统计专家的青睐 Min
  • Springboot 指定日志打印文件夹

    日志打印作为日常开发是必不可少了 SpringBoot项目中引入spring boot starter 这里面就包含了日志所需要的依赖 下面是两种方法打印日志 都很简单 亲测有效 方法一 直接在application yml中添加配置 指定
  • java 获取两个List集合的交集

    获取两个List集合的交集 可以使用Java中的retainAll方法来获取两个List的交集 假设有两个List类型的集合list1和list2 代码如下 List
  • MySQL主主复制+Keepalived 打造高可用MySQL集群

    转载地址 http www linuxidc com Linux 2014 09 106570 htm 为了响应公司需求 打造出更安全的mysql集群 能够实现mysql故障后切换 研究了几天终于有了成果 一起分享一下 首先介绍一下这套集群
  • 代码和数据结构

    代码 58同城 给出任意一个正整数 怎么用递归把他反过来打印 include
  • C++ 类 & 对象

    C 在 C 的基础上增加了面向对象编程 OOP 支持面向对象程序设计 类是 C 的核心特性 一种用户自定义的类型 用于指定对象的形式 类包含数据和用于处理数据的方法 函数 数据称为成员变量 函数称为成员函数 类可以看作是一种模板 用来创建具
  • 2022-03-09 Unity 3D两个场景的切换

    文章目录 效果 实现步骤 1 创建场景 2 添加按钮 3 写C 脚本实现切换 4 添加Component到Button上 5 添加两个Scene到Build中 测试效果 参考资料 效果 在scene1中点击按钮 进入scene2 实现步骤
  • Android 高德地图 关于INVALID_USER_KEY和INVALID_USER_SCODE的问题

    本文主要讲我在配置高德地图时候碰到的问题和解决方法 希给遇到同样问题的你一些帮助 1 INVALID USER KEY 当时我的Log上显示此问题 并且显示key为空 但我明明在mete data标签中写了我的key值 后发现manifes
  • 基于Spring Boot AOP用户权限系统模块开发

    公司项目需要涉及到用户权限的问题 每个用户都应该有自己的权限 而且权限应该是灵活可变的 系统的登陆模块因为涉及到分布式部署的问题以及前后端分离 不能采用传统的session作为登陆方式 而是采用JWT的方式实现 保证了接口的无状态性 但是这