[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-public-WahvQp1v":3,"public-project-articles-WahvQp1v":17},{"id":4,"uuid":5,"project_id":6,"title":7,"content":8,"type":9,"status":10,"public_enabled":10,"views":11,"sort":12,"created_at":13,"updated_at":14,"project_title":15,"project_slug":16},777,"WahvQp1v",49,"09. Springboot3+vue3实现JWT登录鉴权","## 为什么要做鉴权？\n\n因为管理系统的数据是敏感的，隐私的，而且一般每个角色权限是不同的，所以必须在数据的增删改查操作的时候对访问的用户做权限验证。\n\n## 什么是 JWT？\n\n\u003Cfont style=\"color:rgb(64, 64, 64);\">JSON Web Token（JWT）是一种开放标准（RFC 7519），用于在网络应用间安全地传输信息。它以紧凑且自包含的方式，通过 JSON 对象在各方之间传递经过验证的信息。\u003C\u002Ffont>\n\n\n\nJWT 由三部分组成，用 `.` 分隔：\n\n**Header**（头部）：包含算法（如 HMAC SHA256 或 RSA）和令牌类型（固定为 `JWT`）。\n\n```plain\n{\n  \"alg\": \"HS256\",\n  \"typ\": \"JWT\"\n}\n```\n\n**Payload**（负载）：携带声明（如用户身份、权限、有效期等），分为三类：\n\n- **Registered claims**（预定义字段，如 `exp` 过期时间、`iss` 签发者）。\n- **Public claims**（公开自定义字段，需避免冲突）。\n- **Private claims**（私有字段，双方协商）。\n\n```plain\n{\n  \"sub\": \"1234567890\",\n  \"name\": \"John Doe\",\n  \"admin\": true\n}\n```\n\n+ **\u003Cfont style=\"color:rgb(64, 64, 64);\">Signature\u003C\u002Ffont>**\u003Cfont style=\"color:rgb(64, 64, 64);\">（签名）：对头部和负载的\u003C\u002Ffont>**\u003Cfont style=\"color:rgb(64, 64, 64);\">签名\u003C\u002Ffont>**\u003Cfont style=\"color:rgb(64, 64, 64);\">，防止数据篡改。\u003C\u002Ffont>\n\n## \u003Cfont style=\"color:rgb(64, 64, 64);\">集成 JWT\u003C\u002Ffont>\n\n```xml\n\u003Cdependency>\n    \u003CgroupId>com.auth0\u003C\u002FgroupId>\n    \u003CartifactId>java-jwt\u003C\u002FartifactId>\n    \u003Cversion>4.3.0\u003C\u002Fversion>\n\u003C\u002Fdependency>\n```\n\n## \u003Cfont style=\"color:rgb(64, 64, 64);\">生成 Token\u003C\u002Ffont>\n\n```java\n\u002F**\n * 生成token\n *\u002F\npublic static String createToken(String data, String sign) {\n    return JWT.create().withAudience(data) \u002F\u002F 将 userId-role 保存到 token 里面,作为载荷\n            .withExpiresAt(DateUtil.offsetDay(new Date(), 1)) \u002F\u002F 1天后token过期\n            .sign(Algorithm.HMAC256(sign)); \u002F\u002F 以 password 作为 token 的密钥, HMAC256算法加密\n}\n```\n\n## 在登录接口返回 token\n\nAdminService 的 login 方法\n\n```java\n\u002F\u002F 创建token并返回给前端\nString token = TokenUtils.createToken(dbAdmin.getId() + \"-\" + \"ADMIN\", dbAdmin.getPassword());\ndbAdmin.setToken(token);\n```\n\n\n\n## Token 的格式\n\n![](https:\u002F\u002Fcdn.nlark.com\u002Fyuque\u002F0\u002F2025\u002Fpng\u002F751015\u002F1740315545809-cfc48cc8-3e8d-49e9-a6d6-ca2b09977668.png)\n\n```vue\neyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxLUFETUlOIiwiZXhwIjoxNzQwNDAxOTI1fQ.zp2dX3A-Iv5lTv2seejmnHA24qQUPX0hXeXxHmiCZsI\n```\n\n## JWT 拦截器\n\nWebConfig\n\n```java\npackage com.example.common;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.servlet.config.annotation.InterceptorRegistry;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\n@Configuration\npublic class WebConfig implements WebMvcConfigurer {\n\n    @Override\n    public void addInterceptors(InterceptorRegistry registry) {\n        registry.addInterceptor(jwtInterceptor())\n                .addPathPatterns(\"\u002F**\")\n                .excludePathPatterns(\"\u002Flogin\", \"\u002Fregister\");\n    }\n\n    @Bean\n    public JWTInterceptor jwtInterceptor() {\n        return new JWTInterceptor();\n    }\n\n}\n\n```\n\n\n\n```java\npackage com.example.common;\n\nimport cn.hutool.core.util.StrUtil;\nimport com.auth0.jwt.JWT;\nimport com.auth0.jwt.JWTVerifier;\nimport com.auth0.jwt.algorithms.Algorithm;\nimport com.example.entity.Account;\nimport com.example.entity.Admin;\nimport com.example.exception.CustomerException;\nimport com.example.service.AdminService;\nimport com.example.service.UserService;\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.servlet.HandlerInterceptor;\n\n@Component\npublic class JWTInterceptor implements HandlerInterceptor {\n\n    @Resource\n    AdminService adminService;\n    @Resource\n    UserService userService;\n\n    @Override\n    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {\n        \u002F\u002F 1. 从请求头拿到token\n        String token = request.getHeader(\"token\");\n        if (StrUtil.isEmpty(token)) {\n            \u002F\u002F 如果没拿到，从参数里再拿一次\n            token = request.getParameter(\"token\");\n        }\n        \u002F\u002F 2. 认证token\n        if (StrUtil.isBlank(token)) {\n            throw new CustomerException(\"401\", \"您无权限操作\");\n        }\n        Account account = null;\n        try {\n            \u002F\u002F 拿到token 的载荷数据\n            String audience = JWT.decode(token).getAudience().get(0);\n            String[] split = audience.split(\"-\");\n            String userId = split[0];\n            String role = split[1];\n            \u002F\u002F 根据token解析出来的userId去对应的表查询用户信息\n            if (\"ADMIN\".equals(role)) {\n                account = adminService.selectById(userId);\n            } else if (\"USER\".equals(role)) {\n                account = userService.selectById(userId);\n            }\n        } catch (Exception e) {\n            throw new CustomerException(\"401\", \"您无权限操作\");\n        }\n        if (account == null) {\n            throw new CustomerException(\"401\", \"您无权限操作\");\n        }\n        try {\n            \u002F\u002F 验证签名\n            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(account.getPassword())).build();\n            jwtVerifier.verify(token);\n        } catch (Exception e) {\n            throw new CustomerException(\"401\", \"您无权限操作\");\n        }\n        return true;\n    }\n}\n\n```\n\n## 出现 401 错误，您无权限访问数据怎么办？\n\n![](https:\u002F\u002Fcdn.nlark.com\u002Fyuque\u002F0\u002F2025\u002Fpng\u002F751015\u002F1740316860873-535becdd-9421-407a-90c4-407e1046f669.png)\n\n在 vue 的 request.js 的拦截器里面 加上统一的请求头  token\n\n![](https:\u002F\u002Fcdn.nlark.com\u002Fyuque\u002F0\u002F2025\u002Fpng\u002F751015\u002F1740317016542-bdde9382-5e1c-4155-99b7-dd696fd32931.png)\n\n看网络请求，出现了 token\n\n![](https:\u002F\u002Fcdn.nlark.com\u002Fyuque\u002F0\u002F2025\u002Fpng\u002F751015\u002F1740317052155-804ef1e9-2bcb-4e8d-8746-0b7c0c7974e4.png)\n\n\n\n## request.js 的代码示例\n\n```javascript\nimport axios from \"axios\";\nimport {ElMessage} from \"element-plus\";\nimport router from \"@\u002Frouter\u002Findex.js\";\n\nconst request = axios.create({\n    baseURL: 'http:\u002F\u002Flocalhost:9999',\n    timeout: 30000  \u002F\u002F 后台接口超时时间\n})\n\n\u002F\u002F request 拦截器\n\u002F\u002F 可以自请求发送前对请求做一些处理\nrequest.interceptors.request.use(config => {\n    config.headers['Content-Type'] = 'application\u002Fjson;charset=utf-8';\n    let user = JSON.parse(localStorage.getItem('code_user') || '{}')\n    config.headers['token'] = user.token\n    return config\n}, error => {\n    return Promise.reject(error)\n});\n\n\u002F\u002F response 拦截器\n\u002F\u002F 可以在接口响应后统一处理结果\nrequest.interceptors.response.use(\n    response => {\n        let res = response.data;\n        \u002F\u002F 兼容服务端返回的字符串数据\n        if (typeof res === 'string') {\n            res = res ? JSON.parse(res) : res\n        }\n        if (res.code === '401') {\n            ElMessage.error(res.msg)\n            router.push('\u002Flogin')\n        } else {\n            return res\n        }\n    },\n    error => {\n        if (error.response.status === 404) {\n            ElMessage.error('未找到请求接口')\n        } else if (error.response.status === 500) {\n            ElMessage.error('系统异常，请查看后端控制台报错')\n        } else {\n            console.error(error.message)\n        }\n        return Promise.reject(error)\n    }\n)\n\nexport default request\n```\n\n## 解析 Token 获取用户信息\n\n```java\npackage com.example.utils;\n\nimport cn.hutool.core.date.DateUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.auth0.jwt.JWT;\nimport com.auth0.jwt.algorithms.Algorithm;\nimport com.example.entity.Account;\nimport com.example.mapper.AdminMapper;\nimport com.example.service.AdminService;\nimport com.example.service.UserService;\nimport jakarta.annotation.PostConstruct;\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletRequest;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.context.request.RequestContextHolder;\nimport org.springframework.web.context.request.ServletRequestAttributes;\n\nimport java.util.Date;\n\n@Component\npublic class TokenUtils {\n\n    @Resource\n    AdminService adminService;\n    @Resource\n    UserService userService;\n\n    static AdminService staticAdminService;;\n    static UserService staticUserService;\n\n    \u002F\u002F springboot工程启动后会加载这段代码\n    @PostConstruct\n    public void init() {\n        staticAdminService = adminService;\n        staticUserService = userService;\n    }\n\n\n    \u002F**\n     * 生成token\n     *\u002F\n    public static String createToken(String data, String sign) {\n        return JWT.create().withAudience(data) \u002F\u002F 将 userId-role 保存到 token 里面,作为载荷\n                .withExpiresAt(DateUtil.offsetDay(new Date(), 1)) \u002F\u002F 1天后token过期\n                .sign(Algorithm.HMAC256(sign)); \u002F\u002F 以 password 作为 token 的密钥, HMAC256算法加密\n    }\n\n    \u002F**\n     * 获取当前登录的用户信息\n     *\u002F\n    public static Account getCurrentUser() {\n        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();\n        String token = request.getHeader(\"token\");\n        if (StrUtil.isBlank(token)) {\n            token = request.getParameter(\"token\");\n        }\n        \u002F\u002F 拿到token 的载荷数据\n        String audience = JWT.decode(token).getAudience().get(0);\n        String[] split = audience.split(\"-\");\n        String userId = split[0];\n        String role = split[1];\n        \u002F\u002F 根据token解析出来的userId去对应的表查询用户信息\n        if (\"ADMIN\".equals(role)) {\n            return staticAdminService.selectById(userId);\n        } else if (\"USER\".equals(role)) {\n            return staticUserService.selectById(userId);\n        }\n        return null;\n    }\n\n}\n\n```\n\n\n\n在 service 方法里面 获取当前的登录用户信息\n\n```java\nAccount currentUser = TokenUtils.getCurrentUser();\n```\n\n","coding",1,7151,1523,"2025-02-23 21:58:00","2026-05-03 22:49:02","带小白做毕设2025系列课程","graduation-project-2025",{"project":18,"items":19},{"id":6,"title":15,"slug":16},[20,28,35,42,49,56,63,70,77,84,85,92,99,106,113,120,127,134,141,148,155],{"id":21,"uuid":22,"project_id":6,"title":23,"type":9,"status":10,"public_enabled":10,"views":24,"sort":25,"created_at":26,"updated_at":27,"project_title":15,"project_slug":16},766,"XmlcAcY0","00. 带小白做毕设2025课程介绍",19012,1512,"2025-02-22 15:29:01","2026-05-07 15:33:28.189425+00",{"id":29,"uuid":30,"project_id":6,"title":31,"type":9,"status":10,"public_enabled":10,"views":32,"sort":33,"created_at":34,"updated_at":14,"project_title":15,"project_slug":16},767,"nmjXCdVH","01. 前端Vue3 框架的快速搭建以及项目工程的讲解",15797,1513,"2025-02-13 17:13:40",{"id":36,"uuid":37,"project_id":6,"title":38,"type":9,"status":10,"public_enabled":10,"views":39,"sort":40,"created_at":41,"updated_at":14,"project_title":15,"project_slug":16},768,"pMdPrVeH","02. 使用Vue3集成Element-Plus快速搭建一个管理系统的页面框架",15959,1514,"2025-02-14 11:25:07",{"id":43,"uuid":44,"project_id":6,"title":45,"type":9,"status":10,"public_enabled":10,"views":46,"sort":47,"created_at":48,"updated_at":14,"project_title":15,"project_slug":16},771,"8PikYMQU","03. Springboot3框架的快速搭建以及项目工程的讲解",12768,1517,"2025-02-21 17:21:51",{"id":50,"uuid":51,"project_id":6,"title":52,"type":9,"status":10,"public_enabled":10,"views":53,"sort":54,"created_at":55,"updated_at":14,"project_title":15,"project_slug":16},772,"Q1TCG9Jj","04. Springboot3整合MyBatis实现数据库操作",11144,1518,"2025-03-07 15:50:30",{"id":57,"uuid":58,"project_id":6,"title":59,"type":9,"status":10,"public_enabled":10,"views":60,"sort":61,"created_at":62,"updated_at":14,"project_title":15,"project_slug":16},773,"De7YPnEc","05. Springboot3+vue3实现增删改查、分页查询、批量删除（上）",10827,1519,"2025-02-22 15:09:19",{"id":64,"uuid":65,"project_id":6,"title":66,"type":9,"status":10,"public_enabled":10,"views":67,"sort":68,"created_at":69,"updated_at":14,"project_title":15,"project_slug":16},774,"YKEHfsPd","06. Springboot3+vue3实现增删改查、分页查询、批量删除（下）",7760,1520,"2025-02-22 22:00:02",{"id":71,"uuid":72,"project_id":6,"title":73,"type":9,"status":10,"public_enabled":10,"views":74,"sort":75,"created_at":76,"updated_at":14,"project_title":15,"project_slug":16},775,"sNDKpWVJ","07. Springboot3+Vue3实现excel批量导入导出",6552,1521,"2025-02-23 10:49:24",{"id":78,"uuid":79,"project_id":6,"title":80,"type":9,"status":10,"public_enabled":10,"views":81,"sort":82,"created_at":83,"updated_at":14,"project_title":15,"project_slug":16},776,"1uMP9O6C","08. Springboot3+vue3实现登录注册功能",7964,1522,"2025-02-23 18:14:13",{"id":4,"uuid":5,"project_id":6,"title":7,"type":9,"status":10,"public_enabled":10,"views":11,"sort":12,"created_at":13,"updated_at":14,"project_title":15,"project_slug":16},{"id":86,"uuid":87,"project_id":6,"title":88,"type":9,"status":10,"public_enabled":10,"views":89,"sort":90,"created_at":91,"updated_at":14,"project_title":15,"project_slug":16},778,"QFFAqZh1","10. Springboot3+vue3实现文件上传和下载",6171,1524,"2025-02-24 14:16:27",{"id":93,"uuid":94,"project_id":6,"title":95,"type":9,"status":10,"public_enabled":10,"views":96,"sort":97,"created_at":98,"updated_at":14,"project_title":15,"project_slug":16},1278,"S2eL2g5L","11. Springboot3+vue3实现个人中心、修改密码",5945,1525,"2025-02-24 18:10:59",{"id":100,"uuid":101,"project_id":6,"title":102,"type":9,"status":10,"public_enabled":10,"views":103,"sort":104,"created_at":105,"updated_at":14,"project_title":15,"project_slug":16},1279,"LkN8Mmsn","12. Springboot3+Vue3实现系统公告功能",4967,1526,"2025-02-25 11:50:13",{"id":107,"uuid":108,"project_id":6,"title":109,"type":9,"status":10,"public_enabled":10,"views":110,"sort":111,"created_at":112,"updated_at":14,"project_title":15,"project_slug":16},1280,"i7wziuEN","13. Springboot3+Vue3实现角色权限控制",4446,1527,"2025-02-25 11:51:38",{"id":114,"uuid":115,"project_id":6,"title":116,"type":9,"status":10,"public_enabled":10,"views":117,"sort":118,"created_at":119,"updated_at":14,"project_title":15,"project_slug":16},1281,"pGwiTCRn","14. Springboot3+Vue3实现富文本编辑器功能",4578,1528,"2025-02-26 16:04:58",{"id":121,"uuid":122,"project_id":6,"title":123,"type":9,"status":10,"public_enabled":10,"views":124,"sort":125,"created_at":126,"updated_at":14,"project_title":15,"project_slug":16},1282,"tZ8iDql5","15. Springboot3+Vue3实现模块之间的关联",4454,1529,"2025-02-26 18:28:55",{"id":128,"uuid":129,"project_id":6,"title":130,"type":9,"status":10,"public_enabled":10,"views":131,"sort":132,"created_at":133,"updated_at":14,"project_title":15,"project_slug":16},1283,"gb01JPC2","16. Springboot3+Vue3实现echarts数据统计",4307,1530,"2025-03-03 16:58:21",{"id":135,"uuid":136,"project_id":6,"title":137,"type":9,"status":10,"public_enabled":10,"views":138,"sort":139,"created_at":140,"updated_at":14,"project_title":15,"project_slug":16},1284,"59bDkSFf","17. Springboot3+Vue3实现提交审核业务功能",3793,1531,"2025-03-04 11:58:16",{"id":142,"uuid":143,"project_id":6,"title":144,"type":9,"status":10,"public_enabled":10,"views":145,"sort":146,"created_at":147,"updated_at":14,"project_title":15,"project_slug":16},1285,"gApyb58X","18. Springboot3+Vue3实现预约审核业务功能",3332,1532,"2025-03-05 20:07:24",{"id":149,"uuid":150,"project_id":6,"title":151,"type":9,"status":10,"public_enabled":10,"views":152,"sort":153,"created_at":154,"updated_at":14,"project_title":15,"project_slug":16},1286,"XfpY5re0","19. Springboot3+Vue3实现前台首页的设计",3508,1533,"2025-03-05 20:08:12",{"id":156,"uuid":157,"project_id":6,"title":158,"type":9,"status":10,"public_enabled":10,"views":159,"sort":160,"created_at":161,"updated_at":14,"project_title":15,"project_slug":16},1287,"BnSPRBOc","20. Springboot3+Vue3实现前台轮播图和详情页的设计",4062,1534,"2025-03-17 17:13:36"]