http 是一种无状态协议,就是说每次用户进行用户名和密码认证之后,http 不会留下记录,下一次请求还需要进行认证。因为http 不知道每次请求是哪一个用户发出的。
为了能识别哪个用户提交的请求,在用户首次登录之后,在服务端存储一份用户登录的信息,这份信息在服务端就是通过 session 保存,然后把这份信息返回给客户端,客户端存储在 cookie 中,下次请求时,带上 cookie 中的信息,服务端就可以判断出是哪一个用户的请求。
了解到基于 session 认证方式存在的问题之后,有没有一种认证去解决这些问题?下面我们来看看基于Token认证方式。
token 就是验证用户身份的凭证,成为令牌,
使用建议
概念:JWT 全称 JSON Web Token,是一种基于 Token 的认证授权机制。可以生成 token,也可以解析验证 token。
官网地址:https://jwt.io/
先看看官网上 JWT 的具体样式
JWT 通常是这样的:xxxxx.yyyyy.zzzzz
。
示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
通过示例可以看出 JWT 由 Header、Payload、Signature 三部分组成。
第一部分:Header(头)作用:记录令牌类型、签名算法等。
{
"alg": "HS256",
"typ": "JWT"
}
此 JSON 经过 Base64 编码,形成 JWT 的第一部分。
第二部分:Payload(有效载荷)作用:携带一些用户信息或者过期时间
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Payload 默认是不加密的,不要将隐私数据放到 Payload 中,此 JSON 经过 Base64 编码,形成 JWT 的第二部分。
第三部分:Signature(签名)作用:防止 Token 被篡改,确保安全性。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
Signature 是对前两部分的签名,这个签名生成需要用到:
算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用点(.)分隔,这个字符串就是 JWT 。
首先新建一个 springBoot 项目 jwt ,然后在 pom 中引入所需依赖。
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.duan</groupId>
<artifactId>jwt</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.4.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.3</version>
</dependency>
<!-- JWT依赖-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- Springboot测试方法依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
在 JWTTest 测试类中新建生成 JWT 方法 getJWT,代码如下
package com.duan;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.Test;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @author db
* @version 1.0
* @description JWTTest
* @since 2023/12/21
*/
public class JWTTest {
/**
* 生成令牌
*/
@Test
public void getJWT(){
Map<String, Object> map = new HashMap<>();
map.put("id",1);
map.put("username","程序员康康");
String jwt = Jwts.builder().signWith(SignatureAlgorithm.HS256, "cxykk1217") // 设置签名算法和密钥
.setClaims(map) // 设置给定的数据
.setExpiration(new Date(System.currentTimeMillis() + 1000L * 60 * 3))
.compact();
System.out.println(jwt);
}
}
启动测试方法,打印结果如下:
在 JWTTest 测试类中新建解析 JWT 方法 parseJwt,代码如下:
package com.duan;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.junit.Test;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @author db
* @version 1.0
* @description JWTTest
* @since 2023/12/21
*/
public class JWTTest {
/**
* 解析令牌
*/
@Test
public void parseJwt(){
Claims body = Jwts.parser()
.setSigningKey("cxykk1217")
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNzAzOTkxMjg5LCJ1c2VybmFtZSI6Iueoi-W6j-WRmOW6t-W6tyJ9.Q_xG0pDaG2rkix2rEw2SNS5uBwTS5f0FbzcLFpQ0yJY")
.getBody();
System.out.println(body);
}
}
启动测试方法,打印结果如下:
以上测试可以生成和解析 JWT,在项目实际使用时 JWT 一般都是以工具类的方式出现。下面我们把生成和解析 JWT 的方法封装成工具类
package com.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;
/**
* @author db
* @version 1.0
* @description JWTUtils
* @since 2023/12/31
*/
public class JWTUtils {
// 密钥
private static String signKey = "cxykk1217";
// 过期时间
private static Long expire = 1000L*60*30; // 30分钟
/**
* 生成JWT
* @param claims JWT第二部分负载payload中存储的内容
* @return
*/
public static String generateJwt(Map<String,Object> claims){
String jwt = Jwts.builder().signWith(SignatureAlgorithm.HS256, signKey)
.addClaims(claims)
.setExpiration(new Date(System.currentTimeMillis() + expire))
.compact();
return jwt;
}
public static Claims parseJWT(String jwt){
Claims claims = Jwts.parser().setSigningKey(signKey)
.parseClaimsJws(jwt)
.getBody();
return claims;
}
}
代码仓库链接:https://gitee.com/duan138/practice-code/tree/dev/jwt
基于 session 和基于 Token 认证方式,本质上没有什么区别,都是对用户身份的认证机制,只是在使用过程中校验的方式不同,各有优缺点,不能说哪个好哪个不好,要根据实际需求选择响应的方式,后续会有文章实现基于 token 的方式去实现用户登录访问控制。
改变你能改变的,接受你不能改变的,关注我,一起成长,共同进步。