文章目录
- 接口校验,权限拦截
-
- 通过自定义注解,基于面向切面编程来实现
-
- [1. 自定义异常](#1. 自定义异常)
- [2. 自定义注解](#2. 自定义注解)
- [3. AOP面向切面类](#3. AOP面向切面类)
- [4. Controller层使用](#4. Controller层使用)
- 统一异常处理和信息返回
-
- [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("辅导员/老师可以更新");
}
}