关于JWT的原理,这里不细说。以一个例子来阐述在uniapp中使用JWT从后端到前端的过程,代码仅用于帮助说明问题,所以只列出其中核心部分。这里假定对SpringBoot有些了解。
1. 创建表及相应的mapper,service,controller等类。
在MySQL中创建user表:
字段 | 数据类型 | 说明 |
---|---|---|
Id | Int(11) | 主键,自动增长 |
username | varchar(50) | 账号 |
password | varchar(50) | 密码 |
varchar(50) | 邮箱 | |
create_time | datetime | 创建时间 |
modify_time | datetime | 修改时间 |
实体类:User.java,放在pojo包下面。
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "Id", type = IdType.AUTO)
private Long id;
private String username;
private String password;
private String email;
private LocalDateTime createTime;
private LocalDateTime modifyTime;
}
mapper类:UserMapper.java,放在mapper包下面。
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface UserMapper extends BaseMapper<User> {
}
UserMapper.xml文件:放在resources/mybatis文件夹下面。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.edu.hbue.mapper.UserMapper">
</mapper>
业务层接口:IUserService.java,放在service包下面。
import com.baomidou.mybatisplus.extension.service.IService;
public interface IUserService extends IService<User> {
}
业务实现类UserServiceImple.java, 放在service/impl包下面。
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
private UserMapper userMapper;
}
控制层:UserController.java,放在controller包下面。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userService;
}
创建dto包,在该包下面创建 LoginDTO.java:
import lombok.Data;
@Data
public class LoginDTO {
private String username;
private String password;
}
2. 后端生成token
在pom.xml中加入相关的依赖:
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
在util包下面创建JWTUtils.java文件,代码如下:
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
//基于jsonwebtoken包生成token和解析token的工具类比较多
//此处代码参考up主mszl
public class JWTUtils {
private static final String jwtToken = "1234567890!@#$$";
public static String createToken(Long userId){
Map<String,Object> claims = new HashMap<>();
claims.put("userId",userId);
JwtBuilder jwtBuiler = Jwts.builder()
.signWith(SignatureAlgorithm.HS256,jwtToken)
.setClaims(claims)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis()+24*60*60*1000));
String token = jwtBuiler.compact();
return token;
}
public static Map<String, Object> checkToken(String token){
try {
Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token);
return (Map<String,Object>)parse.getBody();
}catch (Throwable e) {
e.printStackTrace();
}
return null;
}
}
给IUserService增加一个方法:
R login(LoginDTO loginDTo);
这里的R是一个用于返回给前端的JavaBean类,这个类的几个属性对前端非常重要。
@Data
public class R {
private long status;
private String describe ;
private Object message;
// 成功的数据结构封装 带对象数据
public static R success(String describe,Object message){
return new R(200,describe,message);
}
// 失败返回的数据 带对象数据
public static R error(String describe, Object message){
return new R(500,describe,message);
}
}
相应地给UserServiceImpl类增加实现方法:
@Override
public R login(LoginDTO loginDTO) {
String username = loginDTO.getUsername();
String password = loginDTO.getPassword();
if(StringUtils.isBlank(username)|| StringUtils.isBlank(password)){
return R.error("用户名或密码为空");
}
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername,username);
queryWrapper.eq(User::getPassword,password);
User user = this.getOne(queryWrapper);
if(user == null){
return R.error("用户名或密码错误");
}
String token = JWTUtils.createToken(user.getId());
return R.success("登录成功",token);
}
给UserController增加方法:
@PostMapping("/login")
public R login(@RequestBody LoginDTO loginDTO){
return userService.login(loginDTO);
}
3. 前端收到token后的处理
构建一个登录表单,注意uni.request()中data参数所传递的数据与LoginDTO类,其回调函数res跟R类的关系。
<template>
<view>
<view >
<input class="border" type="text" placeholder="请输入姓名..." v-model="message.username">
</view>
<view>
<input class="border" type="text" password="true" placeholder="请输入密码..." v-model="message.password">
</view>
<view>
<button type="primary" @click="onSubmit">登录</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
message:{
username:"",
password:"",
}
};
},
methods:{
uni.request({
url:"http://localhost:8090/user/login",
method:"POST",
data:{
username:this.message.username,
password:this.message.password
},
success:(res)=>{
console.log(res);
if(res.data.status!=200){
uni.showToast({
title:res.data.describe
});
}else{
// 后端生产的token,前端收到后保存在本地缓存里
uni.setStorageSync("token",res.data.message);
}
},
fail:(err)=>{
}
});
}
}
</script>
4. 前端每次发送请求要带上token
uniapp程序向后端发出请求,需要将收到的token写在请求头的header里面。
getUserInfo(){
uni.request({
url:"http://localhost:8090/user/currentuser",
header:{
Authorization:uni.getStorageSync("token")
},
success: (res) => {
console.log(res);
}
});
给UserController增加方法:
@RequestMapping(value="/currentuser")
public R getUserInfo(@RequestHeader("Authorization") String token){
User user = userService.getUserByToken(token);
if(user == null){
return R.error("token有误");
}
return R.success("成功取得用户信息", user);
}
给IUserSevice新增getUserByToken()方法:
User getUserByToken(String token);
相应的UserServiceImpl的实现方法如下:
@Override
public User getUserByToken(String token) {
if(StringUtils.isBlank(token)){
return null;
}
Map<String,Object> stringObjectMap = JWTUtils.checkToken(token);
if(stringObjectMap == null){
return null;
}
// 此处要根据userId查询用户
int id = (Integer) stringObjectMap.get("userId");
Long userId = new Long(id);
User user = this.getById(userId);
return user;
}
此时后端并未对请求进行拦截处理。
5. 对前端发送来的请求使用拦截器检查token
新建interceptor包,并创建JwtInterceptor.java:
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
public class JwtInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(!(handler instanceof HandlerMethod)){
return true;
}
if(StringUtils.isBlank(token)){
R r = R.error("未登录");
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JSON.toJSONString(r));
return false;
}
return true;
}
}
注册拦截器,在config包下面创建WebMVCConfig.java类:
@Configuration
public class WebMVCConfig implements WebMvcConfigurer {
@Autowired
private JwtInterceptor jwtInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
List<String> pathPatterns=new ArrayList<>();
pathPatterns.add("/user/currentuser");
List<String> excludePathPatterns=new ArrayList<>();
excludePathPatterns.add("/user/login");
excludePathPatterns.add("/user/register");
registry.addInterceptor(jwtInterceptor) //添加拦截器
.addPathPatterns(pathPatterns) //添加要拦截的url
.excludePathPatterns(excludePathPatterns); //添加不需要拦截的url
}
}
小结:
(1)JWT的重心在后端。在本例中,在很多地方做了简化处理,比如生成的JWT用诸如Redis这样的缓存存储起来,有利于提高效率;对前端传递来的token进行检验,本例仅只是从判空的角度做的。
(2)uniapp前端也有许多改进的地方,比如在前端使用拦截器,对发出的请求统一处理,而不是重复地在uni.request()中写上header。再如对接收到的token可以使用Vuex来处理等。