手拉手后端Springboot整合JWT

环境介绍

技术栈

springboot+mybatis-plus+mysql+java-jwt

软件

版本

mysql

8

IDEA

IntelliJ IDEA 2022.2.1

JDK

1.8

Spring Boot

2.7.13

mybatis-plus

3.5.3.2

Json Web令牌简称JWT

Token是在服务端产生的一串字符串是客户端访问资源接口(AP)时所需要的资源凭证。

Token认证

Token是在服务端产生的一串字符串是客户端访问资源接口(AP)时所需要的资源凭证。

Token认证流程

1、客户端使用用户名跟密码请求登录,服务端收到请求,验证用户名与密码验证成功后,服务端会签发一个 token并把这个 token发送给客户端,客户端收到 token后,会把它存储起来,比如放在cookie里或者localStorage里

2、客户端每次向服务端请求资源的时候需要带着服务端签发的 token

3、服务端收到请求,然后去验证客户端请求里面带着的 token,如果验证成功就向客户端返回请求的数据

token用户认证是一种服务端无状态的认证方式,服务端不用存放token数据。

用解析 token的计算时间换取 session的存储空间,从而减服务器的力,减少频繁的查询数据库

token完全由应用管理,所以它可以避开同源策略

JWT的使用

JSON Web Token(简称JWT)是一个 token的具体实现方式,是目前最流行的跨域认证解决方案。JWT的原理是:服务器认证以后,生成一个JSON对象,发回给用户。

{

       “name” :”张三”,

       “time”:”2022年10月10日”

}

用户与服务端通信时,都要发回该JSON对象。服务器完全只靠这个对象认定用户身份。

为防止用户篡改数据,服务器在生成对象时,会加上签名

JWT由三个部分组成:Header(头部)、Payload(负载)、Signature(签名)

Header.Payload.Signature

官方描述

Header

JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存

{

  "alg": "HS256",

  "typ": "JWT"

}

Payload

有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择

iss:发行人

exp:到期时间

sub:主题

aud:用户

nbf:在此之前不可用

iat:发布时间

jti:JWT ID用于标识该JWT

Signature

签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。

加入依赖

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

数据库

实体类

package com.example.domain;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;/*** * @TableName user*/
@TableName(value ="user")
@Data
public class User implements Serializable {/*** 用户id*/@TableId(type = IdType.AUTO)private Integer uid;/*** 用户名*/private String username;/*** 密码*/private String password;/*** 盐值*/private String salt;/*** 电话号码*/private String phone;/*** 电子邮箱*/private String email;/*** 性别:0-女,1-男*/private Integer gender;/*** 头像*/private String avatar;/*** 是否删除:0-未删除,1-已删除*/private Integer isDelete;/*** 日志-创建人*/private String createdUser;/*** 日志-创建时间*/private Date createdTime;/*** 日志-最后修改执行人*/private String modifiedUser;/*** 日志-最后修改时间*/private Date modifiedTime;@TableField(exist = false)private static final long serialVersionUID = 1L;
}

mapper(dao)

