灵活!Spring Boot 自定义注解结合参数解析器实现权限控制

开发 前端
在实际项目中,该方案适用于对性能和灵活性要求较高的场景,开发者可以在此基础上,结合自身业务需求,进一步优化和扩展,如 动态权限配置、RBAC(基于角色的访问控制) 等。

在现代 Web 应用开发中,权限控制是至关重要的一个环节,尤其是在微服务架构和前后端分离的模式下。如何在保证安全性的同时,兼顾开发的便捷性和代码的可读性,是开发者需要重点关注的问题。

Spring Boot 提供了多种方式来实现权限管理,例如 Spring Security,但在某些场景下,我们希望有更轻量级、灵活的权限控制方案。本篇文章将介绍如何通过 Spring Boot 3.4 中的 自定义注解 结合 AOP 和 参数解析器,实现一套可扩展的权限控制方案。

本方案的核心思路是:

  1. 通过 AOP 拦截 
    需要权限校验的方法,实现全局权限校验逻辑。
  2. 通过拦截器(Interceptor) 
    解析请求 Token,并将用户信息存储到上下文中,方便后续权限校验使用。
  3. 使用自定义参数解析器(HandlerMethodArgumentResolver) 
    在 Controller 方法参数中,通过注解直接获取当前登录用户信息,提高代码的可读性。
  4. 支持 SpEL(Spring 表达式语言) 
    以动态方式获取用户数据,实现更灵活的权限控制。

本文将结合代码示例,详细介绍该方案的实现方式,并最终构建一套完整的权限校验框架。

自定义注解

获取当前用户信息的注解

package com.icoderoad.auth;


