JWT(1)介绍与使用

一、介绍

1、简介

JWT无状态登录,是token的一种。全称json web token轻量级的授权和身份认证规范。跨平台存在。结构是 JSON,它是一个 JavaScript 对象表示法。它有各种部分,标头、有效负载和签名部分。这些都被打包起来,然后对 Base64 进行编码和签名,以便它是 URL 安全的。

2、与session相对比较

(1)有状态登录session:tomcat中,把用户的登录信息保存在服务端的session中,再给用户一个cookie值。记录对应的session值,下次客户端带cookie来访问时,再识别对应的session找到用户的信息。缺点:服务器的负荷变大,保存大量的session数据。

(2)无状态登录jwt:服务器不保存用户的登录信息,由客户端请求携带自己的身份信息(带入token来获取身份认证)。服务器不再保存用户的登录信息,多次请求不用访问回到同一台服务,服务器可以弹性伸缩,减小服务端压力,缺点一旦JWT签发,在有效期内这个token将会一直有效,而session有自己的会话周期。

3、流程:
  • 第一次请求访问时候,由服务器对用户进行身份验证
  • 验证通过,将身份信息加密进程token,返回给客户端,作为其访问其他服务器集群的身份凭证。
  • 每次请求携带token,服务器对其进行解密判断有效性。

4、安全问题:

token必须安全可靠,采用JWT + RSA非对称加密,另外,JWT一旦签发,在有效期内将会一直有效,可以通过设置较短过期时间,或在业务接口再次做判断、再加入拦截等方法来减小安全风险。

(1)Base64编码方式是可逆的,也就是透过编码后发放的Token内容是可以被解析的。一般而言,我们都不建议在有效载荷内放敏感讯息,比如使用者的密码。

(2)JWT其中的一个组成内容为Signature,可以防止通过Base64可逆方法回推有效载荷内容并将其修改。因为Signature是经由Header跟Payload一起Base64组成的。

(3) Cookie 被窃取了第三方可以做 CSRF 攻击:Cookie丢失,就表示身份就可以被伪造。故官方建议的使用方式是存放在LocalStorage中,并放在请求头中发送。

5、失效问题
  • 无状态JWT令牌(Stateless JWT Token)发放出去之后,不能通过服务器端让令牌失效,必须等到过期时间过才会失去效用。
  • 假设在这之间Token被拦截,或者有权限管理身份的差异造成授权Scope修改,都不能阻止发出去的Token失效并要求使用者重新请求新的Token。
6、在线解析平台

New tab (jwt.io)

二、结构

在紧凑的形式中,JWT包含三个由点(.)分隔的部分,它们分别是:header、payload、sign。

如我生成了一个jwt  token:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJ3ZWItZGVtbyIsImlzcyI6Ind0eXkiLCJ1c2VybmFtZTEiOiJscyIsImV4cCI6MTcyMDE2NjY4NCwidXNlcm5hbWUiOiJscyJ9.QZpJ0mMjm7PmXFb682Xnp5KhaUp8gQ50g-rOU8nvYow
1、Header
1.1、介绍

第一部分是header。为base64编码,可解码,不要存放关键信息。如我这里解码得到:

或者通过jwt在线解析平台: 

1.2、组成

Header通常由两部分组成:

(1)令牌的类型(即JWT);

(2)常用的加密算法,见

com.auth0.jwt.algorithms.Algorithm
2、Payload

第二部分是包含具体的身份信息和声明 (claims)的载荷。为base64编码,可解码。

放声明内容,就是存放沟通讯息的地方,在定义上有3种声明(Claims):

(1)Registered claims(注册声明):
这些是一组预先定义的声明,它们不是强制性的,但推荐使用,以提供一组有用的,可互操作的声明。 其中一些是:iss(发行者),exp(到期时间),sub(主题),aud(受众)等。#Registered Claim Names#

(2)Public claims(公开声明):
这些可以由使用JWT的人员随意定义。 但为避免冲突,应在IANA JSON Web令牌注册表中定义它们,或将其定义为包含防冲突命名空间的URI。
(3)Private claims(私有声明):
这些是为了同意使用它们但是既没有登记,也没有公开声明的各方之间共享信息,而创建的定制声明。

 如我这里解码得到:

通过jwt在线解析平台:

3、Signature
3.1、介绍

签名,由前两部分加密形成,用于验证 Token 的完整性和真实性,不能base64解码。

如我使用一个sign通过base64解码不了

3.2、加密算法

 Signature 不能 被解密。因为 Signature 并不是加密的结果,而是使用指定的加密算法(如 HMAC、RSA 等)基于 Header 和 Payload 计算出来的一个摘要或签名,相当于先加密再hash。Signature 的作用是用于验证 JWT 是否被篡改过,而不是用来获取原始数据或加密的数据。

