令牌认证学习

0 阅读1分钟

是什么?

身份令牌是现代Web应用和移动应用中用于管理用户会话的核心机制

Image text

为什么需要令牌

没有令牌的世界——Cookie/Session(基于服务器存储的会话) 的传统模式

  • 你输入用户名密码登录。
  • 服务器验证通过,在服务器的内存里创建一个Session(会话记录),并生成一个对应的 Session ID(会话ID)。
  • 服务器把 Session ID 发给浏览器,浏览器存在 Cookie 里。
  • 以后每次请求,浏览器带上这个 ID。
  • 关键点:服务器收到 ID 后,必须去自己的内存(或数据库)里查找这个 Session 是否存在、是否过期

令牌的出现

无状态(Stateless)。服务器不再存储 Session,而是把用户信息(如用户ID、昵称、过期时间)经过加密,直接生成一个令牌交给客户端。 客户端每次带着这个令牌来,服务器只需要验证令牌本身的签名就能确认身份,无需查数据库或内存

令牌三步走

Image text

常见令牌

JWT(JSON Web Token)

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

JWT 由三部分组成:Header(头部)、Payload(负载)和Signature(签名)

  • 头部(Header):声明了加密算法(比如用了什么哈希算法)。
  • 载荷(Payload):存放实际数据的地方。比如用户ID(user_id: 123)、用户名、过期时间(exp)。注意:这里的数据只是用Base64编码(可解码看到原文),所以千万不要在JWT里存放密码等敏感信息。
  • 签名(Signature):这是最关键的防伪部分。服务器使用只有自己知道的密钥,对前两部分进行加密生成签名。如果有人篡改了令牌里的用户ID,签名就无法匹配,服务器会直接拒绝。
jwt demo
  <dependencies>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId> <!-- 或 jjwt-gson,用于 JSON 处理 -->
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>


package com.jysemel;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtExample {

    // 生成一个足够安全的签名密钥(实际应用中应从配置文件读取或使用密钥库)
    private static final SecretKey SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256); // HS256 需要 256位密钥

    // 过期时间:1小时(毫秒)
    private static final long EXPIRATION_TIME = 3600000;

    public static void main(String[] args) {
        // 1. 生成 JWT
        String token = generateToken("user123", "admin");
        System.out.println("生成的 JWT:\n" + token);

        // 2. 验证并解析 JWT
        parseToken(token);
    }

    /**
     * 生成 JWT
     * @param subject  用户标识(如用户名、用户ID)
     * @param role     用户角色(自定义声明示例)
     * @return JWT 字符串
     */
    public static String generateToken(String subject, String role) {
        // 设置头部(可选,默认即可)
        Map<String, Object> header = new HashMap<>();
        header.put("typ", "JWT");

        // 设置载荷(Claims)
        JwtBuilder builder = Jwts.builder()
                .setHeader(header)                 // 头部
                .setSubject(subject)                // 主题(通常是用户标识)
                .setIssuedAt(new Date())            // 签发时间
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // 过期时间
                .setIssuer("MyApp")                  // 签发者
                .setAudience("MyClient")             // 接收方
                .setId(java.util.UUID.randomUUID().toString()) // JWT ID,用于防止重放攻击
                .claim("role", role)                 // 自定义声明:角色
                .claim("dept", "engineering")        // 自定义声明:部门
                .signWith(SECRET_KEY);                // 签名

        return builder.compact();
    }

    /**
     * 解析 JWT,验证签名并提取数据
     * @param token JWT 字符串
     */
    public static void parseToken(String token) {
        try {
            // 创建解析器,设置签名密钥
            JwtParser parser = Jwts.parserBuilder()
                    .setSigningKey(SECRET_KEY)
                    .build();

            // 解析 JWT,得到 Jws<Claims> 对象
            Jws<Claims> jws = parser.parseClaimsJws(token);

            // 获取头部
            JwsHeader header = jws.getHeader();
            System.out.println("\n--- 解析结果 ---");
            System.out.println("算法:" + header.getAlgorithm());
            System.out.println("类型:" + header.getType());

            // 获取载荷
            Claims claims = jws.getBody();
            System.out.println("主题(subject):" + claims.getSubject());
            System.out.println("签发时间:" + claims.getIssuedAt());
            System.out.println("过期时间:" + claims.getExpiration());
            System.out.println("签发者:" + claims.getIssuer());
            System.out.println("接收方:" + claims.getAudience());
            System.out.println("JWT ID:" + claims.getId());
            System.out.println("角色(自定义声明):" + claims.get("role"));
            System.out.println("部门(自定义声明):" + claims.get("dept"));

        } catch (ExpiredJwtException e) {
            System.err.println("JWT 已过期:" + e.getMessage());
        } catch (UnsupportedJwtException e) {
            System.err.println("不支持的 JWT:" + e.getMessage());
        } catch (MalformedJwtException e) {
            System.err.println("JWT 格式错误:" + e.getMessage());
        } catch (SignatureException e) {
            System.err.println("JWT 签名验证失败:" + e.getMessage());
        } catch (IllegalArgumentException e) {
            System.err.println("JWT 参数非法:" + e.getMessage());
        }
    }
}

OAuth2.0(不透明令牌)

OAuth2.0 是一个开放授权标准,它允许用户授权第三方应用访问其资源,而无需将用户名和密码提供给第三方应用。

  • OAuth 2.0 授权码模式流程图(时序图) Image text
  • OAuth 2.0 核心角色关系图 Image text