Session 认证和Token 认证

一、基于 Session 认证方式

1.1、http 无状态性

http 是一种无状态协议,就是说每次用户进行用户名和密码认证之后,http 不会留下记录,下一次请求还需要进行认证。因为http不知道每次请求是哪一个用户发出的。

为了能识别哪个用户提交的请求,在用户首次登录之后,在服务端存储一份用户登录的信息,这份信息在服务端就是通过 session 保存,然后把这份信息返回给客户端,客户端存储在 cookie 中,下次请求时,带上 cookie 中的信息,服务端就可以判断出是哪一个用户的请求。

1.2、session认证流程

  1. 客户端向服务器发送用户名、密码等认证信息。
  2. 服务器通过验证后,创建一个 session ,并将 session 信息保存起来。
  3. 服务器向客户端返回一个 sessionId ,客户端自动把 sessionId 存储在 cookie 中。
  4. 后续客户端每一次请求。都会在请求头携带 cookie ,将 sessionId 传给服务器。
  5. 服务器获得 cookie 之后,通过 cookie 中的 sessionId 解析出用户信息,执行相关业务。

1.3、存在的问题

  • 每个用户信息都存储在服务端,随着用户量的增加,服务器的开销会增大。
  • session 存储在服务端中,在分布式系统中,这种方式将会失效,为了保证各个服务器中的 session存储的信息一致,需要引入额外的中间件,比如:redis 等
  • 对于非浏览器客户端不适用,原因在于 session 依赖 cookie,移动端没有 cookie
  • 不安全,session 基于 cookie ,如果 cookie 信息被截获,很容易进行 CSRF(跨域请求伪造攻击)

了解到基于 session 认证方式存在的问题之后,有没有一种认证去解决这些问题?下面我们来看看基于Token认证方式。

二、基于Token认证方式

token 就是验证用户身份的凭证,成为令牌,

2.1、认证流程

  1. 客户端向服务器发送用户名、密码等认证信息。
  2. 服务端通过验证后,服务端会返回已经签名的 Token,也就是 JWT。
  3. 客户端收到 token 后,存储起来,比如放在 cookie 或者 localStorage 中。
  4. 客户端每次向服务端请求资源时需要带上这个 token。
  5. 服务端收到请求后去验证这个 token,成功则返回请求数据。

2.2、特点:

  • 基于 token 的认证方式是一种服务端无状态的认证方式,服务端不存储 token 数据,适合分布式系统。
  • 不依赖cookie机制,适用于浏览器客户端同时也适用移动端。

使用建议

  • 建议将 JWT 存放在 localStorage 中,放在 Cookie 中会有 CSRF 风险。
  • 请求服务端并携带 JWT 的做法是放在 HTTP 请求头中。

三、JWT

3.1、什么是 JWT

概念:JWT 全称 JSON Web Token,是一种基于 Token 的认证授权机制。可以生成 token,也可以解析验证 token。

官网地址https://jwt.io/

先看看官网上 JWT 的具体样式

3.2、JWT 由哪几部分组成

JWT 通常是这样的:xxxxx.yyyyy.zzzzz。 示例:

erlang 复制代码
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

通过示例可以看出 JWT 由 Header、Payload、Signature 三部分组成。

第一部分:Header(头)作用:记录令牌类型、签名算法等。

json 复制代码
{
  "alg": "HS256",
  "typ": "JWT"
}

此 JSON 经过 Base64 编码,形成 JWT 的第一部分。

第二部分:Payload(有效载荷)作用:携带一些用户信息或者过期时间

json 复制代码
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

Payload 默认是不加密的,不要将隐私数据放到 Payload 中,此 JSON 经过 Base64 编码,形成 JWT 的第二部分。

第三部分:Signature(签名)作用:防止 Token 被篡改,确保安全性。

json 复制代码
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

Signature 是对前两部分的签名,这个签名生成需要用到:

  • Header + Payload
  • 存放在服务端的密钥(不能泄露)
  • 签名算法

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用(.)分隔,这个字符串就是 JWT 。

3.3、SpringBoot整合JWT

首先新建一个 springBoot 项目 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">
    <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,代码如下

java 复制代码
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,代码如下:

java 复制代码
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 的方法封装成工具类

java 复制代码
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 的方式去实现用户登录访问控制。


改变你能改变的,接受你不能改变的,关注我,一起成长,共同进步。

相关推荐
2401_882727575 小时前
低代码配置式组态软件-BY组态
前端·后端·物联网·低代码·前端框架
追逐时光者6 小时前
.NET 在 Visual Studio 中的高效编程技巧集
后端·.net·visual studio
大梦百万秋6 小时前
Spring Boot实战:构建一个简单的RESTful API
spring boot·后端·restful
斌斌_____7 小时前
Spring Boot 配置文件的加载顺序
java·spring boot·后端
路在脚下@7 小时前
Spring如何处理循环依赖
java·后端·spring
海绵波波1078 小时前
flask后端开发(1):第一个Flask项目
后端·python·flask
小奏技术9 小时前
RocketMQ结合源码告诉你消息量大为啥不需要手动压缩消息
后端·消息队列
AI人H哥会Java11 小时前
【Spring】控制反转(IoC)与依赖注入(DI)—IoC容器在系统中的位置
java·开发语言·spring boot·后端·spring
凡人的AI工具箱11 小时前
每天40分玩转Django:Django表单集
开发语言·数据库·后端·python·缓存·django
奔跑草-11 小时前
【数据库】SQL应该如何针对数据倾斜问题进行优化
数据库·后端·sql·ubuntu