1.What is JWT?
JWT,The full English name is JSON Web Token: JSON Web Token. An open standard based on JSON (RFC 7519) developed for conveying declarations between network application environments. This specification allows us to use JWT to transfer secure and reliable information between clients and servers. JWT is a lightweight, secure cross-platform transmission format that defines a compact, self-contained way to securely transfer information as a JSON object between communicating parties. This information can be verified and trusted through digital signatures.
- Compact: This string is concise, with small data volume and fast transmission speed. It can be transmitted through URL parameters, data submitted by HTTP requests and HTTP Header.
- Self-contained: The payload contains a lot of information, such as the user’s ID, etc. If others get this string, they can get these key business information, thus avoiding obtaining them through database queries and other methods.
Structure of JWT is a string composed of three pieces of information connected by .
Header
.Payload
.Signature
for example:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxIn0.ihOZFzg3ZGIbBMneRy-4RMqors1P3nuO-wRJnQtTzWQ
Header
Carrying two parts of information: token type and encryption algorithm used
{
"typ": "JWT",
"alg": "HS256"
}
Token type: JWT Encryption algorithm: HS256
Payload
A place to store valid information
iss: jwt issuer sub: user for whom jwt is oriented aud: the party receiving jwt exp: expiration timestamp (the expiration time of jwt, this expiration time must be greater than the issuance time) nbf: defines before what time, the jwt is Unavailable iat: the issuance time of jwt jti: the unique identity of jwt, mainly used as a one-time token to avoid replay attacks
Signature
Visa header and payload contents. Use the algorithm declared in the Header and receive three parameters: base64-encoded Header, base64-encoded Payload and secret for operation. The key secret is stored on the server, and the server will generate a token and verify it based on this key.
2.environment setup
Refer to the mysql module in the code repository. Only docker-compose.yml is posted here.
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"
run
docker-compose -f docker-compose.yml -p mysql5.7 up -d
init table
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.Code Project
Experimental goal:Implement JWT issuance and verification
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 generate
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 verify
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 {
}
}
The above are just some key codes. For all codes, please see the code repository below.
Code repository
4.Test
- start Spring Boot application
- login (http://localhost:8088/api/login?username=admin&password=123)
{
"user": {
"username": "admin",
"password": "123",
"id": "1"
},
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxIn0.ihOZFzg3ZGIbBMneRy-4RMqors1P3nuO-wRJnQtTzWQ"
}
- set token in the 在header,access url (http://localhost:8088/api/getMessage)
5.Reference
- https://www.v2ex.com/t/817906
- http://www.liuhaihua.cn/archives/710374.html
- https://github.com/ChuaWi/SpringBoot-JWT/tree/master
6.Questions
Here are 2 questions for readers
- How to refresh the token after it has expired? Ensure that you log in again when it expires, instead of automatically refreshing in the background, and it never expires?
- If you want to implement the function of forcing users to offline, how to implement it?