使用SpringAOP自定義權(quán)限控制注解的實(shí)現(xiàn)示例
本文通過自定義注解對 Controller 層的方法實(shí)現(xiàn)訪問控制,其核心原理是基于 Spring AOP 的面向切面編程機(jī)制。系統(tǒng)在運(yùn)行時(shí)由 Spring 生成目標(biāo)類的動(dòng)態(tài)代理對象,在方法執(zhí)行前織入權(quán)限校驗(yàn)邏輯,從而實(shí)現(xiàn)對用戶訪問權(quán)限的統(tǒng)一攔截與驗(yàn)證,確保接口調(diào)用符合預(yù)設(shè)的安全規(guī)則。
package per.mjn.rbacdemo.common.security.annotation;
public enum Logical
{
/**
* 必須具有所有的元素
*/
AND,
/**
* 只需具有其中一個(gè)元素
*/
OR
}
package per.mjn.rbacdemo.common.security.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 登錄認(rèn)證:只有登錄之后才能進(jìn)入該方法
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE})
public @interface RequiresLogin {
}
package per.mjn.rbacdemo.common.security.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 權(quán)限認(rèn)證:必須具有指定權(quán)限才能進(jìn)入該方法
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequiresPermissions {
/**
* 需要校驗(yàn)的權(quán)限碼
*/
String[] value() default {};
/**
* 驗(yàn)證模式:AND | OR, 默認(rèn)AND
*/
Logical logical() default Logical.AND;
}
package per.mjn.rbacdemo.common.security.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 角色認(rèn)證:必須具有指定角色標(biāo)識(shí)才能進(jìn)入該方法
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface RequiresRoles {
/**
* 需要校驗(yàn)的角色標(biāo)識(shí)
*/
String[] value() default {};
/**
* 驗(yàn)證邏輯:AND | OR,默認(rèn)AND
*/
Logical logical() default Logical.AND;
}
定義異常
package per.mjn.rbacdemo.common.exception;
public class NotLoginException extends RuntimeException {
public NotLoginException(String message) {
super(message);
}
}
package per.mjn.rbacdemo.common.exception;
public class NotPermissionException extends RuntimeException {
public NotPermissionException(String message) {
super("無權(quán)限訪問接口:" + message);
}
}
package per.mjn.rbacdemo.common.exception;
public class NotRoleException extends RuntimeException {
public NotRoleException(String message) {
super("缺少角色:" + message);
}
}
package per.mjn.rbacdemo.common.security.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import per.mjn.rbacdemo.common.security.annotation.RequiresLogin;
import per.mjn.rbacdemo.common.security.annotation.RequiresPermissions;
import per.mjn.rbacdemo.common.security.annotation.RequiresRoles;
import per.mjn.rbacdemo.common.security.auth.AuthUtil;
import java.lang.reflect.Method;
/**
* 基于 Spring Aop 的注解鑒權(quán)
*/
@Aspect
@Component
public class PreAuthorizeAspect {
/**
* 構(gòu)建
*/
public PreAuthorizeAspect() {
}
/**
* 定義AOP簽名 (切入所有使用鑒權(quán)注解的方法)
*/
public static final String POINTCUT_SIGN = " @annotation(per.mjn.rbacdemo.common.security.annotation.RequiresLogin) || "
+ "@annotation(per.mjn.rbacdemo.common.security.annotation.RequiresPermissions) || "
+ "@annotation(per.mjn.rbacdemo.common.security.annotation.RequiresRoles)";
/**
* 聲明AOP簽名
*/
@Pointcut(POINTCUT_SIGN)
public void pointcut() {
}
/**
* 環(huán)繞切入
*
* @param joinPoint 切面對象
* @return 底層方法執(zhí)行后的返回值
* @throws Throwable 底層方法拋出的異常
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 注解鑒權(quán)
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
checkMethodAnnotation(signature.getMethod());
try
{
// 執(zhí)行原有邏輯
Object obj = joinPoint.proceed();
return obj;
}
catch (Throwable e)
{
throw e;
}
}
/**
* 對一個(gè)Method對象進(jìn)行注解檢查
*/
public void checkMethodAnnotation(Method method) {
// 校驗(yàn) @RequiresLogin 注解
RequiresLogin requiresLogin = method.getAnnotation(RequiresLogin.class);
if (requiresLogin != null)
{
AuthUtil.checkLogin();
}
// 校驗(yàn) @RequiresRoles 注解
RequiresRoles requiresRoles = method.getAnnotation(RequiresRoles.class);
if (requiresRoles != null)
{
AuthUtil.checkRole(requiresRoles);
}
// 校驗(yàn) @RequiresPermissions 注解
RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class);
if (requiresPermissions != null)
{
AuthUtil.checkPermi(requiresPermissions);
}
}
}
package per.mjn.rbacdemo.common.security.auth;
import per.mjn.rbacdemo.common.exception.NotLoginException;
import per.mjn.rbacdemo.common.exception.NotPermissionException;
import per.mjn.rbacdemo.common.exception.NotRoleException;
import per.mjn.rbacdemo.common.security.annotation.Logical;
import per.mjn.rbacdemo.common.security.annotation.RequiresPermissions;
import per.mjn.rbacdemo.common.security.annotation.RequiresRoles;
import per.mjn.rbacdemo.common.security.context.LoginUserHolder;
import per.mjn.rbacdemo.domain.LoginUser;
import java.util.List;
public class AuthLogic {
// 模擬登錄用戶(實(shí)際開發(fā)中從緩存或Token中獲?。?
public LoginUser getLoginUser() {
LoginUser user = LoginUserHolder.get();
if (user == null) {
throw new NotLoginException("用戶未登錄或token無效");
}
return user;
}
public LoginUser getLoginUser(String token) {
return getLoginUser(); // 簡化處理
}
public void checkLogin() {
if (getLoginUser() == null) {
throw new NotLoginException("未登錄");
}
}
public void checkRole(RequiresRoles requiresRoles) {
String[] roles = requiresRoles.value();
Logical logical = requiresRoles.logical();
List<String> userRoles = getLoginUser().getRoles();
if (logical == Logical.AND) {
for (String role : roles) {
if (!userRoles.contains(role)) {
throw new NotRoleException(role);
}
}
} else {
for (String role : roles) {
if (userRoles.contains(role)) return;
}
throw new NotRoleException(String.join(",", roles));
}
}
public void checkPermi(RequiresPermissions requiresPermissions) {
String[] perms = requiresPermissions.value();
Logical logical = requiresPermissions.logical();
List<String> userPerms = getLoginUser().getPermissions();
if (logical == Logical.AND) {
for (String perm : perms) {
if (!userPerms.contains(perm)) {
throw new NotPermissionException(perm);
}
}
} else {
for (String perm : perms) {
if (userPerms.contains(perm)) return;
}
throw new NotPermissionException(String.join(",", perms));
}
}
}
package per.mjn.rbacdemo.common.security.auth;
import per.mjn.rbacdemo.common.security.annotation.*;
import per.mjn.rbacdemo.domain.LoginUser;
public class AuthUtil {
public static AuthLogic authLogic = new AuthLogic();
public static void checkLogin() {
authLogic.checkLogin();
}
public static void checkRole(RequiresRoles requiresRoles) {
authLogic.checkRole(requiresRoles);
}
public static void checkPermi(RequiresPermissions requiresPermissions) {
authLogic.checkPermi(requiresPermissions);
}
public static LoginUser getLoginUser(String token) {
return authLogic.getLoginUser(token);
}
}
package per.mjn.rbacdemo.common.security.context;
import per.mjn.rbacdemo.domain.LoginUser;
public class LoginUserHolder {
private static final ThreadLocal<LoginUser> userThreadLocal = new ThreadLocal<>();
public static void set(LoginUser loginUser) {
userThreadLocal.set(loginUser);
}
public static LoginUser get() {
return userThreadLocal.get();
}
public static void clear() {
userThreadLocal.remove();
}
}
本文中的程序只是為了快速驗(yàn)證自定義注解能否起到權(quán)限控制的作用,并未連接數(shù)據(jù)庫,所以,在請求攔截器中設(shè)計(jì)請求用戶的信息(以代表當(dāng)前請求的用戶身份),同時(shí)為了便于測試,后期可直接在該部分修改用戶信息。
package per.mjn.rbacdemo.common.security.interceptor;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import per.mjn.rbacdemo.common.security.context.LoginUserHolder;
import per.mjn.rbacdemo.domain.LoginUser;
import java.util.ArrayList;
import java.util.List;
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("Authorization");
// 實(shí)際應(yīng)用中,token 應(yīng)該解密+驗(yàn)簽+查緩存等
if (token != null && token.equals("mock-token")) {
LoginUser loginUser = new LoginUser();
loginUser.setUsername("testUser");
List<String> roles = new ArrayList<>();
roles.add("admin");
List<String> permissions = new ArrayList<>();
// permissions.add("system:user:query");
permissions.add("system:user:create");
loginUser.setRoles(roles);
loginUser.setPermissions(permissions);
LoginUserHolder.set(loginUser);
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
LoginUserHolder.clear();
}
}
package per.mjn.rbacdemo.common.web;
import java.util.HashMap;
public class AjaxResult extends HashMap<String, Object> {
public static AjaxResult success(Object data) {
AjaxResult result = new AjaxResult();
result.put("code", 200);
result.put("msg", "success");
result.put("data", data);
return result;
}
public static AjaxResult error(String msg) {
AjaxResult result = new AjaxResult();
result.put("code", 500);
result.put("msg", msg);
return result;
}
}
package per.mjn.rbacdemo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
import per.mjn.rbacdemo.common.security.interceptor.TokenInterceptor;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private TokenInterceptor tokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor)
.addPathPatterns("/**");
}
}
package per.mjn.rbacdemo.controller;
import org.springframework.web.bind.annotation.*;
import per.mjn.rbacdemo.common.security.annotation.*;
import per.mjn.rbacdemo.common.security.annotation.Logical;
import per.mjn.rbacdemo.common.web.AjaxResult;
import per.mjn.rbacdemo.domain.LoginUser;
import java.util.List;
@RestController
@RequestMapping("/user")
public class UserController {
@RequiresLogin
@GetMapping("/profile")
public AjaxResult getProfile() {
return AjaxResult.success("用戶信息");
}
@RequiresPermissions("system:user:query")
@GetMapping("/{userId}")
public AjaxResult getInfo(@PathVariable(value = "userId", required = false) Long userId) {
return AjaxResult.success("查詢用戶: " + (userId != null ? userId : "全部"));
}
@RequiresRoles(value = {"admin", "manager"}, logical = Logical.OR)
@PostMapping("/create")
public AjaxResult createUser() {
LoginUser user = new LoginUser();
user.setUsername("admin");
user.setRoles(List.of("admin", "user"));
user.setPermissions(List.of("system:user:query", "system:user:create"));
return AjaxResult.success("創(chuàng)建成功");
}
}
package per.mjn.rbacdemo.domain;
import java.util.List;
import java.util.Set;
import lombok.Data;
@Data
public class LoginUser {
private String username;
private List<String> roles;
private List<String> permissions;
}
package per.mjn.rbacdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RbacDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RbacDemoApplication.class, args);
}
}
測試自定義權(quán)限控制注解
(1)訪問時(shí)未攜帶Token