计算方法:

Signature是这么算出来的?

var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'your_secret');

说明:
1、header和payload先用base64加密用点链接起来,再用header里alg所指定的算法计算摘要
2、your_secret 就是秘钥,加密方和解密方共同知道的,解密的时候要用

如:使用HMAC SHA256算法,签名将按照以下方式创建:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

通过在线解析平台:

3.3、使用建议

不要存放敏感信息在Token里。
Payload中的exp时效不要设定太长。
开启Only Http预防XSS攻击。
如果担心重播攻击(replay attacks )可以增加jti(JWT ID),exp(有效时间) Claim。
在你的应用程序应用层中增加黑名单机制,必要的时候可以进行Block做阻挡(这是针对掉令牌被第三方使用窃取的手动防御)。

三、api操作

1、sign生成token

需要指定算法,算法又依赖了secret,所以需要保管好secret防止别人随意生成来攻击系统

JWT.create()
                //iss:签发方
                .withIssuer("wtyy")
                //aud:接收jwt的一方
                .withAudience("web-demo")
                //exp:jwt的过期时间
                //.withExpiresAt()
                //其他自定义通信信息
                .withClaim("username", userEntity.getUsername())
                .withClaim("age",userEntity.getAge())
                .withExpiresAt(date)
                .sign(algorithm);
2、verify验证token

需要指定算法,这个算法和secret要和生成时保持一致。保管好secret,即使别人用同样的算法但是secret不一样也无法通过校验。

JWTVerifier verifier = JWT.require(algorithm).build();
//验证不通过会抛异常
verifier.verify(token);
3、获取payload

注意,只要是一个jwt,都可以获取payload。

为了防止攻击应该先验证是不是签发方生成的,即先verify再获取payload。

DecodedJWT jwt = JWT.decode(token);
Map<String, Claim> claims = jwt.getClaims();
//通信字段
String username = claims.get("username").asString();
int age = claims.get("age").asInt();

获取payload后还可以通过这些通信信息做进一步的校验,如是否过期issuer是否正确等等从而判断token是否合法。 

四、实现

1、(token)jwt插件:

有两个,Auto0和jsonwebtoken都可以生成token

  • 性能方面auth0更优。
  • 功能方面jsonwebtoken支持更高级的应用,如高级加密算法等。

其中,(1)auth0的maven依赖为:

 <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.7.0</version>
        </dependency>

(2)jsonwebtoken的maven依赖为:

 <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt</artifactId>
      <version>0.9.0</version>
    </dependency>
 2、性能对比:
(1)Auto0

工具类

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import org.springframework.beans.factory.annotation.Value;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class Auth0Util {
    /**
     * 引入apollo的配置
     */
    private static String TOKEN_KEY = "54f12048-1f56-45c1-a3f1-2c546bc2bb42";
    /**
     * jwt有效时间
     */
    private static long TOKEN_TIMEOUT = 10* 60 * 1000;

    /**
     * jwt生成方
     */
    private final static String JWT_ISSUER = "lb-fw-gw";

    /**
     * 生成jwt
     *
     * @param header
     * @return
     */
    public static String create(Map<String, Object> header) {
        return create(header, new HashMap<>(0), JWT_ISSUER, TOKEN_TIMEOUT);
    }

    public static String create(Map<String, Object> header, Map<String, String> claims) {
        return create(header, claims, JWT_ISSUER, TOKEN_TIMEOUT);
    }

    public static String create(Map<String, Object> header, long timeout) {
        return create(header, new HashMap<>(0), JWT_ISSUER, timeout);
    }

    /**
     * 生成jwt
     *
     * @param header
     * @param issuer
     * @return
     */
    public static String create(Map<String, Object> header, Map<String, String> claims, String issuer, long timeout) {
        String token;
        try {
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_KEY);
            Date date = new Date(System.currentTimeMillis() + timeout);
            JWTCreator.Builder builder = JWT.create()
                    .withHeader(header)
                    .withIssuer(issuer)
                    .withExpiresAt(date);
            for (String key : claims.keySet()) {
                builder.withClaim(key, claims.get(key));
            }
            token = builder.sign(algorithm);
        } catch (JWTVerificationException exception) {
            System.out.println(exception.getMessage());
            return null;
        }
        return token;
    }

    /**
     * 验证jwt
     *
     * @param token
     * @return
     */
    public static DecodedJWT decode(String token) {
        DecodedJWT jwt;
        try {
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_KEY);
            JWTVerifier verifier = JWT.require(algorithm)
//                    .withIssuer(JWT_ISSUER)
//                    .acceptLeeway(1)
//                    .acceptExpiresAt(1)
//                    .acceptIssuedAt(1)
//                    .acceptNotBefore(1)
                    .build();
            jwt = verifier.verify(token);
        } catch (JWTVerificationException exception) {
            System.out.println(exception.getMessage());
            return null;
        }
        return jwt;
    }
}

 单测:

