ITPub博客

首页 > 数据库 > 数据库开发技术 > Spring Boot集成JWT快速入门demo

Spring Boot集成JWT快速入门demo

原创 数据库开发技术 作者:hblog 时间:2024-04-01 17:00:15 0 删除 编辑

1.JWT是什么?

JWT,英文全称JSON Web Token:JSON网络令牌。为了在网络应用环境间传递声明而制定的一种基于JSON的开放标准(RFC 7519)。这个规范允许我们使用JWT在客户端和服务端之间传递安全可靠的信息。JWT是一个轻便的安全跨平台传输格式,定义了一个紧凑自包含的方式,用于通信双方之间作为 JSON 对象安全地传递信息。此信息可以通过数字签名进行验证和信任。

  • 紧凑:这个字符串简洁,数据量小,传输速度快,能通过URL参数、HTTP请求提交的数据以及HTTP Header的方式进行传递。

  • 自包含:负载中包含很多信息,比如用户的ID等。别人拿到这个字符串,就能拿到这些关键的业务信息,从而避免再通过数据库查询等方式得到它们。

JWT的结构 JWT由三段信息用.连接构成的字符串。

Header.Payload.Signature 例如:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxIn0.ihOZFzg3ZGIbBMneRy-4RMqors1P3nuO-wRJnQtTzWQ


  • Header头部

承载两部分信息:token类型和采用的加密算法

{   "typ": "JWT",  "alg": "HS256"}

token类型:JWT 加密算法:HS256

  • Payload负载

存放有效信息的地方

iss: jwt签发者 sub: jwt所面向的用户 aud: 接收jwt的一方 exp: 过期时间戳(jwt的过期时间,这个过期时间必须要大于签发时间) nbf: 定义在什么时间之前,该jwt都是不可用的 iat: jwt的签发时间 jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击

  • Signature签名

对头部及负载内容进行签证。采用Header中声明的算法,接收三个参数:base64编码的Header、base64编码的Payload和密钥(secret)进行运算。密钥secret是保存在服务端的,服务端会根据这个密钥进行生成token和进行验证。

2.环境搭建

参考代码仓库里面的mysql模块,这里只贴出docker-compose.yml

version: '3'services:  mysql:    image: registry.cn-hangzhou.aliyuncs.com/zhengqing/mysql:5.7    container_name: mysql_3306    restart: unless-stopped                                       volumes:      - "./mysql/my.cnf:/etc/mysql/my.cnf"      - "./mysql/init-file.sql:/etc/mysql/init-file.sql"      - "./mysql/data:/var/lib/mysql"#      - "./mysql/conf.d:/etc/mysql/conf.d"      - "./mysql/log/mysql/error.log:/var/log/mysql/error.log"      - "./mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d" # init sql script directory -- tips: it can be excute  when `/var/lib/mysql` is empty    environment:                        # set environment,equals docker run -e      TZ: Asia/Shanghai      LANG: en_US.UTF-8      MYSQL_ROOT_PASSWORD: root         # set root password      MYSQL_DATABASE: demo              # init database name    ports:                              # port mappping      - "3306:3306"

运行

docker-compose -f docker-compose.yml -p mysql5.7 up -d

初始化表

DROP TABLE IF EXISTS `jwt_user`; CREATE TABLE `jwt_user`( `id` varchar(32) CHARACTER SET utf8 NOT NULL COMMENT '用户ID', `username` varchar(100) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '登录账号', `password` varchar(255) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '密码')ENGINE = InnoDB CHARACTER SET = utf8 COMMENT = '用户表' ROW_FORMAT = Compact;
INSERT INTO jwt_user VALUES('1','admin','123');

3.代码工程

实验目标:实现JWT的签发和验证

pom.xml

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <parent>        <artifactId>springboot-demo</artifactId>        <groupId>com.et</groupId>        <version>1.0-SNAPSHOT</version>    </parent>    <modelVersion>4.0.0</modelVersion>
<artifactId>jwt</artifactId>
<properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.1</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency>

