文章目录
一、整体流程图
简述流程如下:
- 用户登录 → 后端验证用户名密码;
- 验证通过 → 生成Token返回前端;
- 前端存储Token → 后续请求携带Token;
- 后端通过拦截器/过滤器校验Token。
二、后端实现(以 Spring Boot 为例)
1. 依赖引入(如 jjwt)
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
2. 生成Token工具类
package com.admin.utils;
import io.jsonwebtoken.*;
import java.util.Date;
public class JwtUtils {
private static final String SECRET_KEY = "mySecretKey123456";
private static final long EXPIRATION = 1000 * 60 * 60 * 24; // 1天
public static String generateToken(Long userId) {
return Jwts.builder()
.setSubject(userId.toString()) // 把用户ID放在sub字段里
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public static Long getUserIdFromToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
return Long.parseLong(claims.getSubject());
}
}
3.将用户id存储
将用户id和用户类型存储在SecurityContextHolder.getContext()中,方便后续获取。(这里我是根本我自己项目的需求存储的,因为未设置单独的角色表,所以把用户类型也存储了,方便后续业务的处理)
package com.admin.filter;
import com.admin.businessuser.constant.Type;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements Serializable {
private Long userId;
private Type type; // MEMBER / MERCHANT / ADMINISTRATOR
}
package com.admin.filter;
import com.admin.businessuser.domain.UserDO;
import com.admin.businessuser.mapper.UserMapper;
import com.admin.utils.JwtUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.Collections;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final UserMapper userMapper;
public JwtAuthenticationFilter(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (token != null && !token.isEmpty()) {
try {
Long userId = JwtUtils.getUserIdFromToken(token);
// 查数据库获取用户信息(你可以用 UserService 或 Mapper)
UserDO user = userMapper.selectById(userId); // 你自己项目里的 user 表
if (user != null) {
LoginUser loginUser = new LoginUser(userId, user.getType());
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
loginUser,
null,
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
// token 无效,继续走过滤链或返回401
}
}
filterChain.doFilter(request, response);
}
}
/**
后续获取代码:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
LoginUser loginUser= (LoginUser) authentication.getPrincipal();
Long userId = loginUser.getUserId();
/
4. 登录业务逻辑
@Override
public ResultVO<Map<String, Object>> login(UserLoginDTO userLoginDTO) {
String username = userLoginDTO.getUsername();
String password = userLoginDTO.getPassword();
LambdaQueryWrapper<UserDO> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(UserDO::getUsername, username)
.eq(UserDO::getPassword, SecureUtil.md5(password));
// 查询用户
UserDO user = userMapper.selectOne(wrapper);
if (user == null) {
return ResultVO.fail("用户名或密码错误");
}
if (Type.MEMBER.equals(user.getType())) {
return ResultVO.fail("非管理员及商家账号无法登录");
}
// 生成 JWT Token
String token = JwtUtils.generateToken(user.getId());
// 构建响应数据(不直接返回完整 UserDO)
Map<String, Object> data = new HashMap<>();
data.put("token", token);
Map<String, Object> userMap = new HashMap<>();
userMap.put("id", user.getId());
userMap.put("username", user.getUsername());
userMap.put("password", user.getPassword());
userMap.put("nickname", user.getNickname());
userMap.put("sex", user.getSex());
userMap.put("telephone", user.getTelephone());
userMap.put("avatar", user.getAvatar());
userMap.put("type", user.getType());
data.put("user", userMap);
return ResultVO.success(data);
}
@PostMapping("/login")
ResultVO<Map<String, Object>> login(@RequestBody UserLoginDTO userLoginDTO) {
return userService.login(userLoginDTO);
}
三、前端实现(以 Vue + Axios 为例)
1.前端登录后,通过将用户信息放在store中,使用pinia进行管理。
登录接口联调,登入成功获取token和用户信息:
const onSubmit = async ({
validateResult,
firstError,
}: {
validateResult: boolean | undefined;
firstError: string | undefined;
}) => {
if (validateResult !== true) {
MessagePlugin.warning(firstError || "未知错误");
return;
}
try {
const res = await login(formData);
if (res.code === 0) {
const token = res.data.token;
const user = res.data.user;
localStorage.setItem("token", token);
localStorage.setItem("user", JSON.stringify(user));
userStore.setUserInfo(token, user);
// 默认保存用户名和密码
localStorage.setItem("adminSavedUsername", formData.username);
localStorage.setItem("adminSavedPassword", formData.password);
MessagePlugin.success("登录成功");
router.push("order");
} else {
MessagePlugin.warning(res.message || "登录失败");
}
} catch (e) {
console.error("请求失败:", e);
MessagePlugin.warning("网络请求失败");
}
};
store文件夹下的user.ts文件。后续统一从这里获取用户信息。场景:例如用户个人信息模块。
import { defineStore } from "pinia";
export const useUserStore = defineStore("user", {
state: () => ({
token: "",
user: null as any,
}),
actions: {
setUserInfo(token: string, user: any) {
this.token = token;
this.user = user;
},
clearUserInfo() {
this.token = "";
this.user = null;
},
},
persist: true, // 使用 pinia-plugin-persist 持久化
});
2.请求拦截器设置请求头
目的:只有登录成功,或token未过期的情况下才能访问相应接口。token过去则需要重新登录才能访问。
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
import { MessagePlugin } from "tdesign-vue-next";
const service = axios.create({
// TODO
baseURL: import.meta.env.VITE_API_BASE, //基础访问路径,我是引用过来的
timeout: 10000,
withCredentials: true,
});
//请求拦截器
service.interceptors.request.use(
(config) => {
const token = localStorage.getItem("token");
if (token) {
config.headers["Authorization"] = token; // 使用 token 设置请求头
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
说明:本文只展示主要的流程,模糊处各位码友可以交流提供很好的方案,至于前端依赖的安装大家自己安装即可。