news 2026/5/24 11:11:08

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot 权限控制三件套:JWT 登录校验 + 拦截器 + AOP 角色注解实战

文章目录

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

接口校验,权限拦截

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

  • 加依赖
<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. 自定义异常

// com.yourpackage.exception.AccessDeniedException.java package com.yourpackage.exception;publicclassAccessDeniedExceptionextendsRuntimeException{publicAccessDeniedException(Stringmessage){super(message);}}
  • 继承RuntimeException是为了让他必须是非受检异常,不需要再方法上显示throws

2. 自定义注解

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

3. AOP面向切面类

@Aspect@ComponentpublicclassRoleCheckAspect{@Around("@annotation(hasRole)")publicObjectcheckPermission(ProceedingJoinPointjoinPoint,HasRolehasRole)throwsThrowable{// 1. 从 session 获取当前用户WhitelistSettingcurrentUser=SessionUtils.getCurrentUserInfo();if(currentUser==null){thrownewRuntimeException("用户未登录,请先登录");}// 2. 获取用户的角色ID(假设 WhitelistSetting 有 getRoleId() 方法)StringuserRoleId=currentUser.getRoleId();if(userRoleId==null||userRoleId.trim().isEmpty()){thrownewRuntimeException("用户角色信息缺失");}// 3. 获取注解中允许的角色列表String[]allowedRoles=hasRole.value();if(allowedRoles==null||allowedRoles.length==0){thrownewRuntimeException("HasRole 注解必须指定至少一个角色");}// 4. 校验用户角色是否在允许列表中booleanhasAccess=Arrays.asList(allowedRoles).contains(userRoleId);if(!hasAccess){thrownewRuntimeException("权限不足:需要角色 ["+String.join(", ",allowedRoles)+"],当前角色为 ["+userRoleId+"]");}// 5. 放行returnjoinPoint.proceed();}}

4. Controller层使用

@RestController@RequestMapping("/api")publicclassDemoController{@GetMapping("/admin/data")@HasRole({"ADMIN","SUPER_ADMIN"})publicStringadminData(){return"管理员专属数据";}@GetMapping("/user/profile")@HasRole({"USER","ADMIN"})publicStringuserProfile(){return"用户或管理员可访问";}}

统一异常处理和信息返回

1. 创建统一信息返回类

publicclassResp<T>{//服务端返回的错误码privateintcode=200;//服务端返回的错误信息privateStringmsg="success";//我们服务端返回的数据privateTdata;privateResp(intcode,Stringmsg,Tdata){this.code=code;this.msg=msg;this.data=data;}publicstatic<T>Respsuccess(Tdata){Respresp=newResp(200,"success",data);returnresp;}publicstatic<T>Respsuccess(Stringmsg,Tdata){Respresp=newResp(200,msg,data);returnresp;}publicstatic<T>Resperror(AppExceptionCodeMsgappExceptionCodeMsg){Respresp=newResp(appExceptionCodeMsg.getCode(),appExceptionCodeMsg.getMsg(),null);returnresp;}publicstatic<T>Resperror(intcode,Stringmsg){Respresp=newResp(code,msg,null);returnresp;}publicintgetCode(){returncode;}publicStringgetMsg(){returnmsg;}publicTgetData(){returndata;}}

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

@ControllerAdvicepublicclassGlobalExceptionHandler{@ExceptionHandler(value={Exception.class})@ResponseBodypublic<T>Resp<T>exceptionHandler(Exceptione){//这里先判断拦截到的Exception是不是我们自定义的异常类型if(einstanceofAppException){AppExceptionappException=(AppException)e;returnResp.error(appException.getCode(),appException.getMsg());}//如果拦截的异常不是我们自定义的异常(例如:数据库主键冲突)returnResp.error(500,"服务器端异常");}}

3. 创建一个枚举类型

//这个枚举类中定义的都是跟业务有关的异常publicenumAppExceptionCodeMsg{INVALID_CODE(10000,"验证码无效"),USERNAME_NOT_EXISTS(10001,"用户名不存在"),USER_CREDIT_NOT_ENOUTH(10002,"用户积分不足");;privateintcode;privateStringmsg;publicintgetCode(){returncode;}publicStringgetMsg(){returnmsg;}AppExceptionCodeMsg(intcode,Stringmsg){this.code=code;this.msg=msg;}}

4. 创建自定义的异常类

publicclassAppExceptionextendsRuntimeException{privateintcode=500;privateStringmsg="服务器异常";publicAppException(AppExceptionCodeMsgappExceptionCodeMsg){super();this.code=appExceptionCodeMsg.getCode();this.msg=appExceptionCodeMsg.getMsg();}publicAppException(intcode,Stringmsg){super();this.code=code;this.msg=msg;}publicintgetCode(){returncode;}publicStringgetMsg(){returnmsg;}}

拦截器+JWT实现登录校验

1. 添加依赖

<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工具包

packagecom.demo.util;importio.jsonwebtoken.*;importio.jsonwebtoken.security.Keys;importjavax.crypto.SecretKey;importjava.util.Date;importjava.util.HashMap;importjava.util.Map;publicclassJwtUtils{privatestaticfinallongEXPIRE=2*60*60*1000;privatestaticfinalSecretKeySECRET_KEY=Keys.hmacShaKeyFor("abcdefg1234567890abcdefg1234567890".getBytes());publicstaticStringgenerateToken(LonguserId,Stringrole){Map<String,Object>claims=newHashMap<>();claims.put("role",role);returnJwts.builder().setClaims(claims).setSubject(String.valueOf(userId)).setExpiration(newDate(System.currentTimeMillis()+EXPIRE)).signWith(SECRET_KEY).compact();}publicstaticClaimsparseToken(Stringtoken){returnJwts.parserBuilder().setSigningKey(SECRET_KEY).build().parseClaimsJws(token).getBody();}}

3. Threadlocal保存用户信息

publicclassUserContext{privatestaticfinalThreadLocal<Long>userIdHolder=newThreadLocal<>();privatestaticfinalThreadLocal<String>roleHolder=newThreadLocal<>();publicstaticvoidsetUserId(Longid){userIdHolder.set(id);}publicstaticLonggetUserId(){returnuserIdHolder.get();}publicstaticvoidsetRole(Stringrole){roleHolder.set(role);}publicstaticStringgetRole(){returnroleHolder.get();}publicstaticvoidclear(){userIdHolder.remove();roleHolder.remove();}}

4. 拦截器校验登录

importorg.springframework.stereotype.Component;importorg.springframework.web.servlet.HandlerInterceptor;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importcom.fasterxml.jackson.databind.ObjectMapper;@ComponentpublicclassAuthInterceptorimplementsHandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{Stringuri=request.getRequestURI();if(uri.equals("/login"))returntrue;// 放行登录Stringtoken=request.getHeader("Authorization");if(token==null)returnJson(response,401,"未登录");else{try{token=token.replace("Bearer ","");varclaims=JwtUtils.parseToken(token);UserContext.setUserId(Long.valueOf(claims.getSubject()));UserContext.setRole((String)claims.get("role"));returntrue;}catch(Exceptione){returnJson(response,401,"Token 无效或过期");returnfalse;}}returnfalse;}privatevoidreturnJson(HttpServletResponseresponse,intcode,Stringmsg)throwsException{response.setContentType("application/json;charset=UTF-8");ObjectMappermapper=newObjectMapper();response.getWriter().write(mapper.writeValueAsString(Result.fail(code,msg)));}@OverridepublicvoidafterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler,Exceptionex){UserContext.clear();}}

5. 注册拦截器

importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;@ConfigurationpublicclassWebConfigimplementsWebMvcConfigurer{@AutowiredprivateAuthInterceptorauthInterceptor;@OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){registry.addInterceptor(authInterceptor).addPathPatterns("/**");}}

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

importjava.lang.annotation.*;@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceRequireRole{String[]value();}
importorg.aspectj.lang.annotation.*;importorg.aspectj.lang.ProceedingJoinPoint;importorg.aspectj.lang.reflect.MethodSignature;importorg.springframework.stereotype.Component;@Aspect@ComponentpublicclassRoleAspect{@Around("@annotation(RequireRole)")publicObjectcheckRole(ProceedingJoinPointjoinPoint)throwsThrowable{MethodSignaturesignature=(MethodSignature)joinPoint.getSignature();RequireRoleannotation=signature.getMethod().getAnnotation(RequireRole.class);StringuserRole=UserContext.getRole();for(Stringrole:annotation.value()){if(role.equals(userRole))returnjoinPoint.proceed();}returnResult.fail(403,"权限不足");}}

7. Controller层示例

importorg.springframework.web.bind.annotation.*;importjava.util.Map;@RestControllerpublicclassUserController{@PostMapping("/login")publicResult<Map<String,Object>>login(@RequestParamStringusername,@RequestParamStringpassword){// 模拟验证LonguserId=1L;Stringrole=switch(username){case"student"->"student";case"counselor"->"counselor";case"teacher"->"teacher";default->"student";};Stringtoken=JwtUtils.createToken(userId,role);Map<String,Object>data=Map.of("token",token,"role",role);returnResult.success(data);}@RequireRole({"student"})@GetMapping("/list")publicResult<String>list(){returnResult.success("学生可以访问列表");}@RequireRole({"counselor","teacher"})@PostMapping("/update")publicResult<String>update(){returnResult.success("辅导员/老师可以更新");}}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/25 0:35:42

Python验证码处理实战:从12306项目看验证码识别的技术演进

一、引言&#xff1a;验证码&#xff0c;网络安全的第一道防线 在网络应用中&#xff0c;验证码是防止自动化攻击的重要手段。12306作为中国铁路售票系统&#xff0c;其验证码设计尤为复杂&#xff0c;从早期的数字字母组合&#xff0c;到后来的图片点击&#xff0c;再到如今的…

作者头像 李华
网站建设 2026/5/22 7:37:31

【Hadoop+Spark+python毕设】哮喘患者症状数据可视化分析系统、计算机毕业设计、包括数据爬取、数据分析、数据可视化、Hadoop、实战教学

&#x1f393; 作者&#xff1a;计算机毕设小月哥 | 软件开发专家 &#x1f5a5;️ 简介&#xff1a;8年计算机软件程序开发经验。精通Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等技术栈。 &#x1f6e0;️ 专业服务 &#x1f6e0;️ 需求定制化开发源码提…

作者头像 李华
网站建设 2026/5/22 12:42:53

java计算机毕业设计山西工程技术学院学生党员管理系统的设计和实现 高校学生党务工作数字化平台的设计与实现 面向二级学院的学生党员信息一站式服务平台

计算机毕业设计山西工程技术学院学生党员管理系统的设计和实现m6m3l9&#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。 在高校党建业务日益精细化的今天&#xff0c;传统纸质与人工…

作者头像 李华
网站建设 2026/5/22 7:40:26

【二分查找-开区间思维】

文章目录红蓝染色法1\. 核心逻辑&#xff1a;(-1, n)2\. 代码模板3\. 为什么很多人喜欢这种写法&#xff1f;&#xff08;优势&#xff09;4\. 劣势与注意事项开区间和闭区间的区别1\. 为什么它是“闭区间”写法&#xff1f;2\. 这张图在解释哪段代码&#xff1f;3\. 和刚才说的…

作者头像 李华
网站建设 2026/5/22 6:46:07

C 标准库 - <locale.h>

C 标准库 - <locale.h> 引言 在C语言编程中,正确处理不同语言环境下的字符编码和格式是非常重要的。《locale.h》头文件提供了C标准库中用于处理本地化(locale)的功能。本文将详细介绍《locale.h》头文件的功能、使用方法以及注意事项。 <locale.h>概述 《l…

作者头像 李华
网站建设 2026/5/22 8:06:56

新手必看:轻松解决pyproject.toml metadata错误

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个面向Python新手的教程&#xff0c;解释pyproject.toml文件的基本结构和常见metadata错误。教程应包含简单的错误示例和逐步修复指南&#xff0c;使用通俗易懂的语言和图示。…

作者头像 李华