</dependencies></project>

application.yaml

spring:  datasource:    driver-class-name: com.mysql.cj.jdbc.Driver    url: jdbc:mysql://localhost:3306/demo?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT    username: root    password: root
server: port: 8088

Jwt生成

package com.et.jwt.service;
import com.auth0.jwt.JWT;import com.auth0.jwt.algorithms.Algorithm;
import com.et.jwt.entity.User;import org.springframework.stereotype.Service;
import java.time.Instant;import java.time.LocalDateTime;import java.time.ZoneId;import java.util.Date;

@Service("TokenService")
public class TokenService { long outHours=1; public String getToken(User user) { Instant instant = LocalDateTime.now().plusHours(outHours).atZone(ZoneId.systemDefault()).toInstant(); Date expire = Date.from(instant); String token=""; // save information to token ,such as: user id and Expires time token= JWT.create() .withAudience(user.getId()) .withExpiresAt(expire) .sign(Algorithm.HMAC256(user.getPassword())); // use HMAC256 to generate token,key is user's password return token; }}

jwt验证

package com.et.jwt.interceptor;
import com.auth0.jwt.JWT;import com.auth0.jwt.JWTVerifier;import com.auth0.jwt.algorithms.Algorithm;import com.auth0.jwt.exceptions.JWTDecodeException;import com.auth0.jwt.exceptions.JWTVerificationException;
import com.et.jwt.annotation.PassToken;import com.et.jwt.annotation.UserLoginToken;import com.et.jwt.entity.User;import com.et.jwt.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.method.HandlerMethod;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.lang.reflect.Method;import java.util.Date;

public class AuthenticationInterceptor implements HandlerInterceptor { @Autowired UserService userService; @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception { // get token from http header String token = httpServletRequest.getHeader("token"); if(!(object instanceof HandlerMethod)){ return true; } HandlerMethod handlerMethod=(HandlerMethod)object; Method method=handlerMethod.getMethod(); //check is included @passtoken annotation? jump if have if (method.isAnnotationPresent(PassToken.class)) { PassToken passToken = method.getAnnotation(PassToken.class); if (passToken.required()) { return true; } } //check is included @UserLoginToken ? if (method.isAnnotationPresent(UserLoginToken.class)) { UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class); if (userLoginToken.required()) { // excute verify if (token == null) { throw new RuntimeException("token invalid,please login again"); } // get user id from token String userId; try { userId = JWT.decode(token).getAudience().get(0); } catch (JWTDecodeException j) { throw new RuntimeException("401"); } User user = userService.findUserById(userId); if (user == null) { throw new RuntimeException("user is not exist,please login again"); } // verify token JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build(); try { jwtVerifier.verify(token); if (JWT.decode(token).getExpiresAt().before(new Date())) { throw new RuntimeException("token Expires"); }
} catch (JWTVerificationException e) { throw new RuntimeException("401"); }
return true; } } return true; }
@Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
} @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
}}

以上只是一些关键代码,所有代码请参见下面代码仓库

代码仓库

  • https://github.com/Harries/springboot-demo

4.测试

  • 启动Spring Boot应用

  • 登录系统

    http://localhost:8088/api/login?username=admin&password=123

{ "user": { "username": "admin", "password": "123", "id": "1" }, "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxIn0.ihOZFzg3ZGIbBMneRy-4RMqors1P3nuO-wRJnQtTzWQ"}
  • 在header里面设置 token,访问

    http://localhost:8088/api/getMessage

5.引用

  • https://www.v2ex.com/t/817906

  • http://www.liuhaihua.cn/archives/710374.html

  • https://github.com/ChuaWi/SpringBoot-JWT/tree/master

6.Questions

这里留2个问题给读者

  1. 过期了如何刷新token呢?保证过期时候重新登录,而不是后台自动刷新,一直不过期?

  2. 如果要实现强制下线用户功能,该如何实现呢?


原文作者:HBLOG;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2022-05-04

  • 博文量
    210
  • 访问量
    111749