import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.Map;

class Auth0UtilTest {

    /**
     * jwt创建测试结果
     * 次数 : 3批耗时ms
     * 1000 : 1502 1673 1513
     * 10000 : 2353 2319 2311
     * 100000 : 5681 5604 5452
     */
    @Test
    void create() {
        Map<String, Object> header = new HashMap<>(4);
        header.put("auth", "level1");
        Map<String, String> claims = new HashMap<>(4);
        claims.put("user", "xxxs");
        long btime = System.currentTimeMillis();
        int times = 100000;
        for (int i = 1; i <= times; i++) {
            Auth0Util.create(header);
        }
        System.out.println(System.currentTimeMillis() - btime);
    }

    /**
     * jwt校验测试结果
     * 次数 : 3批耗时ms
     * 1000 : 898 927 939
     * 10000 : 1973 1992 1882
     * 100000 : 4402 4562 4649
     */
    @Test
    void decode() {
        Map<String, Object> header = new HashMap<>(4);
        header.put("auth", "level1");
        Map<String, String> claims = new HashMap<>(4);
        claims.put("user", "xxxs");
        String token = Auth0Util.create(header); //创建一个新的token
        System.out.println("token:" + token);
        int times = 100000; //循环次数
        long btime = System.currentTimeMillis();
        for (int i = 1; i <= times; i++) {
            Auth0Util.decode(token);
        }
        System.out.println(System.currentTimeMillis() - btime);
    }
}
(2)jsonwebtoken

工具类

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @description: jsonwebtoken工具类
 */
public class JsonWebTokenUtil {
    /**
     * jwt密钥 必须大于32个字符
     */
    private static String TOKEN_KEY = "54f12048-1f56-45c1-a3f1-2c546bc2bb42";
    /**
     * jwt有效时间
     */
    private static long TOKEN_TIMEOUT = 60 * 30 * 1000;

    /**
     * jwt生成方
     */
    private final static String JWT_ISSUER = "lb-fw-gw";

    /**
     * 生成jwt
     *
     * @param header
     * @return
     */
    public static String create(Map<String, Object> header) {
        return create(header, new HashMap<>(2), JWT_ISSUER, TOKEN_TIMEOUT);
    }

    public static String create(Map<String, Object> header, Map<String, Object> claims) {
        return create(header, claims, JWT_ISSUER, TOKEN_TIMEOUT);
    }

    public static String create(Map<String, Object> header, long timeout) {
        return create(header, new HashMap<>(2), JWT_ISSUER, timeout);
    }

    public static String create(Map<String, Object> header, Map<String, Object> claims, long timeout) {
        return create(header, claims, JWT_ISSUER, timeout);
    }

    /**
     * 生成jwt
     *
     * @param header
     * @return
     */
    public static String create(Map<String, Object> header, Map<String, Object> claims, String issuer, long timeout) {
        String token;
        try {
            Date date = new Date(System.currentTimeMillis() + timeout);
            SecretKey key = Keys.hmacShaKeyFor(TOKEN_KEY.getBytes());
            token = Jwts.builder()
                    .setHeader(header)
                    .setClaims(claims)
                    .setIssuer(issuer)
                    .setExpiration(date)
                    .signWith(key, SignatureAlgorithm.HS256)
                    .compact();
        } catch (JwtException exception) {
            System.out.println(exception.getMessage());
            return null;
        }
        return token;
    }

    /**
     * 验证jwt
     *
     * @param token
     * @return
     */
    public static Jws<Claims> decode(String token) {
        Jws<Claims> claimsJws;
        try {
            Key key = new SecretKeySpec(TOKEN_KEY.getBytes(), SignatureAlgorithm.HS256.getJcaName());
            claimsJws = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
        } catch (JwtException exception) {
            System.out.println(exception.getMessage());
            return null;
        }
        return claimsJws;
    }
}

单测:

import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.Map;
class JsonWebTokenUtilTest {

    /**
     * jwt创建测试结果
     * 次数 : 3批耗时ms
     * 1000 : 2037 1850 2052
     * 10000 : 4874 4833 4908
     * 100000 : 18598 17841 17942
     */
    @Test
    void create() {
        HashMap<String, Object> header = new HashMap<>(4);
        header.put("auth", "level1");
        Map<String, Object> claims = new HashMap<>(4);
        claims.put("user", "xxxs");
        long btime = System.currentTimeMillis();
        int times = 100000;
        for (int i = 1; i <= times; i++) {
            JsonWebTokenUtil.create(header,claims);
        }
        System.out.println(System.currentTimeMillis() - btime);
    }

