news 2026/4/23 15:08:28

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/4/23 8:38:51

AlignTwoPolyDatas 基于ICP算法的配准和相机视角切换

一&#xff1a;主要的知识点 1、说明 本文只是教程内容的一小段&#xff0c;因博客字数限制&#xff0c;故进行拆分。主教程链接&#xff1a;vtk教程——逐行解析官网所有Python示例-CSDN博客 2、知识点纪要 本段代码主要涉及的有①ICP模型配准&#xff0c;②配准结果的检测…

作者头像 李华
网站建设 2026/4/23 8:38:52

YOLOv11 改进 - C2PSA | C2PSA融合EDFFN高效判别频域前馈网络(CVPR 2025):频域筛选机制增强细节感知,优化复杂场景目标检测

前言 本文介绍了高效判别频域前馈网络(EDFFN),并将其集成到YOLOv11中。EDFFN是为解决图像复原中局部信息表征不足和频域计算成本过高问题而提出的。传统方法存在SSM全局信息偏向性和频域FFN高计算成本的问题,EDFFN通过将频域操作位置从FFN中间层迁移到末端,降低了计算成本…

作者头像 李华
网站建设 2026/4/23 8:38:56

YOLOv11改进 - C3k2融合 | C3k2融DBlock解码器块( CVPR 2025 ) Decoder Block:解码器块,去模糊和提升图像清晰度

前言 本文介绍了用于多任务低光图像恢复的DarkIR模型中的DBlock,并将其集成到YOLOv11。DBlock部署在模型解码器阶段,负责图像去模糊、提升细节锐度及上采样。它遵循Metaformer架构,由扩张空间注意力模块(Di - SpAM)和门控前馈网络(GatedFFN)构成。Di - SpAM通过多尺度扩…

作者头像 李华
网站建设 2026/4/23 8:38:56

企业级应用部署中共享库问题的5个真实案例

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 开发一个共享库问题案例库应用&#xff0c;包含以下功能&#xff1a;1. 分类展示不同场景下的共享库错误案例&#xff08;Docker/物理机/交叉编译等&#xff09;2. 每种案例提供环境…

作者头像 李华
网站建设 2026/4/23 8:52:31

FP8:AI加速计算的新利器,如何用快马平台快速实现?

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 使用快马平台开发一个基于FP8格式的简单神经网络模型&#xff0c;用于图像分类任务。要求&#xff1a;1. 使用Kimi-K2模型生成FP8量化代码&#xff1b;2. 包含FP8数据格式转换功能&…

作者头像 李华
网站建设 2026/4/23 8:53:23

tar -czvf vs 其他压缩工具:效率对比

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 创建一个性能测试工具&#xff0c;对比tar -czvf、zip和rar在相同数据集下的压缩速度、压缩率和CPU占用率。工具应生成详细的对比报告&#xff0c;帮助用户选择最适合的压缩方式。点…

作者头像 李华