package com.example.mapper;import com.example.domain.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;import java.util.Date;
@Mapper
public interface UserMapper extends BaseMapper<User> {/*** 插入用户数据* @param user 用户数据* @return 受影响的行数*/int insert(User user);/*** 根据用户名查询用户是否存在* @param username* @return  成功返回单个用户数据,否返回null*/User findByUserName(@Param("username") String username);/*** 根据uid查询* @param uid* @return*/User findByUid(@Param("uid") Integer uid);/*** 更新用户个人资料信息* @param user* @return*/Integer updateUserInfoByUid(User user);/*** 根据用户id修改密码* @param uid* @return password=?,modified_user=?,modified_time=?*/Integer updatePasswordByUid(@Param("uid")Integer uid,@Param("password")String password,@Param("modified_user")String modified_user,@Param("modified_time") Date modified_time);
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper"><resultMap id="UserPojoMap" type="com.example.domain.User"><id column="uid" property="uid"></id><result column="is_delete" property="isDelete"></result><result column="created_user" property="createdUser"></result><result column="created_time" property="createdTime"></result><result column="modified_user" property="modifiedUser"></result><result column="modified_time" property="modifiedTime"></result></resultMap><!--    useGeneratedKeys="true" 开启主键自增 keyProperty="uid" 指定uid字段--><insert id="insert" useGeneratedKeys="true" keyProperty="uid">insert into user(username,password,salt,phone,email,gender,avatar,is_delete,created_user,created_time,modified_user,modified_time)values(#{username},#{password},#{salt},#{phone},#{email},#{gender},#{avatar},#{isDelete},#{createdUser},#{createdTime},#{modifiedUser},#{modifiedTime})</insert><select id="findByUserName" resultMap="UserPojoMap">select * from user where username=#{username}</select><select id="findByUid" resultMap="UserPojoMap">select * from user where uid=#{uid}</select><update id="updatePasswordByUid">update user set password=#{password},modified_user=#{modified_user},modified_time=#{modified_time} where uid=#{uid}</update><update id="updateUserInfoByUid">update user set<if test="phone!=null">phone=#{phone},</if><if test="email!=null">email=#{email},</if><if test="gender!=null">gender=#{gender},</if>modified_user=#{modifiedUser}, modified_time=#{modifiedTime} where uid=#{uid}</update><sql id="Base_Column_List">uid,username,password,salt,phone,email,gender,avatar,is_delete,created_user,created_time,modified_user,modified_time</sql>
</mapper>

service

package com.example.service;import com.example.domain.User;
import com.baomidou.mybatisplus.extension.service.IService;
public interface UserService extends IService<User> {/*** 用户注册方法* @param user*/void reg(User user);/*** 用户登入方法* @param username* @param password* @return*/User login(String username,String password);/*** 根据uid查询* @param uid* @return User*//**** @param uid* @param username* @param oldPassword* @param newPassword*/void changePassword(Integer uid,String username,String oldPassword,String newPassword);/*** 通过uid获取用户数据* @param uid* @return*/User getUserInfoByUid(Integer uid);/*** 修改用户信息* @param user* @return*/void changeUserInfo(Integer uid,String username,User user);}

ServiceImpl

package com.example.service.impl;import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.domain.User;
import com.example.service.UserService;
import com.example.mapper.UserMapper;
import com.example.service.exception.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;import java.util.Date;
import java.util.UUID;
@Service
@DS("wms")
public class UserServiceImpl extends ServiceImpl<UserMapper, User>implements UserService{@Autowiredprivate UserMapper userDao;@Overridepublic void reg(User user) {//判断用户是否被注册过String username = user.getUsername();User byUserName = userDao.findByUserName(username);if (byUserName == null){//密码的加密处理:MD5算法//盐值+password+盐值String oldPassword =user.getPassword();//获取盐值String salt = UUID.randomUUID().toString().toUpperCase();//保存盐值user.setSalt(salt);String newPassword = getMD5Password(oldPassword,salt);user.setPassword(newPassword);//用户注册//        is_delete INT COMMENT '是否删除:0-未删除,1-已删除',//        created_user VARCHAR(20) COMMENT '日志-创建人',//        created_time DATETIME COMMENT '日志-创建时间',//        modified_user VARCHAR(20) COMMENT '日志-最后修改执行人',//        modified_time DATETIME COMMENT '日志-最后修改时间',Date nowTime=new Date();user.setIsDelete(0);user.setCreatedUser(user.getUsername());user.setCreatedTime(nowTime);user.setModifiedUser(user.getUsername());user.setModifiedTime(nowTime);Integer rows = userDao.insert(user);if (rows == 0 ){throw  new InsertException("注册失败(未知失败)请重新注册");};}else {throw new UsernameOccupyException("用户名被占用");}}@Overridepublic User login(String username,String password) {User UserLogin = userDao.findByUserName(username);//盐值认证String md5Password = getMD5Password(password, UserLogin.getSalt());if (UserLogin == null){throw new UserNullException("用户不存在");}//检测密码是否匹配if (!UserLogin.getPassword().equals(md5Password)){throw new PasswordNotMatchException("用户密码错误");}//判断is_delete字段值是否为1表示被标记为删除if (UserLogin.getIsDelete() == 1){throw new UserNullException("用户已被删除");}User user = new User();user.setUid(UserLogin.getUid());user.setUsername(UserLogin.getUsername());user.setAvatar(UserLogin.getAvatar());//返回用户数据,是为了辅助页面return user;}@Overridepublic void changePassword(Integer uid, String username, String oldPassword, String newPassword) {User user = userDao.findByUid(uid);if (user ==null){throw new UserNullException("用户不存在");}if (user.getIsDelete() ==1){throw new UserDeletedException("用户不存在或已被删除");}//密码对比String md5Password = getMD5Password(oldPassword, user.getSalt());if (!user.getPassword().equals(md5Password)){throw new PasswordNotMatchException("原密码错误");}//更新passwordString newPasswordMd5 = getMD5Password(newPassword, user.getSalt());Integer rows = userDao.updatePasswordByUid(uid, newPasswordMd5,username,new Date());if (rows !=1){throw new PasswordUpdateException("修改密码未知异常");}}//根据id获取userInfo@Overridepublic User getUserInfoByUid(Integer uid) {User result = userDao.findByUid(uid);if (result == null || result.getIsDelete() ==1){throw new UserNullException("用户不存在");}User user = new User();user.setUsername(result.getUsername());user.setUid(result.getUid());user.setPhone(result.getPhone());user.setEmail(result.getEmail());user.setGender(result.getGender());return user;}//修改用户信息@Overridepublic void changeUserInfo(Integer uid, String username, User user) {User result = userDao.findByUid(uid);if (result == null || result.getIsDelete() ==1){throw new UserNullException("用户不存在");}user.setUid(uid);user.setModifiedUser(username);user.setModifiedTime(new Date());Integer rows = userDao.updateUserInfoByUid(user);if (rows != 1){throw new InfoUpdateException("修改用户信息未知异常");}}//password加密方法private String getMD5Password(String password,String salt){for (int i =0;i<5;i++){password = DigestUtils.md5DigestAsHex((salt + password + salt).getBytes()).toUpperCase();}//返回加密之后的密码return password;}
}

JWT工具类

public class JWTUtil {private static final   String TOKENKey="qgs12345";/*** 生成token* @param map* @return 返回token*/public static String getToken(Map<String,String> map){Calendar instance = Calendar.getInstance();instance.add(Calendar.DATE,7);//7天过期//添加payloadJWTCreator.Builder builder = JWT.create();map.forEach((k,v)->{builder.withClaim(k,v);});builder.withExpiresAt(instance.getTime());//设置令牌过期时间//生成并返回tokenreturn builder.sign(Algorithm.HMAC256(TOKENKey)).toString();}/*** 验证token* @param token*/public static void verify(String token){JWT.require(Algorithm.HMAC256(TOKENKey)).build().verify(token);}/*** 获取token中payload* @param token* @return*/public static DecodedJWT getTokenInfo(String token){return JWT.require(Algorithm.HMAC256(TOKENKey)).build().verify(token);}
}

UserController

@RestController
@RequestMapping("/users")
@CrossOrigin //表示都允许跨域访问
public class UserController extends BaseController{@Autowiredprivate UserService userModuleService;@RequestMapping("/login")public Map<String,Object> login(String username, String password){Map<String,Object> map =new HashMap<>();try {User data = userModuleService.login(username, password);Map<String,String> payload =new HashMap<>();payload.put("username",data.getUsername());//生成JWT令牌String token =JWTUtil.getToken(payload);map.put("state",true);map.put("msg","登入成功");map.put("token",token);}catch (Exception e){map.put("state",false);map.put("msg","登入失败");}return map;}}

登录,产生token

验证token方式一

认证代码中写token认证流程,过多的认证请求会导致代码冗余

@RestController
@RequestMapping("/users")
@CrossOrigin //表示都允许跨域访问
public class UserController extends BaseController{@Autowiredprivate UserService userModuleService;@RequestMapping("/login")//验证token@RequestMapping("/loginVerify")public Map<String,Object> loginVerify(String token){System.out.println(token);Map<String,Object> map =new HashMap<>();try {//生成JWT令牌JWTUtil.verify(token);map.put("state",true);map.put("msg","验证成功");}catch (Exception e){map.put("state",true);map.put("msg","验证失败");}return map;}}

验证token方式二 JWT拦截器

抛弃方式一的代码冗余

public class JWTInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {Map<String,Object> map =new HashMap<>();// 获取请求头中的JWT令牌String token = request.getHeader("token");// 进行JWT令牌的验证逻辑try {//生成JWT令牌JWTUtil.verify(token);return true;//放行请求}catch (Exception e){map.put("state",false);map.put("msg","token验证失败");//map转jsonString msg =new ObjectMapper().writeValueAsString(map);response.setContentType("application/json;charset=utf-8");response.getWriter().println(msg);}return false;}
}

@Component
public class InterceptorConfig implements WebMvcConfigurer {public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new JWTInterceptor()).addPathPatterns("/users//loginVerify")//拦截路径,根据实际情况进行配置.excludePathPatterns("/users/login","/reg") ;//放行路径}}

简化loginVerify后

//验证token
@RequestMapping("/loginVerify")
public Map<String,Object> loginVerify(HttpServletRequest request){Map<String,Object> map =new HashMap<>();String token =request.getHeader("token");System.out.println(token);map.put("state",true);map.put("msg","验证成功");return map;
}

请求头携带token效果

Session认证

session用户认证流程

1、用户向服务器发送用户名和密码。

2、服务器验证通过后,在当前对话( session)里面保存相关数据,比如用户角色登录时间等。

3、服务器向用户返回一个 session_id,写入用户的 Cookie。

4、用户随后的每一次请求,都会通过Cookie,将 session_id传回服务器。

5、服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/589868.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[DAU-FI Net开源 | Dual Attention UNet+特征融合+Sobel和Canny等算子解决语义分割痛点]

文章目录 概要I Introduction小结 概要 提出的架构&#xff0c;双注意力U-Net与特征融合&#xff08;DAU-FI Net&#xff09;&#xff0c;解决了语义分割中的挑战&#xff0c;特别是在多类不平衡数据集上&#xff0c;这些数据集具有有限的样本。DAU-FI Net 整合了多尺度空间-通…

【Qt之Quick模块】6. QML语法详解_3 QML对象特性

概述 每一个QML对象类型都包含一组已定义的特性。当进行实例时都会包含一组特性&#xff0c;这些特性是在对象类型中定义的。 一个QML文档中的对象类型声明了一个新的类型&#xff0c;即实例出一个类型。 其中包含以下特性。 the id attribute &#xff1a; id特性property a…

机器学习——主成分分析(PCA)

目录 背景 引入 特征维度约减 特征维度约减的概念 为何要维度约减? 维度约减的应用 常规维度约减方法 主成分分析 主成分分析 (PCA)基本思路 主成分的代数定义和代数推导 主成分的代数定义 主成分的代数推导 PCA算法两种实现方法 1、基于特征值分解协方差矩阵实…

【Android Gradle 插件】Android Plugin DSL Reference 离线文档下载 ( GitHub 下载文档 | 查看文档 )

一、Android Plugin DSL Reference 文档下载 二、Android Plugin DSL Reference 文档查看 一、Android Plugin DSL Reference 文档下载 在之前的博客 【Android Gradle 插件】Android Plugin DSL Reference 文档介绍 ( 1.2 ~ 3.4 版本文档地址 | 4.1 ~ 7.1 版本文档地址 ) 中…

第三十七周周报:文献阅读+掩码、多头注意力机制+位置编码

目录 摘要 Abstract 文献阅读&#xff1a;基于注意力的LSTM大地震预报网络 现有问题 提出方法 基于注意力的LSTM网络 研究实验 实验目的 数据集 评估指标 数据预处理和特征提取 结果讨论 Masked Self-Attention&#xff08;掩码自注意力&#xff09; Muti-Head S…

vue3-11

后端Java代码 src\router\a6router.ts文件 import { createRouter, createWebHashHistory } from vue-router import { useStorage } from vueuse/core import { Menu, Route } from ../model/Model8080 const clientRoutes [{path: /login,name: login,component: () > …

flutter学习-day23-使用extended_image处理图片的加载和操作

文章目录 1. 介绍2. 属性介绍3. 使用 1. 介绍 在 Flutter 的开发过程中&#xff0c;经常会遇到图片的显示和加载处理&#xff0c;通常显示一个图片&#xff0c;都有很多细节需要处理&#xff0c;比如图片的加载、缓存、错误处理、图片的压缩、图片的格式转换等&#xff0c;如果…

WinForm开发 - C# RadioButton(单选框) 设置默认选中或取消默认选中

WinForm开发中RadioButton组件使用过程中的小技巧。 1、属性界面操作 如果有多个组件&#xff0c;希望不显示默认选中单选框只需要将其Checked属性全部设置为False即可&#xff0c; 如果希望默认多个组件中显示默认选中&#xff0c;将其Checked属性设置为True。 2、代码实…

爱吃饼干的小白鼠2023年终总结

目录 前言 学习生活经历 回顾2023 参加活动 回顾点点滴滴 展望2024 2024年新的起点和目标 前言 大家好&#xff0c;我是爱吃饼干的小白鼠。今天刚好是2024年1月1日&#xff0c;时间飞逝&#xff0c;2023年过的飞快&#xff0c;我已经入驻CSDN有一年了&#xff0c;这一年…

Rust学习笔记005:结构体 struct

在 Rust 中&#xff0c;struct 是一种用于创建自定义数据类型的关键字&#xff0c;它允许你定义和组织数据的结构。struct 可以包含多个不同类型的字段&#xff08;fields&#xff09;&#xff0c;每个字段都有一个名称和一个类型。 定义结构体 下面是一个简单的例子&#xff…

MySQL8.0 ROW_NUMBER 调用案例

ROW_NUMBER()是一个窗口函数或分析函数&#xff0c;它为从1开始应用的每一行分配一个序号。 创建表和生成所需要的数据 CREATE TABLE chapter11 ( shopname VARCHAR(255) NULL, sales VARCHAR(255) NULL, sale_date DATE NULL ); INSERT INTO chapter11 (shopname, sales, sal…

大数据背景下基于联邦学习的小微企业信用风险评估研究

摘要&#xff1a; 小微企业信用风险评估难是制约其融资和发展的一个主要障碍。基于大数据的小微企业信用风险评估依然面临着单机构数据片面、跨机构数据共享难、模型不稳定等诸多挑战。针对相关问题和挑战&#xff0c;本项目拟在多主体所有权数据隐私保护与安全共享的背景下&am…

构建基础wlan网络 hcia无线

实验 旁挂组网 二层网络 ac为 dhcp的服务器给ap地址 s1给sta的ip地址 DHCP 业务为直接转发 实验步骤 第一步 poe 开启 poe en 开启 第二步 有线连接 vlan的配置 s1 vlan batch 100 101 连接的端口 port link-type trunk port trunk allow-pass …

NodeJs - Chrome内存分析工具使用

NodeJs - Chrome内存分析工具使用 一. 前期准备二. Chrome 内存分析工具使用2.1 查看快照2.2 使用案例 一. 前期准备 我们下载好相关依赖&#xff1a; npm i v8-profiler-next测试代码&#xff1a; const v8Profiler require(v8-profiler-next) const fs require(fs)funct…

听GPT 讲Rust源代码--library/alloc(2)

File: rust/library/alloc/src/vec/mod.rs 在Rust源代码中&#xff0c;rust/library/alloc/src/vec/mod.rs这个文件是Rust标准库中的Vec类型的实现文件。Vec是一个动态大小的数组类型&#xff0c;在内存中以连续的方式存储其元素。 具体来说&#xff0c;mod.rs文件中定义了以下…

Elasticsearch:使用 ELSER v2 文本扩展进行语义搜索

Elastic 提供了一个强大的 ELSER 供我们进行语义搜索。ELSER 是一种稀疏向量的搜索方法。我们无需对它做任何的微调及训练。它是一种 out-of-domain 的模型。目前它仅对英文进行支持。希望将来它能对其它的语言支持的更好。更多关于 ELSER 的知识&#xff0c;请参阅文章 “Elas…

MySql——1146 - Table‘mysql.proc‘doesn‘t exit是这个

项目场景&#xff1a; 做自己的小项目需要连接mysql数据库 问题描述 点击数据库时报错 1146 - Table’mysql.proc’doesn’t exit 原因分析&#xff1a; 误删原生的mysql数据库 解决方案&#xff1a; 重新安装装部署mysql就好了 注意不要轻易删除原生的东西

day55 算法训练|动态规划part15

392.判断子序列 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace"是"abcde"的…

react学习第一天

脚手架的创建 1.创建环境变量 npm init -y 2.创建node-modules npm add -D create-react-app 3.创建脚手架 npx create-react-app react-demo1 报错一号 报错原因&#xff1a;node版本太低 解决&#xff1a;升级版本 nvm install 14.0.0 nvm use 14.0.0 报错二号 报错原因&…

牛客网SQL训练5—SQL大厂面试真题

文章目录 一、某音短视频1.各个视频的平均完播率2.平均播放进度大于60%的视频类别3.每类视频近一个月的转发量/率4.每个创作者每月的涨粉率及截止当前的总粉丝量5.国庆期间每类视频点赞量和转发量6.近一个月发布的视频中热度最高的top3视频 二、用户增长场景&#xff08;某度信…