(2)訪問時(shí)攜帶Token,但沒有該接口的訪問權(quán)限


(3)訪問時(shí)攜帶Token,且擁有該接口的訪問權(quán)限
此時(shí),需要將TokenInterceptor類中preHandle()方法中的注釋去掉
permissions.add("system:user:query");

附:pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.0</version>
<relativePath/>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>per.mjn</groupId>
<artifactId>RBACDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>RBACDemo</name>
<description>RBACDemo</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<!-- redis依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- lombok 依賴 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- MyBatis 依賴 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!-- MySQL 依賴 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- Java Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<!-- Jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
項(xiàng)目結(jié)構(gòu)目錄

到此這篇關(guān)于使用SpringAOP自定義權(quán)限控制注解的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)SpringAOP自定義權(quán)限控制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
手把手教你實(shí)現(xiàn)Java第三方應(yīng)用登錄
本文主要介紹了手把手教你實(shí)現(xiàn)Java第三方應(yīng)用登錄,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
Struts2源碼分析之ParametersInterceptor攔截器
這篇文章主要介紹了Struts2源碼分析之ParametersInterceptor攔截器,ParametersInterceptor攔截器其主要功能是把ActionContext中的請求參數(shù)設(shè)置到ValueStack中,,需要的朋友可以參考下2019-06-06
SpringBoot使用jasypt加解密密碼的實(shí)現(xiàn)方法(二)
這篇文章主要介紹了SpringBoot使用jasypt加解密密碼的實(shí)現(xiàn)方法(二),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10
java使用gzip實(shí)現(xiàn)文件解壓縮示例
這篇文章主要介紹了java使用gzip實(shí)現(xiàn)文件解壓縮示例,需要的朋友可以參考下2014-03-03
Java詳細(xì)講解Math和Random類中有哪些常用方法
Math類位于java.lang包中,包含很多用于科學(xué)計(jì)算的類方法,這些方法可以直接通過類名調(diào)用。Random類獲取隨機(jī)數(shù),位于java.util包中,本篇帶你了解它們的常用方法2022-05-05
Quarkus中ConfigSourceInterceptor的加密配置實(shí)現(xiàn)
這篇文章主要為大家介紹Quarkus中ConfigSourceInterceptor加密配置的實(shí)現(xiàn)方式,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-02-02

