Springboot基于拦截器的鉴权实现
文章介绍了如何利用拦截器和自定义注解实现简单的角色鉴权功能。通过创建`RequireAuth`、`RequirePermission`和`PermitAll`三个注解,结合拦截器的`preHandle`方法,实现对API的鉴权控制。拦截器通过反射获取注解信息,进行登录和权限检查,未通过时抛出异常并由全局异常处理器处理。最后将拦截器配置到Spring MVC中,完成鉴权功能。
文章目录
前言
用户鉴权是后端服务常见的需求,常见的做法是在用户访问API前根据用户当前的角色验证用户是否有访问权限。在大型项目中一般会使用Spring Security或Shiro等成熟的安全框架,而在一些小项目中,我们可以利用拦截器+自定义注解来完成简单的角色鉴权功能。
说到自定义注解,大家第一个想到的也许是Spring AOP,利用AOP也能完成鉴权功能,这里选择拦截器是因为它比AOP更适合完成本文需求,通过拦截器preHandle方法暴露的handler参数,我们可以利用反射来获取注解。
鉴权流程
在实现一项功能,特别是复杂的功能前,我习惯画思维导图来明确功能的运作流程。

创建鉴权注解,给需要鉴权的API方法/类加上注解。
当用户访问API时,请求进入拦截器,通过反射获取API方法/类上的注解,进行鉴权,鉴权通过则放行,不通过则抛出异常,由全局异常处理器捕获并响应相应的HTTP状态码。
Code
注解
/** * 鉴权注解,验证用户是否登录 */@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited
public @interface RequireAuth {}/** * 鉴权注解,验证用户是否拥有所需权限 */@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited
public @interface RequirePermission { /** * 需要的权限名 */ String[] value();
/** * 权限关系,默认为or */ Relation relation() default Relation.OR;
enum Relation { OR, AND }}/** * 鉴权注解,直接放行 */@Target({ElementType.TYPE, ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited
public @interface PermitAll {}这里创建了三个注解, 对应三种处理方式,在Controller中进行注解。
@RequireAuth@RestController@AllArgsConstructorpublic class TestController { private TestService testService;
// 方法上没有鉴权注解,从类上获取到RequireAuth,进行登录检查 @PostMapping("/api1") public R<?> api1() { }
// 方法上获取到PermitAll注解,优先处理,直接放行 @PermitAll @PostMapping("/api2") public R<?> api2() { }
// 谁会同时放两个鉴权注解啊喂! @RequirePermission @RequireAuth @PostMapping("/api3") public R<?> api3() { }}拦截器
接下来配置拦截器,拦截器是鉴权的主要逻辑实现。
public class AuthenticationInterceptor implements HandlerInterceptor { @Override public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) { if (handler instanceof HandlerMethod method) { Class<?> clazz = method.getBeanType();
// 获取方法上的注解 RequireAuth requireAuth = method.getMethodAnnotation(RequireAuth.class); RequirePermission requirePermission = method.getMethodAnnotation(RequirePermission.class); PermitAll permitAll = method.getMethodAnnotation(PermitAll.class);
// 若方法上没有注解,尝试获取类上的注解 if (requireAuth == null && requirePermission == null && permitAll == null) { requireAuth = clazz.getAnnotation(RequireAuth.class); requirePermission = clazz.getAnnotation(RequirePermission.class); permitAll = clazz.getAnnotation(PermitAll.class); }
// 处理权限鉴定 if (requirePermission != null && !checkPermission(request.getSession(), requirePermission)) { throw new ForbiddenException("您没有权限进行此操作"); // 抛出自定义异常,需继承RuntimeException } // 处理登录鉴定 else if (requireAuth != null && !checkAuth(request.getSession())) { throw new AuthenticationException("请登录后再操作"); // 抛出自定义异常,需继承RuntimeException } // 处理放行 else if (permitAll != null) { return true; } } return true; }
/** * 检查登录 * 省略具体逻辑 */ private boolean checkAuth(HttpSession session) { return true; }
/** * 检查权限 * Relation为OR则拥有注解中一项权限就通过 * Relation为AND则需拥有注解中所有权限才通过 * 省略具体逻辑 */ private boolean checkPermission(HttpSession session, RequirePermission anno) { String[] requirePermissions = anno.value(); RequirePermission.Relation relation = anno.relation();
return true; }}配置
最后把拦截器添加到Spring MVC中。
@Configurationpublic class WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 添加鉴权拦截器 registry.addInterceptor(new AuthenticationInterceptor()); }}全局异常处理
配置全局异常处理器,处理自定义异常
@RestControllerAdvicepublic class GlobalExceptionHandler { /** * 返回401 * 处理用户未登录的异常 */ @ResponseStatus(HttpStatus.UNAUTHORIZED) @ExceptionHandler(AuthenticationException.class) public R<String> AuthorizationException(Exception ex) { return R.fail(ex.getMessage()); }
/** * 返回403 * 处理用户没有权限的异常 */ @ResponseStatus(HttpStatus.FORBIDDEN) @ExceptionHandler(ForbiddenException.class) public R<String> ForbiddenException(Exception ex) { return R.fail(ex.getMessage()); }}至此,简单的鉴权功能完成。