Spring Boot 权限控制三件套:JWT 登录校验 + 拦截器 + AOP 角色注解实战

文章目录

  • 接口校验,权限拦截
  • 统一异常处理和信息返回
    • [1. 创建统一信息返回类](#1. 创建统一信息返回类)
    • [2. 创建全局统一异常处理类](#2. 创建全局统一异常处理类)
    • [3. 创建一个枚举类型](#3. 创建一个枚举类型)
    • [4. 创建自定义的异常类](#4. 创建自定义的异常类)
  • 拦截器+JWT实现登录校验
    • [1. 添加依赖](#1. 添加依赖)
    • [2. JWT工具包](#2. JWT工具包)
    • [3. Threadlocal保存用户信息](#3. Threadlocal保存用户信息)
    • [4. 拦截器校验登录](#4. 拦截器校验登录)
    • [5. 注册拦截器](#5. 注册拦截器)
    • [6. 自定义注解+AOP角色校验](#6. 自定义注解+AOP角色校验)
    • [7. Controller层示例](#7. Controller层示例)

接口校验,权限拦截

通过自定义注解,基于面向切面编程来实现

  • 加依赖
xml 复制代码
		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>29.0-jre</version>
        </dependency>

1. 自定义异常

java 复制代码
// com.yourpackage.exception.AccessDeniedException.java package com.yourpackage.exception; 
public class AccessDeniedException extends RuntimeException { 
	public AccessDeniedException(String message) { 
		super(message); 
	}
}
  • 继承RuntimeException 是为了让他必须是非受检异常,不需要再方法上显示throws

2. 自定义注解

java 复制代码
//
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface hasRole {
	String[] value(); //允许的用户类型数组
}
元注解 作用
@Target 指定注解可用的位置(如方法、类、字段等)
@Retention 指定注解保留策略(源码/编译器/运行时)
@Documented 是否包含在JavaDoc中
@Inherited 子类是否继承父类的注解

3. AOP面向切面类

java 复制代码
@Aspect  
@Component  
public class RoleCheckAspect {  
  
    @Around("@annotation(hasRole)")  
    public Object checkPermission(ProceedingJoinPoint joinPoint, HasRole hasRole) throws Throwable {  
        // 1. 从 session 获取当前用户  
        WhitelistSetting currentUser = SessionUtils.getCurrentUserInfo();  
        if (currentUser == null) {  
            throw new RuntimeException("用户未登录,请先登录");  
        }  
  
        // 2. 获取用户的角色ID(假设 WhitelistSetting 有 getRoleId() 方法)  
        String userRoleId = currentUser.getRoleId();  
        if (userRoleId == null || userRoleId.trim().isEmpty()) {  
            throw new RuntimeException("用户角色信息缺失");  
        }  
  
        // 3. 获取注解中允许的角色列表  
        String[] allowedRoles = hasRole.value();  
        if (allowedRoles == null || allowedRoles.length == 0) {  
            throw new RuntimeException("HasRole 注解必须指定至少一个角色");  
        }  
  
        // 4. 校验用户角色是否在允许列表中  
        boolean hasAccess = Arrays.asList(allowedRoles).contains(userRoleId);  
        if (!hasAccess) {  
            throw new RuntimeException("权限不足:需要角色 [" + String.join(", ", allowedRoles) + "],当前角色为 [" + userRoleId + "]");  
        }  
  
        // 5. 放行  
        return joinPoint.proceed();  
    }  
}

4. Controller层使用

java 复制代码
@RestController  
@RequestMapping("/api")  
public class DemoController {  
  
    @GetMapping("/admin/data")  
    @HasRole({"ADMIN", "SUPER_ADMIN"})  
    public String adminData() {  
        return "管理员专属数据";  
    }  
  
    @GetMapping("/user/profile")  
    @HasRole({"USER", "ADMIN"})  
    public String userProfile() {  
        return "用户或管理员可访问";  
    }  
}

统一异常处理和信息返回

1. 创建统一信息返回类

java 复制代码
public class Resp<T> {

    //服务端返回的错误码
    private int code = 200;
    //服务端返回的错误信息
    private String msg = "success";
    //我们服务端返回的数据
    private T data;

    private Resp(int code,String msg,T data){
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public static <T> Resp success(T data){
        Resp resp = new Resp(200, "success", data);
        return resp;
    }

    public static <T> Resp success(String msg,T data){
        Resp resp = new Resp(200,msg, data);
        return resp;
    }

    public static <T> Resp error(AppExceptionCodeMsg appExceptionCodeMsg){
        Resp resp = new Resp(appExceptionCodeMsg.getCode(), appExceptionCodeMsg.getMsg(), null);
        return resp;
    }
    public static <T> Resp error(int code,String msg){
        Resp resp = new Resp(code,msg, null);
        return resp;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

    public T getData() {
        return data;
    }

}

2. 创建全局统一异常处理类

java 复制代码
@ControllerAdvice
public class GlobalExceptionHandler {


    @ExceptionHandler(value = {Exception.class})
    @ResponseBody
    public <T> Resp<T> exceptionHandler(Exception e){
        //这里先判断拦截到的Exception是不是我们自定义的异常类型
        if(e instanceof AppException){
            AppException appException = (AppException)e;
            return Resp.error(appException.getCode(),appException.getMsg());
        }

        //如果拦截的异常不是我们自定义的异常(例如:数据库主键冲突)
        return Resp.error(500,"服务器端异常");
    }
}

3. 创建一个枚举类型

java 复制代码
//这个枚举类中定义的都是跟业务有关的异常
public enum AppExceptionCodeMsg {

    INVALID_CODE(10000,"验证码无效"),
    USERNAME_NOT_EXISTS(10001,"用户名不存在"),
    USER_CREDIT_NOT_ENOUTH(10002,"用户积分不足");
    ;

    private int code ;
    private String msg ;

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }


    AppExceptionCodeMsg(int code, String msg){
        this.code = code;
        this.msg = msg;
    }

}

4. 创建自定义的异常类

java 复制代码
public class AppException extends RuntimeException{

    private int code = 500;
    private String msg = "服务器异常";


    public AppException(AppExceptionCodeMsg appExceptionCodeMsg){
        super();
        this.code = appExceptionCodeMsg.getCode();
        this.msg = appExceptionCodeMsg.getMsg();

    }

    public AppException(int code,String msg){
        super();
        this.code = code;
        this.msg = msg;

    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

}

拦截器+JWT实现登录校验

1. 添加依赖

xml 复制代码
<dependencies>
    <!-- JWT -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.11.5</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.11.5</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>
        <version>0.11.5</version>
        <scope>runtime</scope>
    </dependency>

    <!-- Spring AOP -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

    <!-- Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

2. JWT工具包

java 复制代码
package com.demo.util;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtUtils {

    private static final long EXPIRE = 2 * 60 * 60 * 1000;

    private static final SecretKey SECRET_KEY =
            Keys.hmacShaKeyFor("abcdefg1234567890abcdefg1234567890".getBytes());

    public static String generateToken(Long userId, String role) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("role", role);

        return Jwts.builder()
                .setClaims(claims)
                .setSubject(String.valueOf(userId))
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .signWith(SECRET_KEY)
                .compact();
    }

    public static Claims parseToken(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(SECRET_KEY)
                .build()
                .parseClaimsJws(token)
                .getBody();
    }
}

3. Threadlocal保存用户信息

java 复制代码
public class UserContext {
    private static final ThreadLocal<Long> userIdHolder = new ThreadLocal<>();
    private static final ThreadLocal<String> roleHolder = new ThreadLocal<>();

    public static void setUserId(Long id) { userIdHolder.set(id); }
    public static Long getUserId() { return userIdHolder.get(); }
    public static void setRole(String role) { roleHolder.set(role); }
    public static String getRole() { return roleHolder.get(); }
    public static void clear() { userIdHolder.remove(); roleHolder.remove(); }
}

4. 拦截器校验登录

java 复制代码
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.databind.ObjectMapper;

@Component
public class AuthInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String uri = request.getRequestURI();
        if (uri.equals("/login")) return true; // 放行登录

        String token = request.getHeader("Authorization");
        if (token == null) returnJson(response, 401, "未登录"); 
        else {
            try {
                token = token.replace("Bearer ", "");
                var claims = JwtUtils.parseToken(token);
                UserContext.setUserId(Long.valueOf(claims.getSubject()));
                UserContext.setRole((String) claims.get("role"));
                return true;
            } catch (Exception e) {
                returnJson(response, 401, "Token 无效或过期");
                return false;
            }
        }
        return false;
    }

    private void returnJson(HttpServletResponse response, int code, String msg) throws Exception {
        response.setContentType("application/json;charset=UTF-8");
        ObjectMapper mapper = new ObjectMapper();
        response.getWriter().write(mapper.writeValueAsString(Result.fail(code, msg)));
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        UserContext.clear();
    }
}

5. 注册拦截器

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private AuthInterceptor authInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor).addPathPatterns("/**");
    }
}

6. 自定义注解+AOP角色校验

java 复制代码
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireRole {
    String[] value();
}
java 复制代码
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class RoleAspect {
    @Around("@annotation(RequireRole)")
    public Object checkRole(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        RequireRole annotation = signature.getMethod().getAnnotation(RequireRole.class);

        String userRole = UserContext.getRole();
        for (String role : annotation.value()) {
            if (role.equals(userRole)) return joinPoint.proceed();
        }
        return Result.fail(403, "权限不足");
    }
}

7. Controller层示例

java 复制代码
import org.springframework.web.bind.annotation.*;
import java.util.Map;

@RestController
public class UserController {

    @PostMapping("/login")
    public Result<Map<String,Object>> login(@RequestParam String username, @RequestParam String password) {
        // 模拟验证
        Long userId = 1L;
        String role = switch (username) {
            case "student" -> "student";
            case "counselor" -> "counselor";
            case "teacher" -> "teacher";
            default -> "student";
        };
        String token = JwtUtils.createToken(userId, role);

        Map<String,Object> data = Map.of("token", token, "role", role);
        return Result.success(data);
    }

    @RequireRole({"student"})
    @GetMapping("/list")
    public Result<String> list() {
        return Result.success("学生可以访问列表");
    }

    @RequireRole({"counselor","teacher"})
    @PostMapping("/update")
    public Result<String> update() {
        return Result.success("辅导员/老师可以更新");
    }
}
相关推荐
xieliyu.5 小时前
Java算法精讲:双指针(三)
java·开发语言·算法
love530love5 小时前
LiveTalking 数字人项目 Windows 部署完全指南(EPGF 架构)
人工智能·windows·python·架构·livetalking·epgf
遇事不決洛必達6 小时前
【Python基础】GIL 锁是什么及其对爬虫的影响
爬虫·python·线程·进程·gil锁
星辰徐哥6 小时前
Spring Boot 微服务架构设计与实现
spring boot·后端·微服务
星辰徐哥6 小时前
Spring Boot 数据导入导出与报表生成
spring boot·后端·ui
明夜之约6 小时前
Spring Boot 自动装配源码
java·spring boot·后端
Leaton Lee6 小时前
Spring Boot分层架构详解:从Controller到Service再到Mapper的完整流程
java·spring boot·后端·架构
Micro麦可乐6 小时前
Spring Boot 实战:从零设计一个短链系统(含完整代码与数据库设计)
数据库·spring boot·后端·哈希算法·雪花算法·短链系统
Jinkxs6 小时前
Resilience4j- 与 Spring Boot 快速集成:自动配置与基础注解使用
java·spring boot·后端
毕设源码_郑学姐6 小时前
计算机毕业设计springboot网络相册设计与实现 基于Spring Boot框架的在线相册管理系统开发与应用 Spring Boot驱动的网络影集设计与实践
spring boot·后端·课程设计