09. Springboot3+vue3实现JWT登录鉴权 – 朝汐の小站
09. Springboot3+vue3实现JWT登录鉴权
本文最后更新于 287 天前,如有错误请邮件至 zhiligyi222na@gmail.com

为什么要做鉴权?

因为管理系统的数据是敏感的,隐私的,而且一般每个角色权限是不同的,所以必须在数据的增删改查操作的时候对访问的用户做权限验证。

什么是JWT?

JSON Web Token (JWT)是一种开放标准(RFC 7519),用于在网络应用间安全地传输信息,它以紧凑且自包含的方式,通过 JSON 对象在各方之间传递经过验证的信息。

JWT 由三部分组成,用 . 分隔:
Header(头部):包含算法(如 HMAC SHA256 或 RSA )和令牌类型(固定为JWT)

{
    "alg": "HS256",
    "typ": "JWT"
}

Payload(负载):携带声明(如用户身份、权限、有效期),分为三类:

  • Registered clanims (预定义字段,如 exp 过期时间、 iss 签发者)。
  • Public clanims (公开自定义字段, 需避免冲突)。
  • Private clanims (私有字段,双方协商)。
{
    "sub": "1234567890",
    "name": "John Doe",
    "admin": true
}
  • Signature(签名):对头部和负载的签名,防止数据篡改。

集成 JWT

<!-- JWT -->
<dependency>
   <groupId>com.auth0</groupId>
   <artifactId>java-jwt</artifactId>
   <version>4.3.0</version>
</dependency>

生成 Token

utils/TokenUtils.java

package org.example.springdemo.utils;

import cn.hutool.core.date.DateUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;

import java.util.Date;

public class TokenUtils {

    /**
     * 生成token
     */
    public static String createToken(String data, String sign) {
        return JWT.create().withAudience(data) // 将 UserId-role 保存到 token 里面,作为载荷
                .withExpiresAt(DateUtil.offsetDay(new Date(),1)) // 1天后token过期
                .sign(Algorithm.HMAC256(sign)); // 以 password 作为 token 的密钥, HMAC256 加密算法
    }
}

在登录接口返回token

AdminService 的 login 方法

//创建token并返回给前端
String token = TokenUtils.createToken(dbAdmin.getId() + "-" + "ADMIN", dbAdmin.getUsername());
dbAdmin.setToken(token);

Token的格式

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxLUFETUlOIiwiZXhwIjoxNzU3NzI3MjU1fQ.NUUpZ6X7MPIsUzxnBvD13fTUUIPNQBgSel35As7Of4A

JWT拦截器

common/WebConfig

package org.example.springdemo.common;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/login", "/register");
    }

    @Bean
    public JWTInterceptor jwtInterceptor() {
        return new JWTInterceptor();
    }
}
common/JWTInterceptor
package org.example.springdemo.common;

import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.example.springdemo.entity.Account;
import org.example.springdemo.exception.CustomerException;
import org.example.springdemo.service.AdminService;
import org.example.springdemo.service.UserService;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class JWTInterceptor implements HandlerInterceptor {

    @Resource
    AdminService adminService;
    @Resource
    UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 从请求头拿到 token
        String token = request.getHeader("token");
        if (StrUtil.isEmpty(token)){
            // 如果没拿到,从参数里再那一次
            token = request.getParameter("token");
        }
        // 2. 认证 token
        if (StrUtil.isBlank(token)){
            throw new CustomerException("401", "您无权限操作");
        }
        Account account = null;
        try {
            // 拿到 token 的载荷数据
            String audience = JWT.decode(token).getAudience().get(0);
            String[] split = audience.split("-");
            String userId = split[0];
            String role = split[1];
            // 根据 token 解析出来的 userId 去对应的表查询用户信息
            if ("ADMIN".equals(role)) {
                account = adminService.selectById(userId);
            } else if ("USER".equals(role)){
                account = userService.selectById(userId);
            }
        } catch (Exception e) {
            throw new CustomerException("401", "您无权限操作");
        }
        if (account == null) {
            throw new CustomerException("401", "您无权限操作");
        }

        try {
            //验证签名
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(account.getPassword())).build();
            jwtVerifier.verify(token);
        } catch (Exception e) {
            throw new CustomerException("401", "您无权限操作");
        }
        return true;
    }
}

出现401错误,您无权限访问数据

在 vue 的 request.js 的拦截器里面加上统一的请求头 token

看网络请求,出现了 token

request.js的代码示例

import axios from "axios";
import {ElMessage} from "element-plus";
import router from "@/router/index.js";

const request = axios.create({
    baseURL: "http://localhost:9999",
    timeout: 3000   //后台接口超时时间
})

//request拦截器
//可以自请求发送前对请求做一些处理
request.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'application/json;charset=utf-8';
    let user = JSON.parse(localStorage.getItem('code_user') || '{}');
    config.headers['token'] = user.token
    return config
}, error => {
    return Promise.reject(error)
});

//response拦截器
//可以在接口响应后对数据统一处理
request.interceptors.response.use(
    response => {
        let res = response.data;
        if(typeof res == 'string'){
            res = res ? JSON.parse(res) : res
        }
        if (res.code === '401'){
            ElMessage.error(res.msg)
            router.push('/login')
        } else {
            return res
        }
    },
    error => {
        if (error.response) {
            if (error.response.status === 404) {
                ElMessage.error("未找到请求接口")
            } else if (error.response.status === 500) {
                ElMessage.error("系统异常,请查看后端控制台报错")
            }
        } else {
            ElMessage.error("网络错误,请检查连接")
            console.error(error.message)
        }
        return Promise.reject(error)
    }
)

export default request

解析 Token 获取用户信息

utils/TokenUtils

package org.example.springdemo.utils;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.xmlbeans.impl.xb.xsdschema.Attribute;
import org.example.springdemo.entity.Account;
import org.example.springdemo.service.AdminService;
import org.example.springdemo.service.UserService;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.Date;

@Component
public class TokenUtils {

    @Resource
    AdminService adminService;
    @Resource
    UserService userService;

    static AdminService staticAdminService;
    static UserService staticUserService;

    // springboot 工程启动后会加载这段代码
    @PostConstruct
    public void init() {
        staticAdminService = adminService;
        staticUserService = userService;
    }

    /**
     * 生成token
     */
    public static String createToken(String data, String sign) {
        return JWT.create().withAudience(data) // 将 UserId-role 保存到 token 里面,作为载荷
                .withExpiresAt(DateUtil.offsetDay(new Date(),1)) // 1天后token过期
                .sign(Algorithm.HMAC256(sign)); // 以 password 作为 token 的密钥, HMAC256 加密算法
    }

    /**
     * 获取当前登录的用户信息
     */
    public static Account getCurrentUser() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String token = request.getHeader("token");
        if (StrUtil.isBlank(token)) {
            token = request.getParameter("token");
        }

        // 拿到 token 的载荷数据
        String audience = JWT.decode(token).getAudience().get(0);
        String[] split = audience.split("-");
        String userId = split[0];
        String role = split[1];
        // 根据 token 解析出来的 userId 去对应的表查询用户信息
        if ("ADMIN".equals(role)) {
            return staticAdminService.selectById(userId);
        } else if ("USER".equals(role)){
            return staticUserService.selectById(userId);
        }
        return null;
    }

}

在 service 方法里面获取当前的登录用户信息

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