JWT通用版,JAVA、PHP、JS均可使用

最近想记录一下过往的内容,顺带记录一下通用JWT写法。网上有许多内容重叠并且说不上重点,还有部分内容错误,本人做一下整理。

JWT的原理不想多说,网上一大推。https://jwt.io/  这个网站可以做校验

Java版

pom.xml 先上依赖

        <!-- JWT依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

工具类

package com.xxxx.config;

import io.jsonwebtoken.*;
import org.springframework.stereotype.Component;
import java.util.Date;

@Component
public class JwtTokenUtil {

    //荷载claim的名称
    private static final String CLAIM_KEY_USERNAME = "sub";
    //荷载的创建时间
    private static final String CLAIM_KEY_CREATED = "created";
    //jwt令牌的秘钥
    private static final String secret = "admin";
    //jwt的失效时间
    private static final long expiration = 1000*60*60*24;


    //生成token
    public static String createToken(String admin){

        return Jwts.builder()
                //header
                .setHeaderParam("typ","JWT")
                .setHeaderParam("alg","HS256")
                //payload
                .claim(CLAIM_KEY_USERNAME,admin)
                .claim(CLAIM_KEY_CREATED,new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                //signature
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();
    }


    //解析token
    public static Claims paresToken(String token){
        JwtParser jwtParser = Jwts.parser();
        Jws<Claims> claimsJws = jwtParser.setSigningKey(secret).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        return claims;
    }

    public static void main(String[] args) {
        String token = JwtTokenUtil.createToken("admin_name");
        System.out.println(token);
        System.out.println(JwtTokenUtil.paresToken(token));

    }


}

结果如下:

token:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbl9uYW1lIiwiY3JlYXRlZCI6MTYyNDc2NDE3Mjc0NiwiZXhwIjoxNjI0ODUwNTcyfQ.6XADYWKDkyfUAZjjZac91cPJIxLYR9RhWhbDH0Mp9Fs

claims:

{sub=admin_name, created=1624764172746, exp=1624850572}

https://jwt.io/ 上校验一下

从这里发现了几个问题,

一是java代码的设置过期时间本质就是秒级时间戳,

二是用上述代码产生的token是需要勾选  秘钥为base64加密后的字符。

三补充部分:文件切记使用UTF-8格式。

这里产生了三端通用jwt的注意点。java要想产生(秘钥为非base64加密后)的字符,需要做一些修改。

//createToken方法块中,修改为下述语句

//signature
.signWith(SignatureAlgorithm.HS256, secret.getBytes())
.compact();


//paresToken方法块中,修改为下述语句
Jws<Claims> claimsJws = jwtParser.setSigningKey(secret.getBytes()).parseClaimsJws(token);

修改后的结果:

token:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbl9uYW1lIiwiY3JlYXRlZCI6MTYyNDc2NDk2MzYzMiwiZXhwIjoxNjI0ODUxMzYzfQ.18bHhkf3P8RsoZkNQsjU9DCOrvH5NkR9WZEI8YzJKYc

claims:

{sub=admin_name, created=1624764963632, exp=1624851363}

https://jwt.io/ 上校验一下,勾选后,校验失败

取消后,校验ok

以上是java部分,后续是PHP与js部分

部分网上的教程,用MD5加密来代替加密算法,其实也可以。不过在遇到多系统打通的情况下,需要使用真正指定的加密函数,同时需要注意一些坑。

以秘钥为非base64加密为例的jwt:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbl9uYW1lIiwiY3JlYXRlZCI6MTYyNDc2NDk2MzYzMiwiZXhwIjoxNjI0ODUxMzYzfQ.18bHhkf3P8RsoZkNQsjU9DCOrvH5NkR9WZEI8YzJKYc

HEADER:

{
  "typ": "JWT",
  "alg": "HS256"
}

PAYLOAD:

{
  "sub": "admin_name",
  "created": 1624764963632,
  "exp": 1624851363
}

秘钥:admin

<?php
/**
 * PHP实现jwt
 */
class JwtAuth {

