news 2026/5/26 21:00:13

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/26 7:29:01

企业级部署:奇安信天擎在金融行业的实战案例

快速体验 打开 InsCode(快马)平台 https://www.inscode.net输入框内输入如下内容&#xff1a; 设计一个金融行业专用的奇安信天擎部署方案。包括网络架构图&#xff08;DMZ、内网分区&#xff09;、策略配置模板&#xff08;如文件监控、进程控制、漏洞防护&#xff09;、应急…

作者头像 李华
网站建设 2026/5/26 4:37:57

【牛客周赛 107】E 题【小苯的刷怪笼】题解

题目链接 题目大意 给定三个正整数 n,a,kn, a, kn,a,k&#xff0c;其中&#xff1a; nnn 为怪物的数量&#xff0c;nnn 个怪物站成一排&#xff0c;从左到右编号 111 到 nnn&#xff1b;aaa 为 nnn 个怪物的血量和&#xff0c;且每个怪物的血量都是正数&#xff1b;kkk 为小苯…

作者头像 李华
网站建设 2026/5/26 4:28:28

5大技巧让DownKyi成为你的B站视频下载神器

5大技巧让DownKyi成为你的B站视频下载神器 【免费下载链接】downkyi 哔哩下载姬downkyi&#xff0c;哔哩哔哩网站视频下载工具&#xff0c;支持批量下载&#xff0c;支持8K、HDR、杜比视界&#xff0c;提供工具箱&#xff08;音视频提取、去水印等&#xff09;。 项目地址: h…

作者头像 李华
网站建设 2026/5/26 4:41:30

java计算机毕业设计陕商院餐厅管理系统 高校智慧食堂订餐与后台运营一体化平台 面向校园多餐厅的在线点餐与膳食服务系统

计算机毕业设计陕商院餐厅管理系统n1c029&#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。后疫情时代&#xff0c;陕商院实行错峰就餐&#xff0c;传统窗口排长队、纸质登记易交叉…

作者头像 李华
网站建设 2026/5/25 13:33:13

互联网大厂Java面试:从Spring Boot到微服务架构的深度剖析

场景描述 在某个晴朗的下午&#xff0c;超好吃来到了一家知名互联网大厂面试Java开发岗位。面试官是一位资深的技术专家&#xff0c;他对超好吃的简历表现出了极大的兴趣。 第一轮面试&#xff1a;基础技术与框架 面试官&#xff1a; “我们先从基础开始&#xff0c;超好吃&…

作者头像 李华