赞
踩
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和进行验证。
参考代码仓库里面的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');
实验目标:实现JWT的签发和验证
- <?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>
- 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
- 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;
- }
- }
- 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
启动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
https://www.v2ex.com/t/817906
http://www.liuhaihua.cn/archives/710374.html
https://github.com/ChuaWi/SpringBoot-JWT/tree/master
这里留2个问题给读者
过期了如何刷新token呢?保证过期时候重新登录,而不是后台自动刷新,一直不过期?
如果要实现强制下线用户功能,该如何实现呢?
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。