  //头部
  private static $header=array(
      'typ'=>'JWT',   //类型
      'alg'=>'HS256', //生成signature的算法
  );

  //使用HMAC生成信息摘要时所使用的密钥
  private static $key="admin";


  /**
   * 获取jwt token
   * @param array $payload jwt载荷  格式如下非必须
   * [
   * 'iss'=>'jwt_admin', //该JWT的签发者
   * 'iat'=>time(), //签发时间
   * 'exp'=>time()+7200, //过期时间
   * 'nbf'=>time()+60, //该时间之前不接收处理该Token
   * 'sub'=>'www.mano100.cn', //面向的用户
   * 'jti'=>md5(uniqid('JWT').time()) //该Token唯一标识
   * ]
   * @return bool|string
   */
  public static function getToken(array $payload)
  {
    if(is_array($payload))
    {
      $base64header=self::base64UrlEncode(json_encode(self::$header,JSON_UNESCAPED_UNICODE));
      $base64payload=self::base64UrlEncode(json_encode($payload,JSON_UNESCAPED_UNICODE));
      $token=$base64header.'.'.$base64payload.'.'.self::signature($base64header.'.'.$base64payload,self::$key,self::$header['alg']);
      return $token;
    }else{
      return false;
    }
  }


  /**
   * 验证token是否有效,默认验证exp,nbf,iat时间
   * @param string $Token 需要验证的token
   * @return bool|string
   */
  public static function verifyToken( $Token)
  {
    $tokens = explode('.', $Token);
    if (count($tokens) != 3)
      return false;

    list($base64header, $base64payload, $sign) = $tokens;

    //获取jwt算法
    $base64decodeheader = json_decode(self::base64UrlDecode($base64header), JSON_OBJECT_AS_ARRAY);
    if (empty($base64decodeheader['alg']))
      return false;

    //签名验证
    if (self::signature($base64header . '.' . $base64payload, self::$key, $base64decodeheader['alg']) !== $sign)
      return false;

    $payload = json_decode(self::base64UrlDecode($base64payload), JSON_OBJECT_AS_ARRAY);

    //签发时间大于当前服务器时间验证失败
    if (isset($payload['iat']) && $payload['iat'] > time())
      return false;

    //过期时间小宇当前服务器时间验证失败
    if (isset($payload['exp']) && $payload['exp'] < time())
      return false;

    //该nbf时间之前不接收处理该Token
    if (isset($payload['nbf']) && $payload['nbf'] > time())
      return false;

    return $payload;
  }

  /**
   * base64UrlEncode  https://jwt.io/ 中base64UrlEncode编码实现
   * @param string $input 需要编码的字符串
   * @return string
   */
  private static function base64UrlEncode( $input)
  {
    return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
  }

  /**
   * base64UrlEncode https://jwt.io/ 中base64UrlEncode解码实现
   * @param string $input 需要解码的字符串
   * @return bool|string
   */
  private static function base64UrlDecode( $input)
  {
    $remainder = strlen($input) % 4;
    if ($remainder) {
      $addlen = 4 - $remainder;
      $input .= str_repeat('=', $addlen);
    }
    return base64_decode(strtr($input, '-_', '+/'));
  }

