JWT 详解与使用方法

本文介绍了JWT(JSONWebToken)的基础概念,包括其优势、构成、工作流程以及在Java中的应用示例,重点展示了JWT在身份验证中的无状态特性及如何使用HMACSHA256或RSA进行签名验证。
摘要由CSDN通过智能技术生成

Jwt 令牌

1、JWT 基础介绍

1)What Is JWT?

  • JWT(JSON Web Token),是一种开放的标准,它定义了一种紧凑且独立的方式,用于在各方之间以 JSON 对象的形式安全的传递信息,是一种轻量级的、自包含的、可验证的令牌,通常用于身份验证授权机制
  • 其中包含了有关用户或者实体的信息,数字签名或者加密信息,来保证令牌的完整性和安全性。

2)Why JWT?

首先来看一下同样可以用于身份验证的 session 的实现原理

JSP系列教材 (七)- session 详解

既然有了 session 就能实现对用户的验证,那我们为什么还需要 JWT 技术呢?

来看一下 session 技术的一些缺点

  • 使用会话技术需要在服务器端来维护会话的状态,这意味着服务器需要分配内存来存储会话的数据,当应用的规模很大的适合,这样会导致服务器的负载增加。
  • 会话技术使用服务器存储会话状态的性质,导致了在扩展应用方面引起的问题,需要考虑如何共享会话状态,实现负载均衡和扩展性。
  • 同时读写会话数据引入性能开销,尤其是在高负载的应用中,等等。

比起 session 会话技术,jwt 有以下的优势

  • JWT 是无状态的,所有的必要信息都存储子啊令牌中,不需要在服务端维护会话状态中。
  • JWT 可以用于在不同域之间来安全的传递用户身份信息
  • JWT 可以使用数字签名或者加密来验证令牌的完整性和保密性,防止数据的篡改和伪造。
  • JWT 的标准规范不限制用途,可以包含任何需要的信息。

JWT的工作原理 - 知乎

2、JWT 认证

JWT 主要是由三部分组成的:Header 标头、Payload 负载、Signature 签名

1)JWT的结构

Header

​ 标头通常包含了令牌的类型(JWT)和所使用的签名算法(例如,HMAC SHA256或RSA),以及其他元数据。

{
  "alg": "HS256", // 签名算法,例如使用 HMAC SHA-256
  "typ": "JWT" // 令牌的类型
}

Payload

​ 负载包含了有关用户或实体的声明,这些声明可以是标准声明(例如,身份验证时间,到期时间等)或自定义声明(根据需要添加的任何信息)。

{
  "sub": "1234567890", // 主题(Subject),通常是用户的唯一标识
  "name": "John Doe", // 用户姓名
  "iat": 1516239022 // 签发时间(Issued At),以 UNIX 时间戳表示
}

Signature

​ 签名用于验证令牌的完整性。签名算法是在标头中指定的,通常使用密钥来生成签名。只有拥有正确密钥的接收方才能验证签名并确定令牌是否有效。

//签名部分用于验证令牌的完整性,通常使用密钥和指定的签名算法进行签名。签名的内容是基于头部和负载的编码字符串。
完整的 jwt 令牌的示例
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.sNCC5z6JBi0kjJvIzl3pJR5CfFmm9Xmp9_X9t_Y4Lrw

2)JWT 认证的工作流程

  1. 前端通过表单将自己的信息发送到后端的接口上。
  2. 后端接收到这些信息之后,将这些信息作为 JWT 的负载(Payload),将其和头部进行编码拼接和签名,形成一个 JWT 密钥(Token)。
  3. 服务器将生成的这个 JWT 作为响应的一部分返回给客户端。
  4. 客户端收到 JWT 并在后续请求中将其包含在请求头或其他适当位置中。
  5. 服务器在接收到请求后,验证 JWT 的签名以确保其完整性和真实性。
  6. 如果 JWT 有效,服务器使用其中的信息来授权用户执行请求的操作。

​

3、使用 JWT

1)如何在 Java 程序中使用 JWT 认证?

引入依赖
<properties>
    <jjwt>0.9.1</jjwt>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>${jjwt}</version>
</dependency>

