【Vue2+SpringBoot在线商城】02-连接前端登录功能与后端接口

【Vue2+SpringBoot在线商城】02-连接前端登录功能与后端接口

本项目是基于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() // 开始构建JWT .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.4 实现一个简单的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; // 返回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 输入错误的账号/密码

得到对应的错误提示

Could not load content