  /**
   * HMACSHA256签名  https://jwt.io/ 中HMACSHA256签名实现
   * @param string $input 为base64UrlEncode(header).".".base64UrlEncode(payload)
   * @param string $key
   * @param string $alg  算法方式
   * @return mixed
   */
  private static function signature( $input,  $key,  $alg = 'HS256')
  {
    $alg_config=array(
      'HS256'=>'sha256'
    );
    return self::base64UrlEncode(hash_hmac($alg_config[$alg], $input, $key,true));
  }
}


php 测试代码

//测试和官网是否匹配begin
$payload    = array(
    'sub'     =>'admin_name',
    'created' =>1624764963632,
    'exp'     =>1624851363
);
$jwt=new JwtAuth();
$token=$jwt->getToken($payload);
echo "<pre>";
echo $token;

//对token进行验证签名
$getPayload=$jwt->verifyToken($token);
echo "<br><br>";
var_dump($getPayload);
echo "<br><br>";
//测试和官网是否匹配end

结果为:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbl9uYW1lIiwiY3JlYXRlZCI6MTYyNDc2NDk2MzYzMiwiZXhwIjoxNjI0ODUxMzYzfQ.18bHhkf3P8RsoZkNQsjU9DCOrvH5NkR9WZEI8YzJKYc

array(3) {
  ["sub"]=>
  string(10) "admin_name"
  ["created"]=>
  int(1624764963632)
  ["exp"]=>
  int(1624851363)
}

加密结果与   java 使用    secret.getBytes()   一致。

.signWith(SignatureAlgorithm.HS256, secret.getBytes())

Jws<Claims> claimsJws = jwtParser.setSigningKey(secret.getBytes()).parseClaimsJws(token);

如果java端使用的是如下

.signWith(SignatureAlgorithm.HS256, secret)

jwtParser.setSigningKey(secret.parseClaimsJws(token);

PHP 工具类对应的修改很简单

$key = self::base64UrlDecode($key);  //补上这一句

private static function signature( $input,  $key,  $alg = 'HS256')
  {
    $alg_config=array(
      'HS256'=>'sha256'
    );
    //加上这句话,表示秘钥需要先进行base64解密
    $key = self::base64UrlDecode($key);
    return self::base64UrlEncode(hash_hmac($alg_config[$alg], $input, $key,true));
  }

条件一样:

结果为

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbl9uYW1lIiwiY3JlYXRlZCI6MTYyNDc2NDk2MzYzMiwiZXhwIjoxNjI0ODUxMzYzfQ.Ennv1377qe4DTh-KiQcIwV3Kql20re9VZoHuX451Huc

array(3) {
  ["sub"]=>
  string(10) "admin_name"
  ["created"]=>
  int(1624764963632)
  ["exp"]=>
  int(1624851363)
}

 网站校验通过。同时与java一致。

同时PHP端的工具类也有部分需要注意事项:

base64UrlDecode、base64UrlEncode 为了与java一致,涉及到特殊字符转化所以做了特殊处理,同时文件的编码格式依然需要注意为UTF-8

下面开始JS端的代码


<!-- 引入三个js文件 -->
<script src="http://code.jquery.com/jquery-1.8.3.min.js"></script>
<script src="http://cdn.bootcss.com/crypto-js/3.1.9/crypto-js.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsrsasign/8.0.20/jsrsasign-all-min.js"></script>


<!-- 逻辑代码 -->

<script>

//和普通base64加密不一样
function base64UrlEncode(str) {
   var encodedSource = CryptoJS.enc.Base64.stringify(str);
   var reg = new RegExp('/', 'g');
   encodedSource = encodedSource.replace(/=+$/,'').replace(/\+/g,'-').replace(reg,'_');
   return encodedSource;
}

let header = JSON.stringify({
	"typ": "JWT",
	"alg": "HS256"
})

let payload =JSON.stringify({
  "sub": "admin_name",
  "created": 1624764963632,
  "exp": 1624851363
});

let secretSalt = "admin";

let before_sign = base64UrlEncode(CryptoJS.enc.Utf8.parse(header)) + '.' + base64UrlEncode(CryptoJS.enc.Utf8.parse(payload));

//secretSalt = base64UrlEncode(secretSalt);

let  signature =CryptoJS.HmacSHA256(before_sign, secretSalt);
 signature = base64UrlEncode(signature);
 
let final_sign = before_sign + '.' + signature;
console.log(final_sign);


</script>


结果为:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbl9uYW1lIiwiY3JlYXRlZCI6MTYyNDc2NDk2MzYzMiwiZXhwIjoxNjI0ODUxMzYzfQ.18bHhkf3P8RsoZkNQsjU9DCOrvH5NkR9WZEI8YzJKYc

网站校验结果: 当前为非勾选秘钥加密选项

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值