当前位置: 首页 > news >正文

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("辅导员/老师可以更新");}}
http://www.jsqmd.com/news/84723/

相关文章:

  • 5大实用技巧:用downkyi打造高效视频下载工作流
  • 百度网盘直链解析实战手册:突破限速封锁的完整解决方案
  • 电力负荷预测新思路:集成学习如何让澳大利亚电力数据“开口说话“?⚡
  • ClickHouse 快速入门
  • A little something to get you started
  • SmoothDiscreteMarchingCubes 多边形网格数据的平滑
  • AlignTwoPolyDatas 基于ICP算法的配准和相机视角切换
  • YOLOv11 改进 - C2PSA | C2PSA融合EDFFN高效判别频域前馈网络(CVPR 2025):频域筛选机制增强细节感知,优化复杂场景目标检测
  • Vue + Echarts 实现科技感数据大屏
  • 删除有序数组中的重复项(C++)
  • downkyi下载优先级终极指南:让你的重要视频先人一步
  • YOLOv11 改进 - C2PSA | C2PSA融合Mona多认知视觉适配器(CVPR 2025):打破全参数微调的性能枷锁:即插即用的提点神器,引领视觉微调新突破
  • 企业级部署:奇安信天擎在金融行业的实战案例
  • Windows右键菜单终极优化指南:让你的右键菜单重获新生
  • 百度网盘直链解析:新手必学的3步全速下载方法
  • 洛雪音乐PC版2.12.0| 最强电脑免费听歌软件,所有平台音乐都能听,需要导入音源
  • YOLOv11改进 - C3k2融合 | C3k2融DBlock解码器块( CVPR 2025 ) Decoder Block:解码器块,去模糊和提升图像清晰度
  • 正义荣耀圣戒 无限代金券买断
  • YOLOv11改进 - C3k2融合 | C3k2融合MambaOut(CVPR 2025),简洁高效的视觉模型基线
  • 【KMP算法】KMP算法揭秘:高效字符串匹配的艺术
  • ZTools v1.1.2:桌面应用启动器与搜索工具
  • 【Hadoop+Spark+python毕设】哮喘患者症状数据可视化分析系统、计算机毕业设计、包括数据爬取、数据分析、数据可视化、Hadoop、实战教学
  • ML-4360 3D视觉 笔记
  • 企业级Git仓库SSH连接安全最佳实践
  • 玩转 Flutter 自定义 Painter:从零打造丝滑的仪表盘动效与可视化图表
  • CSS Padding图解指南:小白也能懂的间距魔法
  • 基于SpringBoot的餐厅推荐系统 计算机毕业设计选题 计算机毕设项目 前后端分离 【源码-文档报告-代码讲解】
  • 禁用MinIO后的7种企业级替代方案评测
  • Kingbase KES常见问题排查与解决指南:从启动报错到性能优化
  • 互联网大厂Java面试:从Spring Boot到微服务架构的深度剖析