当前位置: 首页 > 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.gsyq.cn/news/94950.html

相关文章:

  • ClickHouse 快速入门
  • A little something to get you started
  • YOLOv11 改进 - C2PSA | C2PSA融合EDFFN高效判别频域前馈网络(CVPR 2025):频域筛选机制增强细节感知,优化复杂场景目标检测
  • Vue + Echarts 实现科技感数据大屏
  • SmoothDiscreteMarchingCubes 多边形网格数据的平滑
  • YOLOv11 改进 - C2PSA | C2PSA融合Mona多认知视觉适配器(CVPR 2025):打破全参数微调的性能枷锁:即插即用的提点神器,引领视觉微调新突破
  • YOLOv11 改进 - SPPF模块 | 替代SPPF, Mona多认知视觉适配器(CVPR 2025):打破全参数微调的性能枷锁:即插即用的提点神器
  • 百度网盘直链解析:新手必学的3步全速下载方法
  • 【KMP算法】KMP算法揭秘:高效字符串匹配的艺术
  • CSS Padding图解指南:小白也能懂的间距魔法
  • KL按键映射文件修改
  • 智驾相关名词简介
  • 面向对象程序设计———数字电路模拟程序1、2与第一次课堂测验总结
  • 传统统计 “手忙脚乱” VS 虎贲等考 AI “一键洞察”:数据分析的革命性分水岭
  • document.querySelector在电商网站中的5个实战应用
  • SK海力士×NVIDIA联手,AI NAND性能狂飙30倍!
  • C 标准库 - <locale.h>
  • 单片机芯片] CH32V307 支持手机的虚拟U盘实现拖拽固件升级
  • 【规范驱动的开发方式】之【spec-kit】 的安装入门指南
  • 基于ipsec的医院网络规划设计与实现
  • 电商评论分析实战:Java + NLP 大模型,从 10 万条评论中自动提取“用户槽点”
  • ISCTF2025-病毒分析
  • [数据结构/Java] 数据结构之循环队列
  • 检索增强生成(RAG)技术原理深度解析:突破大模型知识边界的范式革命
  • 基于springboot的技术博客交流系统的设计与实现
  • 基于springboot的运动服服饰销售购买商城系统
  • 英语口语资源合集
  • 如何用DSPy优化RAG prompt示例
  • 鸿蒙PC UI控件库 - TextInput 文本输入框详解
  • 鸿蒙PC UI控件库 - PasswordInput 密码输入框详解