这里我们引入 jjwt(Java JWT: JSON Web Token for Java and Android) 依赖来实现在 Java 中使用 JWT,

示例代码

​ 因为解析和创建 jwt 的操作是被经常使用的,为了避免代码的重复将其封装成了 Util 类

    /**
     * 生成jwt
     * 使用Hs256算法, 私匙使用固定秘钥
     *
     * @param secretKey jwt秘钥
     * @param ttlMillis jwt过期时间(毫秒)
     * @param claims    设置的信息
     * @return
     */
	public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
        
        // 指定签名的时候使用的签名算法,也就是header那部分
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        // 生成JWT的时间
        long expMillis = System.currentTimeMillis() + ttlMillis;
        Date exp = new Date(expMillis);

        // 设置jwt的body
        JwtBuilder builder = Jwts.builder()
                // 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
                .setClaims(claims)
                // 设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置过期时间
                .setExpiration(exp);

        return builder.compact();
    }


	/**
     * Token解密
     *
     * @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
     * @param token     加密后的token
     * @return
     */
    public static Claims parseJWT(String secretKey, String token) {
        // 得到DefaultJwtParser
        Claims claims = Jwts.parser()
                // 设置签名的秘钥
                .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
                // 设置需要解析的jwt
                .parseClaimsJws(token).getBody();
        return claims;
    }

​ 登录功能的实现

    /**
     * 登录
     *
     * @param employeeLoginDTO
     * @return
     */
    @PostMapping("/login")
    public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
        log.info("员工登录:{}", employeeLoginDTO);

        Employee employee = employeeService.login(employeeLoginDTO);

        //登录成功后,生成 jwt 令牌
        Map<String, Object> claims = new HashMap<>();
        claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
        String token = JwtUtil.createJWT(
                jwtProperties.getAdminSecretKey(),
                jwtProperties.getAdminTtl(),
                claims);

        EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
                .id(employee.getId())
                .userName(employee.getUsername())
                .name(employee.getName())
                .token(token)
                .build();

        return Result.success(employeeLoginVO);

​ jwt 校验的拦截器

@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {

    @Autowired
    private JwtProperties jwtProperties;

    /**
     * 校验jwt
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断当前拦截到的是Controller的方法还是其他资源
        if (!(handler instanceof HandlerMethod)) {
            //当前拦截到的不是动态方法,直接放行
            return true;
        }

        //1、从请求头中获取令牌
        String token = request.getHeader(jwtProperties.getAdminTokenName());

        //2、校验令牌
        try {
            log.info("jwt校验:{}", token);
            Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);
            Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());
            log.info("当前员工id{}", empId);
            //3、通过,放行
            BaseContext.setCurrentId(empId);
            return true;
        } catch (Exception ex) {
            //4、不通过,响应401状态码
            response.setStatus(401);
            return false;
        }
    }
}

这样就实现了 jwt 在网络层的一个基本应用

JwtBulider 中可以设置的属性
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package io.jsonwebtoken;

import java.security.Key;
import java.util.Date;
import java.util.Map;

public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
    JwtBuilder setHeader(Header var1);

    JwtBuilder setHeader(Map<String, Object> var1);

    JwtBuilder setHeaderParams(Map<String, Object> var1);

    JwtBuilder setHeaderParam(String var1, Object var2);

    JwtBuilder setPayload(String var1);

    JwtBuilder setClaims(Claims var1);

    JwtBuilder setClaims(Map<String, Object> var1);

    JwtBuilder addClaims(Map<String, Object> var1);

    JwtBuilder setIssuer(String var1);

    JwtBuilder setSubject(String var1);

    JwtBuilder setAudience(String var1);

    JwtBuilder setExpiration(Date var1);

    JwtBuilder setNotBefore(Date var1);

    JwtBuilder setIssuedAt(Date var1);

    JwtBuilder setId(String var1);

    JwtBuilder claim(String var1, Object var2);

    JwtBuilder signWith(SignatureAlgorithm var1, byte[] var2);

    JwtBuilder signWith(SignatureAlgorithm var1, String var2);

    JwtBuilder signWith(SignatureAlgorithm var1, Key var2);

    JwtBuilder compressWith(CompressionCodec var1);

    String compact();
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

*Soo_Young*

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值