SpringBoot+JWT實(shí)現(xiàn)注冊(cè)、登錄、狀態(tài)續(xù)簽流程分析
一、實(shí)現(xiàn)流程
1.注冊(cè)


2.登錄


3.登錄保持【狀態(tài)續(xù)簽】


二、實(shí)現(xiàn)方法
項(xiàng)目結(jié)構(gòu)

1.引入依賴(lài)
<!-- spring-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mysql連接器 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Mybatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.4</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<!-- jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.10.3</version>
</dependency>
2.application配置文件
spring:
application:
name: jwtLogin # 應(yīng)用名稱(chēng)
datasource:
druid:
url: jdbc:mysql://192.168.0.111:3306/login_test?useSSL=false&serverTimezone=UTC
username: samon
password: 123456
driver-class-name: com.mysql.jdbc.Driver
server:
port: 8848 # 應(yīng)用服務(wù) WEB 訪(fǎng)問(wèn)端口3.mysql建表
用戶(hù)表
4.Bean
1.bean/user.java
用戶(hù)bean
package com.cxstar.bean;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.util.Date;
@Data
public class User {
// 自增長(zhǎng)id
@TableId(type = IdType.AUTO)
private Integer id;
private String userName;
private String passWord;
private Date createTime;
private Date lastLogin;
public User() {}
public User(Integer id, String userName) {
this.id = id;
this.userName = userName;
}
}
2.bean/ServiceRes.java
統(tǒng)一Service返回類(lèi)
package com.cxstar.bean;
import lombok.Data;
@Data
public class ServiceRes {
private Integer code;
private String msg;
private String jwt;
private ServiceRes() {}
public ServiceRes(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public ServiceRes(Integer code, String msg, String jwt) {
this.code = code;
this.msg = msg;
this.jwt = jwt;
}
}
3.bean/ControllerRes.java
統(tǒng)一Controller返回類(lèi)
package com.cxstar.bean;
import lombok.Data;
@Data
public class ControllerRes {
private Integer code;
private String msg;
public ControllerRes(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
}
5.Mapper mapper/UserMapper.java
繼承MP
package com.cxstar.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cxstar.bean.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
6.service service.userService.interface
登錄、注冊(cè)、改密業(yè)務(wù)【狀態(tài)續(xù)簽測(cè)試】
package com.cxstar.service;
import com.cxstar.bean.ServiceRes;
import com.cxstar.bean.User;
public interface userService {
// 注冊(cè)
ServiceRes register(User user);
// 登錄
ServiceRes login(User user);
// 改密【帶權(quán)限業(yè)務(wù),用于狀態(tài)續(xù)簽測(cè)試】
ServiceRes changePassWord(User user);
}
service.impl.UserServiceImpl.java
登錄、注冊(cè)、改密業(yè)務(wù)【狀態(tài)續(xù)簽測(cè)試】
package com.cxstar.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.cxstar.bean.ServiceRes;
import com.cxstar.bean.User;
import com.cxstar.mapper.UserMapper;
import com.cxstar.service.userService;
import com.cxstar.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
public class UserServiceImpl implements userService {
@Autowired
UserMapper userMapper;
/**
* 注冊(cè)
* @param user 用戶(hù)類(lèi)
* @return ServiceRes
*/
@Override
public ServiceRes register(User user) {
// 判斷用戶(hù)名是否唯一
if(this.checkUserNameIsUnique(user)) {
// 判斷用戶(hù)名密碼是否合法
if(this.checkUserNameAndPassword(user)) {
// 密碼MD5加密
user.setPassWord(this.MD5Code(user.getPassWord()));
// 加入創(chuàng)建時(shí)間
user.setCreateTime(new Date());
// 入庫(kù)
userMapper.insert(user);
return new ServiceRes(1, "注冊(cè)成功");
} else return new ServiceRes(-1, "用戶(hù)名或密碼不合法");
} else return new ServiceRes(-1, "用戶(hù)名已存在");
}
/**
* 登錄
* @param user 用戶(hù)類(lèi)
* @return ServiceRes
*/
@Override
public ServiceRes login(User user) {
// 判斷用戶(hù)名密碼是否合法
if(this.checkUserNameAndPassword(user)) {
// 密碼MD5加密
user.setPassWord(this.MD5Code(user.getPassWord()));
// 檢查用戶(hù)是否存在
User curUser = this.checkUserIsExit(user);
if(curUser!=null) {
// 更新用戶(hù)最后登錄時(shí)間
curUser.setLastLogin(new Date());
userMapper.updateById(curUser);
// 生成jwt
Map<String, String> payload = new HashMap<>();
payload.put("userId", curUser.getId().toString()); // 加入一些非敏感的用戶(hù)信息
payload.put("userName", curUser.getUserName()); // 加入一些非敏感的用戶(hù)信息
String jwt = JwtUtil.generateToken(payload);
return new ServiceRes(1, "登錄成功", jwt);
} else return new ServiceRes(-1, "用戶(hù)名或密碼錯(cuò)誤");
} else return new ServiceRes(-1, "用戶(hù)名或密碼不合法");
}
/**
* 改密業(yè)務(wù)
* @return ServiceRes
*/
@Override
public ServiceRes changePassWord(User user) {
if(this.updatePassWord(user)) return new ServiceRes(1, "改密成功");
else return new ServiceRes(-1, "改密失敗");
}
/**
* 非對(duì)稱(chēng)加密
* @param text 明文
* @return 密文
*/
private String MD5Code(String text) {
return DigestUtils.md5DigestAsHex(text.getBytes(StandardCharsets.UTF_8));
}
/**
* 修改密碼方法
* @param user 傳入用戶(hù)名和新密碼
* @return 改密成功返回 true 失敗返回 false
*/
private Boolean updatePassWord(User user) {
// 密碼非對(duì)稱(chēng)加密
user.setPassWord(this.MD5Code(user.getPassWord()));
// 更新密碼
return userMapper.updateById(user)>0;
}
/**
* 檢查用戶(hù)是否存在【用戶(hù)名密碼相同】
* @param user 用戶(hù)類(lèi)
* @return 用戶(hù)存在返回 用戶(hù)對(duì)象 不存在返回 null
*/
private User checkUserIsExit(User user) {
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.eq(User::getUserName, user.getUserName());
lqw.eq(User::getPassWord, user.getPassWord());
return userMapper.selectOne(lqw);
}
/**
* 判斷用戶(hù)名是否唯一
* @param user 用戶(hù)類(lèi)
* @return 唯一返回 true 不唯一返回 false
*/
private Boolean checkUserNameIsUnique(User user) {
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.eq(User::getUserName, user.getUserName());
List<User> userList = userMapper.selectList(lqw);
return userList.size() == 0;
}
/**
* 判斷用戶(hù)名密碼是否合法
* @param user 用戶(hù)類(lèi)
* @return 滿(mǎn)足 【英文字母、數(shù)字、下劃線(xiàn)】 返回 true,否則返回 false
*/
private Boolean checkUserNameAndPassword(User user) {
String regex = "^[_a-z0-9A-Z]+$";
return user.getUserName().matches(regex) && user.getPassWord().matches(regex);
}
}
6.Controller controller/UserController.java
登錄、注冊(cè)、改密業(yè)務(wù)【狀態(tài)續(xù)簽測(cè)試】
package com.cxstar.controller;
import com.cxstar.bean.ControllerRes;
import com.cxstar.bean.ServiceRes;
import com.cxstar.bean.User;
import com.cxstar.service.userService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@Autowired
userService userService;
@PostMapping("/register")
public ControllerRes register(User user) {
// 注冊(cè)
ServiceRes serviceRes = userService.register(user);
return new ControllerRes(serviceRes.getCode(), serviceRes.getMsg());
}
@PostMapping("/login")
public ControllerRes login(User user, HttpServletResponse response) {
// 登錄
ServiceRes serviceRes = userService.login(user);
// 登錄成功后往響應(yīng)頭插入jwt
if(serviceRes.getJwt() != null) response.addHeader("access-token", serviceRes.getJwt());
return new ControllerRes(serviceRes.getCode(), serviceRes.getMsg());
}
@PutMapping("/pwd")
public ControllerRes changePassWord(User user, HttpServletRequest request) {
// 取出jwt中的用戶(hù)
User jwtUser = (User)request.getAttribute("jwt-user");
// 合并jwt中用戶(hù)的用戶(hù)名與傳入用戶(hù)的新密碼
// 此處不能直接使用傳入的用戶(hù)名,防止惡意修改其他用戶(hù)的密碼
user.setId(jwtUser.getId());
// 改密
ServiceRes serviceRes = userService.changePassWord(user);
return new ControllerRes(serviceRes.getCode(), serviceRes.getMsg());
}
}
7.JWT工具類(lèi) utils/JwtUtil.java
生成和解析 token 的方法
package com.cxstar.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
public class JwtUtil {
// 簽名密鑰
private static final String SECRET = "hello JWT *%$#$&";
/**
* 生成token
* @param payload token攜帶的信息
* @return token字符串
*/
public static String generateToken(Map<String,String> payload){
// 指定token過(guò)期時(shí)間
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.HOUR, 24); // 24小時(shí)
JWTCreator.Builder builder = JWT.create();
// 構(gòu)建payload
payload.forEach(builder::withClaim);
// 指定簽發(fā)時(shí)間、過(guò)期時(shí)間 和 簽名算法,并返回token
String token = builder.withIssuedAt(new Date()).withExpiresAt(calendar.getTime()).sign(Algorithm.HMAC256(SECRET));
return token;
}
/**
* 解析token
* @param token token字符串
* @return 解析后的token類(lèi)
*/
public static DecodedJWT decodeToken(String token){
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token);
return decodedJWT;
}
}
8.HandlerInterceptor攔截器 interceptor.java
攔截器業(yè)務(wù)實(shí)現(xiàn)
package com.cxstar.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.cxstar.bean.ControllerRes;
import com.cxstar.bean.User;
import com.cxstar.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 攔截需要授權(quán)的接口
*/
@Slf4j
public class PermisssionInterceptor implements HandlerInterceptor {
// 目標(biāo)方法執(zhí)行前調(diào)用
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
// 檢查用戶(hù)JWT
String jwt = request.getHeader("access-token");
// 校驗(yàn)并取出私有信息
try {
// token 解碼
DecodedJWT dj = JwtUtil.decodeToken(jwt);
// 取出基本用戶(hù)信息加入請(qǐng)求頭 --------------------------------------------------------------------------------
String userId = dj.getClaim("userId").asString();
String userName = dj.getClaim("userName").asString();
// jwt校驗(yàn)合格的,將 jwt 中存的用戶(hù)信息加入請(qǐng)求頭,不合格的,請(qǐng)求頭存?zhèn)€空用戶(hù)
request.setAttribute("jwt-user", userId!=null?new User(Integer.valueOf(userId), userName):new User());
// -------------------------------------------------------------------------------------------------------
// 計(jì)算當(dāng)前時(shí)間是否超過(guò)過(guò)期時(shí)間的一半,如果是就幫用戶(hù)續(xù)簽 --------------------------
// 此處并不是永久續(xù)簽,只是為 大于過(guò)期時(shí)間的一半 且 小于過(guò)期時(shí)間 的 token 續(xù)簽
Long expTime = dj.getExpiresAt().getTime();
Long iatTime = dj.getIssuedAt().getTime();
Long nowTime = new Date().getTime();
if((nowTime-iatTime) > (expTime-iatTime)/2) {
// 生成新的jwt
Map<String, String> payload = new HashMap<>();
payload.put("userId", userId); // 加入一些非敏感的用戶(hù)信息
payload.put("userName", userName); // 加入一些非敏感的用戶(hù)信息
String newJwt = JwtUtil.generateToken(payload);
// 加入返回頭
response.addHeader("access-token", newJwt);
}
// -----------------------------------------------------------------------
return true;
} catch (JWTDecodeException e) {
log.error("令牌錯(cuò)誤");
addResBody(response, new ControllerRes(-1, "令牌錯(cuò)誤")); // 新增返回體
return false;
} catch (TokenExpiredException e) {
log.error("令牌過(guò)期");
addResBody(response, new ControllerRes(-1, "令牌過(guò)期")); // 新增返回體
return false;
}
}
// 目標(biāo)方法執(zhí)行后調(diào)用
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
// 頁(yè)面渲染前調(diào)用
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
private void addResBody(HttpServletResponse response, ControllerRes res) throws IOException {
response.setStatus(HttpServletResponse.SC_FORBIDDEN); // 設(shè)置狀態(tài)碼
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter out = response.getWriter();
out.write(JSONObject.toJSONString(res));
out.flush();
out.close();
}
}
config/PermissionWebConfig.java
攔截器攔截規(guī)則
package com.cxstar.config;
import com.cxstar.interceptor.PermisssionInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class PermissionWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new PermisssionInterceptor())
.addPathPatterns("/**") // 攔截哪些頁(yè)面
.excludePathPatterns("/user/login", "/user/register"); // 放行哪些頁(yè)面
}
}
三、測(cè)試
1.注冊(cè)
注冊(cè)成功