    /**
     * jwt校验测试结果
     * 次数 : 3批耗时ms
     * 1000 : 2128 1980 1933
     * 10000 : 9763 10073 9494
     * 100000 : 47531 48893 48845
     */
    @Test
    void decode() {
        HashMap<String, Object> header = new HashMap<>(4);
        header.put("auth", "level1");
        Map<String, Object> claims = new HashMap<>(4);
        claims.put("user", "xxxs");
        String token = JsonWebTokenUtil.create(header,claims); //创建一个新的token
        int times = 100000;
        long btime = System.currentTimeMillis();
        for (int i = 1; i <= times; i++) {
            JsonWebTokenUtil.decode(token);
        }
        System.out.println(System.currentTimeMillis() - btime);
    }
}
3、demo

(1)pom:

<dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.7.0</version>
    </dependency>

(2)util: 

package com.demo.security.util;
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.interfaces.DecodedJWT;
import com.demo.security.constant.UserConstants;


import javax.servlet.http.HttpServletRequest;
import java.util.Date;

public class JwtUtil {

    /**
     * 校验token是否正确
     * @param token  密钥
     * @param secret 用户的密码
     * @return 是否正确
     */
    public static boolean verify(String token, String username, String secret) {
        try {
            // 根据密码生成JWT效验器
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm).withClaim("username", username).build();
            // 效验TOKEN
            DecodedJWT jwt = verifier.verify(token);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 获得token中的信息无需secret解密也能获得
     * @return token中包含的用户名
     */
    public static String getUsername( HttpServletRequest req) {
        try {
            String token =  req.getHeader("token");
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    public static String getUsername( String token) {
        try {
            DecodedJWT jwt = JWT.decode(token);
            return jwt.getClaim("username").asString();
        } catch (JWTDecodeException e) {
            return null;
        }
    }

    /**
     * 生成签名,5min(分钟)后过期
     * @param username 用户名
     * @param secret   用户的密码
     * @return 加密的token
     */
    public static String sign(String username, String secret) {
         Date date = new Date(System.currentTimeMillis() + 30 * 60 * 1000);
        Algorithm algorithm = Algorithm.HMAC256(secret);
        // 附带username信息
        return JWT.create()
                //iss:签发方
                .withIssuer("wtyy")
                //aud:接收jwt的一方
                .withAudience("web-demo")
                //exp:jwt的过期时间,这个过期时间必须要大于签发时间
                //.withExpiresAt()
                //其他自定义通信信息
                .withClaim("username", username)
                .withClaim("username1",username)

                .withExpiresAt(date)
                .sign(algorithm);
    }
}

(3)测试:

如我使用sign生成了一个token,getUsername解析token获取其中的userName字段:

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
laravel-jwt 是一个用于在 Laravel 应用程序中实现 JSON Web Token (JWT) 认证的扩展包。要配置和使用 laravel-jwt,可以按照以下步骤进行: 1. 安装扩展包:可以使用 Composer 在 Laravel 项目中安装 laravel-jwt。打开终端并运行以下命令: ``` composer require tymon/jwt-auth ``` 2. 配置扩展包:安装完成后,需要发布配置文件和生成 JWT 密钥。运行以下命令: ``` php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider" php artisan jwt:secret ``` 3. 配置环境变量:在 `.env` 文件中配置 JWT 相关的环境变量。在 `.env` 文件中添加以下配置项: ``` JWT_SECRET=your_secret_key JWT_TTL=60 ``` `your_secret_key` 是你生成的 JWT 密钥,`JWT_TTL` 是 JWT 的过期时间(以分钟为单位)。 4. 配置用户认证:打开 `config/auth.php` 文件,将 `api` 驱动程序更改为 `jwt`: ```php 'guards' => [ 'api' => [ 'driver' => 'jwt', 'provider' => 'users', ], ], ``` 5. 生成 JWT:你可以使用 `jwt()` 函数来生成 JWT。例如,可以在控制器中使用以下代码生成 JWT: ```php use Illuminate\Support\Facades\Auth; use Tymon\JWTAuth\Facades\JWTAuth; $token = JWTAuth::fromUser(Auth::user()); ``` 6. 验证 JWT:你可以使用 `jwt()` 函数来验证 JWT。例如,可以在路由中使用以下代码进行 JWT 验证: ```php Route::middleware('auth:api')->get('/user', function (Request $request) { return $request->user(); }); ``` 这些是基本的配置和使用步骤。你可以根据具体的需求进一步定制和使用 laravel-jwt。有关更多信息,请参阅 laravel-jwt 的文档。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

w_t_y_y

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

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

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

打赏作者

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

抵扣说明:

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

余额充值