import java.lang.annotation.*;


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface AuthUser {
    /** 通过 SpEL 表达式从当前登录用户信息中提取数据 */
    String value() default "";
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

权限控制注解

package com.icoderoad.auth;


import java.lang.annotation.*;


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PreAuthorize {
    /** 需要的权限 */
    String[] value() default {};


    /** 权限校验逻辑(全部匹配或部分匹配) */
    Logical logic() default Logical.AND;


    enum Logical {
        AND, OR;
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

核心组件实现

生成 Token 的工具类

package com.icoderoad.utils;


import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;


import java.util.*;
import java.util.function.Function;


@Component
public class JwtUtil {
    @Value("${jwt.secret}")
    private String secret;


    @Value("${jwt.expiration}")
    private Long expiration;


    private final ObjectMapper objectMapper;


    public JwtUtil(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }


    /** 生成 JWT 令牌 */
    public String generateToken(User user) {
        try {
            String payload = objectMapper.writeValueAsString(user);
            return createToken(payload);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }


    private String createToken(String payload) {
        return Jwts.builder()
            .claim("info", payload)
            .subject("auth_token")
            .issuedAt(new Date())
            .expiration(new Date(System.currentTimeMillis() + expiration * 1000))
            .signWith(Keys.hmacShaKeyFor(secret.getBytes()))
            .compact();
    }


    /** 解析 Token 获取用户信息 */
    public User getUser(String token) {
        try {
            String info = (String) getClaimFromToken(token, claims -> claims.get("info"));
            return objectMapper.readValue(info, User.class);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    private <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        Claims claims = Jwts.parserBuilder()
            .setSigningKey(Keys.hmacShaKeyFor(secret.getBytes()))
            .build()
            .parseClaimsJws(token)
            .getBody();
        return claimsResolver.apply(claims);
    }
}
  • 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.

认证拦截器

package com.icoderoad.interceptor;


import com.icoderoad.utils.JwtUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.regex.*;


@Component
public class AuthInterceptor implements HandlerInterceptor {
    private static final Pattern AUTH_PATTERN = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-._~+/]+=*)$", Pattern.CASE_INSENSITIVE);


    private final JwtUtil jwtUtil;
    private final ObjectMapper objectMapper;


    public AuthInterceptor(JwtUtil jwtUtil, ObjectMapper objectMapper) {
        this.jwtUtil = jwtUtil;
        this.objectMapper = objectMapper;
    }


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (authorization == null || !authorization.startsWith("Bearer ")) {
            sendError(response, "缺失 Token");
            return false;
        }


        Matcher matcher = AUTH_PATTERN.matcher(authorization);
        if (!matcher.matches()) {
            sendError(response, "无效 Token");
            return false;
        }


        User user = jwtUtil.getUser(matcher.group("token"));
        if (user == null) {
            sendError(response, "登录失效,请重新登录");
            return false;
        }


        SecurityContext.setUser(user);
        return true;
    }


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


    private void sendError(HttpServletResponse response, String message) throws IOException {
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().print(objectMapper.writeValueAsString(Map.of("code", -1, "message", message)));
    }
}
  • 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.

权限切面

package com.icoderoad.aspect;


import com.icoderoad.annotation.PreAuthorize;
import com.icoderoad.context.SecurityContext;
import com.icoderoad.exception.AuthException;
import com.icoderoad.model.User;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;


import java.util.Collections;
import java.util.Set;


@Aspect
@Component
public class PermissionAspect {
    @Around("@annotation(preAuthorize)")
    public Object checkPermission(ProceedingJoinPoint joinPoint, PreAuthorize preAuthorize) throws Throwable {
        User user = SecurityContext.getUser();
        if (user == null) {
            throw new AuthException("请先登录");
        }


        Set<String> requiredPerms = Set.of(preAuthorize.value());
        Set<String> userPerms = user.getPermissions();
        boolean hasPermission = validatePermissions(requiredPerms, userPerms, preAuthorize.logic());


        if (!hasPermission) {
            throw new AuthException("权限不足");
        }


        return joinPoint.proceed();
    }


    private boolean validatePermissions(Set<String> required, Set<String> has, PreAuthorize.Logical logic) 		{
        return logic == PreAuthorize.Logical.AND ? has.containsAll(required) : !Collections.disjoint(required, has);
    }
}
  • 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.

配置拦截器

package com.icoderoad.config;


import com.icoderoad.interceptor.AuthInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;


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


    public WebConfig(AuthInterceptor authInterceptor) {
        this.authInterceptor = authInterceptor;
    }


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor).addPathPatterns("/users/**");
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

测试

package com.icoderoad.controller;


import com.icoderoad.model.User;
import com.icoderoad.util.JwtUtil;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;


import java.util.Map;
import java.util.Set;


@RestController
public class LoginController {
    private final JwtUtil jwtUtil;


    public LoginController(JwtUtil jwtUtil) {
        this.jwtUtil = jwtUtil;
    }


    @GetMapping("/login")
    public ResponseEntity<Object> login(@RequestParam String username) {
        User user = new User(1L, username, Set.of("USER"));
        String token = jwtUtil.generateToken(user);
        return ResponseEntity.ok(Map.of("token", token));
    }
}
  • 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.

结论

通过本篇文章的学习,我们基于 Spring Boot 3.4 实现了一套高效、灵活的权限控制方案,核心组件包括:

  1. 自定义注解 @PreAuthorize
    用于定义方法级权限控制。
  2. 拦截器(Interceptor)用于解析 Token 并获取用户信息。
  3. AOP 切面在方法执行前进行权限校验。
  4. 自定义参数解析器让 Controller 层代码更加简洁优雅。

相比传统的 Spring Security 方案,本方案的优势在于:

  • 更加轻量级不依赖复杂的认证机制,仅需少量代码即可实现。
  • 高度可扩展可以灵活适配不同的认证方式,如 JWT、OAuth2 等。
  • 代码解耦业务逻辑与权限校验分离,提升可维护性。

在实际项目中,该方案适用于对性能和灵活性要求较高的场景,开发者可以在此基础上,结合自身业务需求,进一步优化和扩展,如 动态权限配置、RBAC(基于角色的访问控制) 等。

如果你在 Spring Boot 3.4 版本的开发中,正在寻找一种 既安全又高效的权限控制方案,不妨尝试本文介绍的方法,相信会给你的项目带来新的启发和帮助。


责任编辑:武晓燕 来源: 路条编程
相关推荐

2025-03-10 01:00:00

Spring参数解析器

2022-07-11 10:37:41

MapPart集合

2021-03-16 10:39:29

SpringBoot参数解析器

2024-10-14 17:18:27

2013-01-14 11:40:50

IBMdW

2022-05-11 10:45:21

SpringMVC框架Map

2022-01-06 06:23:49

Swagger参数解析器

2017-08-03 17:00:54

Springmvc任务执行器

2022-11-10 07:53:54

Spring参数校验

2023-10-24 13:48:50

自定义注解举值验证

2024-02-22 08:06:45

JSON策略解析器

2024-12-27 15:37:23

2020-11-25 11:20:44

Spring注解Java

2024-09-10 10:04:47

2018-06-21 14:46:03

Spring Boot异步调用

2023-10-09 07:37:01

2021-12-30 12:30:01

Java注解编译器

2023-01-13 08:11:24

2021-10-12 10:50:31

鸿蒙HarmonyOS应用

2024-02-28 09:35:52

点赞
收藏

51CTO技术栈公众号