數(shù)據(jù)入庫(kù)

2.登錄
登錄成功

查看登錄后返回的token

3.狀態(tài)續(xù)簽【登錄保持】
使用上一步登錄返回的 token 請(qǐng)求改密業(yè)務(wù)

當(dāng) JWT 存在時(shí)間小于 JWT 過(guò)期時(shí)間的一半時(shí)
業(yè)務(wù)會(huì)執(zhí)行成功
執(zhí)行業(yè)務(wù)不會(huì)返回續(xù)簽的 token

當(dāng) JWT 存在時(shí)間大于 JWT 過(guò)期時(shí)間的一半 且 小于過(guò)期時(shí)間 時(shí)
業(yè)務(wù)會(huì)執(zhí)行成功
執(zhí)行業(yè)務(wù)會(huì)返回續(xù)簽的 token,前端的下次請(qǐng)求需要使用新續(xù)簽的 token

當(dāng) JWT 存在時(shí)間大于 JWT 過(guò)期時(shí)間 時(shí)
業(yè)務(wù)會(huì)執(zhí)行失敗
執(zhí)行業(yè)務(wù)不會(huì)返回續(xù)簽的 token

到此這篇關(guān)于SpringBoot+JWT實(shí)現(xiàn)注冊(cè)、登錄、狀態(tài)續(xù)簽的實(shí)戰(zhàn)教程的文章就介紹到這了,更多相關(guān)SpringBoot JWT登錄狀態(tài)續(xù)簽內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot常見(jiàn)登錄(注冊(cè))幾種實(shí)現(xiàn)過(guò)程
- SpringBoot整合MD5加密完成注冊(cè)和登錄方式
- Springboot集成JWT實(shí)現(xiàn)登錄注冊(cè)的示例代碼
- SpringBoot結(jié)合JWT實(shí)現(xiàn)用戶(hù)登錄、注冊(cè)、鑒權(quán)
- 基于SpringBoot和Vue3的博客平臺(tái)的用戶(hù)注冊(cè)與登錄功能實(shí)現(xiàn)
- SpringBoot實(shí)現(xiàn)簡(jiǎn)單的登錄注冊(cè)的項(xiàng)目實(shí)戰(zhàn)
- springboot+VUE實(shí)現(xiàn)登錄注冊(cè)
- SpringBoot實(shí)現(xiàn)登錄注冊(cè)常見(jiàn)問(wèn)題解決方案
- SpringBoot實(shí)現(xiàn)登錄注冊(cè)的三種基本方式
相關(guān)文章
基于Java的Spring框架來(lái)操作FreeMarker模板的示例
這篇文章主要介紹了基于Java的Spring框架來(lái)操作FreeMarker模板的示例,講到了用于進(jìn)行web模板文件的插值操作等例子,需要的朋友可以參考下2016-03-03
Maven是什么?Maven的概念+作用+倉(cāng)庫(kù)的介紹+常用命令的詳解
Maven是一個(gè)項(xiàng)目管理工具,它包含了一個(gè)對(duì)象模型。一組標(biāo)準(zhǔn)集合,一個(gè)依賴(lài)管理系統(tǒng)。和用來(lái)運(yùn)行定義在生命周期階段中插件目標(biāo)和邏輯.,本文給大家介紹Maven的概念+作用+倉(cāng)庫(kù)的介紹+常用命令,感興趣的的朋友跟隨小編一起看看吧2020-09-09
Springboot單元測(cè)試無(wú)法讀取配置文件的解決方案
這篇文章主要介紹了Springboot單元測(cè)試無(wú)法讀取配置文件的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
Java原生方法實(shí)現(xiàn) AES 算法示例
這篇文章主要介紹了Java原生方法實(shí)現(xiàn) AES 算法,結(jié)合實(shí)例形式分析了Java實(shí)現(xiàn)AES加密算法的相關(guān)操作技巧,需要的朋友可以參考下2019-03-03
Springboot集合前端實(shí)現(xiàn)進(jìn)度條顯示功能實(shí)例
這篇文章主要介紹了使用進(jìn)度條提升用戶(hù)體驗(yàn)的原因,特別是在處理大文件上傳、下載或長(zhǎng)時(shí)間運(yùn)行的操作時(shí),進(jìn)度條通過(guò)實(shí)時(shí)反饋任務(wù)進(jìn)度,減少用戶(hù)的不確定感,文中給出了詳細(xì)的代碼示例,需要的朋友可以參考下2024-11-11
Spring Boot使用模板freemarker的示例代碼
本篇文章主要介紹了Spring Boot使用模板freemarker的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10
詳解在springmvc中解決FastJson循環(huán)引用的問(wèn)題
本篇文章主要介紹了在springmvc中解決FastJson循環(huán)引用的問(wèn)題,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-01-01
Spring Boot應(yīng)用發(fā)布到Docker的實(shí)現(xiàn)
這篇文章主要介紹了Spring Boot應(yīng)用發(fā)布到Docker的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06

