本项目是基于 Vue2+SpringBoot+Mysql 制作的在线商城项目。
之前已经实现了后端 Result,也实现了前后端的连通测试。接下来先逐步实现登录功能。不过目前先不连接数据库,先把登录的相关接口和 MD5 加密以及其他必备的功能先写好,然后后续再对接数据库。
一、后端实现
1.1 添加必要依赖
在 pom.xml 中,添加这两个依赖。
jjwt 这个依赖用于生成/校验 Token。后续在 JwtUtil 会用到。hutool-all 是用于做 md5 加密
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.24</version>
</dependency>
1.2 实现 DateTool
用于返回当前时间。
package com.xmut.backend.utils;
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class DateTool {
/**
* 返回当前时间
*
* @return yyyy-MM-dd HH:mm:ss
*/
public static String getCurrTime() {
SimpleDateFormat longFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar calendar = Calendar.getInstance();
return longFormat.format(calendar.getTime());
}
}
1.3 实现 JwtUtil 工具类
1.3.1 定义相关常量
将过期时间设置为 24 小时
private static long EXPIRATION_TIME = 3600000 * 24; // 过期时间:24 小时(3600000 毫秒=1 小时)
暂时先将密钥写死
private static String SECRET = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjY34DFDSSSd";
定义两个常量
private static final String USER_ID = "id"; // 用户 ID 在 JWT 中的 key
private static final String TIME = "currentTime"; // 时间戳在 JWT 中的 key
1.3.2 实现 token 生成方法
public static String generateToken(String id) {
HashMap<String, Object> map = new HashMap<String, Object>(); // 创建一个 hashmap 存储信息
map.put(USER_ID, id); // 将用户 ID 放入
String jwt = Jwts.builder()
.setClaims(map) // 把用户信息放入 JWT
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // 设置过期时间
.signWith(SignatureAlgorithm.HS512, SECRET) // 使用 HS512 算法和密钥签名
.compact(); // 生成最终的 JWT 字符串
return jwt;
}
这个 put 动作,只在 Java 内存里操作,跟 JWT 还没关系,可以 put 很多次,放很多信息。 而后面的 setClaims 动作,则是将备好的信息交给 JWT 生成器,而且只能设置一次 claims。
1.3.3 实现带时间戳的 token 生成方法
代码逻辑和刚刚的 token 生成方法基本一致。多存储了一个当前的时间戳。
public static String generateTokenByTime(String id) {
HashMap<String, Object> map = new HashMap<String, Object>();
map.put(USER_ID, id);
map.put(TIME, DateTool.getCurrTime());
String jwt = Jwts.builder().setClaims(map)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET).compact();
return jwt;
}
1.3.4 实现 token 验证方法
public static Result validateToken(String token) {
Result result = new Result();
if (token == null || "".equals(token.trim())) {
result.fail("missing token");
return result;
}
try {
Map<String, Object> body = Jwts.parser().setSigningKey(SECRET)
.parseClaimsJws(token).getBody();
Object idObj = body.get(USER_ID);
String id = idObj == null ? null : String.valueOf(idObj);
if (id == null || "".equals(id.trim())) {
result.fail("Wrong token without id");
return result;
}
result.setData(id);
return result;
} catch (Exception e) {
result.fail(e.toString());
return result;
}
}
1.4 完善 User 实体类
刚刚为了测试前后端的连接,只设计了 name 字段,现在将其他需要的也补充上来,包括对应的 getter 和 setter 方法。
package com.xmut.backend.entity;
/**
* 测试用实体类
*/
public class User {
// 用于 postJson 测试
private String name;
// 用于 login 测试
private String id;
private String username;
private String password;
private String salt;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getSalt() { return salt; }
public void setSalt(String salt) { this.salt = salt; }
}
1.5 实现一个简单的 UserController
@RequestBody 将请求的 JSON 体自动转换为 User 对象
package com.xmut.backend.controller;
import cn.hutool.crypto.digest.DigestUtil;
import com.xmut.backend.entity.User;
import com.xmut.backend.utils.JwtUtil;
import com.xmut.backend.utils.Result;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 登录接口
* 说明:目前先不接数据库,先写死一个账号,跑通从登录到返回 token 的过程
*/
@RestController
@RequestMapping("/kmall/user")
public class UserController {
// 先写死一个账号
private static final String FIXED_ID = "1";
private static final String FIXED_USERNAME = "admin";
private static final String FIXED_SALT = "kmall_salt";
private static final String FIXED_PASSWORD_MD5 = DigestUtil.md5Hex("123456" + FIXED_SALT);
@PostMapping("/login")
public Result login(@RequestBody User user) {
Result result = new Result();
// 从 User 对象中获取用户名和密码
String username = user == null ? null : user.getUsername();
String password = user == null ? null : user.getPassword();
// 检查用户名是否为 null 或空字符串
if (username == null || "".equals(username.trim())) {
result.fail("用户不存在");
return result;
}
// 验证用户名是否等于固定的"admin",也就是刚刚写死的用户名
if (!FIXED_USERNAME.equals(username.trim())) {
result.fail("用户不存在");
return result;
}
// 计算用户输入密码的 MD5 值,同样也是要加上盐值
String md5Password = DigestUtil.md5Hex((password == null ? "" : password) + FIXED_SALT);
// 比较计算出的 MD5 值与预先计算的固定 MD5 值,如果不匹配,返回密码错误
if (!FIXED_PASSWORD_MD5.equals(md5Password)) {
result.fail("密码错误");
return result;
}
// 成功处理
String token = JwtUtil.generateTokenByTime(FIXED_ID);
// 调用 JWT 工具类生成 token,传入刚刚写死的 FIXED_ID
result.setMessage("登录成功");
result.setData(token);
// 将生成的 token 放入 result 的 data 字段
return result;
}
}
二、前端实现
2.1 实现用户登录的接口 /api/userApi.js
import { postJson } from './index'
// 用户相关接口(module 层)
// 后端:POST /kmall/user/login
export function loginRequest(username, password) {
return postJson('/kmall/user/login', {
username: (username || '').trim(),
password: (password || '').trim()
})
}
2.2 添加相关的测试按钮

2.3 添加必要的数据、函数
先填充好正确的测试账号和密码,可以方便测试。
testUsername: 'admin',
testPassword: '123456',
testLoginRequest: function () {
var self = this // 保存 this 引用
var u = (self.testUsername || '').trim() // 获取用户名
var p = (self.testPassword || '').trim()
loginRequest(u, p) // 调用登录函数,也就是刚刚在 userApi 里面定义的
.then(function (response) {
// 当 loginRequest 成功完成时,执行此回调函数
var result = response.data // 提取响应数据
self.handleApiResult(result) // 调用结果处理方法
})
.catch(function (error) {
// 处理异步失败
self.lastResultText = 'error: ' + error // 更新错误显示
console.log('error:' + error) // 在控制台输出错误信息
})
},
三、测试
3.1 输入正确的账号密码
此处 data 返回的是一个 JWT 的 token 字符串,由三段组成(用.分隔):
- 第 1 段:header(说明用的算法,比如
HS512) - 第 2 段:payload(放进去的内容,比如 id,currentTime, exp)
- 第 3 段:signature(用密钥做的签名,用来防篡改)

可以解码看看:

3.2 输入错误的账号/密码
得到对应的错误提示



