JSON Web Tokens的实现原理


Posted in Python onApril 02, 2017

前言

最近在做一个Python项目的改造,将python项目重构为Java项目,过程中遇到了这个知识点,觉得这个蛮实用的,所以下班后回来趁热打铁写下这篇总结,希望后面的人能够有所借鉴,少走弯路。

一、优势简介

JSON Web Tokens简称jwt,是rest接口的一种安全策略。本身有很多的优势:

解决跨域问题:这种基于Token的访问策略可以克服cookies的跨域问题。

服务端无状态可以横向扩展,Token可完成认证,无需存储Session。

系统解耦,Token携带所有的用户信息,无需绑定一个特定的认证方案,只需要知道加密的方法和密钥就可以进行加密解密,有利于解耦。

防止跨站点脚本攻击,没有cookie技术,无需考虑跨站请求的安全问题。

二、原理简介

JSON Web Tokens的格式组成,jwt是一段被base64编码过的字符序列,用点号分隔,一共由三部分组成,头部header,消息体playload和签名sign。

1.jwt的头部Header是json格式:

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

其中typ是type的简写,代表该类型是JWT类型,加密方式声明是HS256,exp代表当前时间.

2.jwt的消息体Playload

{
  "userid":"123456",
  "iss":"companyName"
}

消息体的具体字段可根据业务需要自行定义和添加,只需在解密的时候注意拿字段的key值获取value。

3.签名sign的生成

最后是签名,签名的生成是把header和playload分别使用base64url编码,接着用'.‘把两个编码后的字符串连接起来,再把这拼接起来的字符串配合密钥进行HMAC SHA-256算法加密,最后再次base64编码下,这就拿到了签名sign. 最后把header和playload和sign用'.‘ 连接起来就生成了整个JWT。

三、校验简介

整个jwt的结构是由header.playload.sign连接组成,只有sign是用密钥加密的,而所有的信息都在header和playload中可以直接获取,sign的作用只是校验header和playload的信息是否被篡改过,所以jwt不能保护数据,但以上的特性可以很好的应用在权限认证上。

1.加密

比如要加密验证的是userid字段,首先按前面的格式组装json消息头header和消息体playload,按header.playload组成字符串,再根据密钥和HS256加密header.playload得到sign签名,最后得到jwtToken为header.playload.sign,在http请求中的url带上参数想后端服务请求认证。

2. 解密

后端服务校验jwtToken是否有权访问接口服务,进行解密认证,如校验访问者的userid,首先

用将字符串按.号切分三段字符串,分别得到header和playload和sign。然后将header.playload拼装用密钥和HAMC SHA-256算法进行加密然后得到新的字符串和sign进行比对,如果一样就代表数据没有被篡改,然后从头部取出exp对存活期进行判断,如果超过了存活期就返回空字符串,如果在存活期内返回userid的值。

四、代码示例

1.python代码的加密解密

#!/usr/bin/env python
# coding: utf-8

from itsdangerous import BadTimeSignature, SignatureExpired
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer

APP_SECRET_KEY="secret"
MAX_TOKEN_AGE=1800
token_generator = Serializer(APP_SECRET_KEY, expires_in=MAX_TOKEN_AGE)

def generate_auth_token(userid):
  access_token = token_generator.dumps({"userid":userid})
  return access_token
def verify_token(token):
  try:
    user_auth = token_generator.loads(token)
    print type(token_generator)
  except SignatureExpired as e:
    raise e
  except BadTimeSignature as e:
    raise e
  return user_auth

2. java代码的加密解密

package api.test.util;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;

/**
 * jwt加解密实现
 * 
 * @author zhengsc
 */
@Slf4j
public class TokenUtil {

  private String ISSUER = "companyName"; // 机构

  private String APP_SECRET_KEY = "secret"; // 密钥

  private long MAX_TOKEN_AGE = 1800; // 存活期

  /**
   * 生成userId的accessToken
   * 
   * @param userid
   * @return
   */
  public String generateAccessToken(String userid) {
    JSONObject claims = new JSONObject();
    claims.put("iss", ISSUER);
    claims.put("userid", userid);
    String accessToken = sign(claims, APP_SECRET_KEY);
    return accessToken;
  }

  /**
   * 解密程序返回userid
   * 
   * @param token
   * @return
   */
  public String verifyToken(String token) {
    String userid = "";
    try {
      String[] splitStr = token.split("\\.");
      String headerAndClaimsStr = splitStr[0] + "." +splitStr[1];
      String veryStr = signHmac256(headerAndClaimsStr, APP_SECRET_KEY);
      // 校验数据是否被篡改
      if (veryStr.equals(splitStr[2])) {
        String header = new String(Base64.decodeBase64(splitStr[0]),"UTF-8");
        JSONObject head = JSONObject.fromObject(header);
        long expire = head.getLong("exp") * 1000L;
        long currentTime = System.currentTimeMillis();
        if (currentTime <= expire){ // 验证accessToken的有效期
          String claims = new String(Base64.decodeBase64(splitStr[1]),"UTF-8");
          JSONObject claim = JSONObject.fromObject(claims);
          userid = (String) claim.get("userid");
        }
      }
    } catch (UnsupportedEncodingException e) {
      log.error(e.getMessage(), e);
    }

    return userid;
  }

  /**
   * 组装加密结果jwt返回
   * 
   * @param claims
   * @param appSecretKey
   * @return
   */
  private String sign(JSONObject claims, String appSecretKey) {
    String headerAndClaimsStr = getHeaderAndClaimsStr(claims);
    String signed256 = signHmac256(headerAndClaimsStr, appSecretKey);
    return headerAndClaimsStr + "." + signed256;
  }

  /**
   * 拼接请求头和声明
   * 
   * @param claims
   * @return
   */
  private String getHeaderAndClaimsStr(JSONObject claims) {
    JSONObject header = new JSONObject();
    header.put("alg", "HS256");
    header.put("typ", "JWT");
    header.put("exp", System.currentTimeMillis() + MAX_TOKEN_AGE * 1000L);
    String headerStr = header.toString();
    String claimsStr = claims.toString();
    String headerAndClaimsStr = Base64.encodeBase64URLSafeString(headerStr.getBytes()) + "."
        + Base64.encodeBase64URLSafeString(claimsStr.getBytes());
    return headerAndClaimsStr;
  }

  /**
   * 将headerAndClaimsStr用SHA1加密获取sign
   * 
   * @param headerAndClaimsStr
   * @param appSecretKey
   * @return
   */
  private String signHmac256(String headerAndClaimsStr, String appSecretKey) {
    SecretKey key = new SecretKeySpec(appSecretKey.getBytes(), "HmacSHA256");
    String result = null;
    try {
      Mac mac;
      mac = Mac.getInstance(key.getAlgorithm());
      mac.init(key);
      result = Base64.encodeBase64URLSafeString(mac.doFinal(headerAndClaimsStr.getBytes()));
    } catch (NoSuchAlgorithmException | InvalidKeyException e) {
      log.error(e.getMessage(), e);
    }
    return result;
  }

}

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持三水点靠木!

Python 相关文章推荐
python 字符串格式化代码
Mar 17 Python
Python基于Tkinter实现的记事本实例
Jun 17 Python
Python 判断 有向图 是否有环的实例讲解
Feb 01 Python
python操作xlsx文件的包openpyxl实例
May 03 Python
Jupyter中直接显示Matplotlib的图形方法
May 24 Python
对Python 除法负数取商的取整方式详解
Dec 12 Python
Python实现定期检查源目录与备份目录的差异并进行备份功能示例
Feb 27 Python
python networkx 根据图的权重画图实现
Jul 10 Python
详解将Python程序(.py)转换为Windows可执行文件(.exe)
Jul 19 Python
深入了解NumPy 高级索引
Jul 24 Python
浅谈Python从全局与局部变量到装饰器的相关知识
Jun 21 Python
Python爬虫框架之Scrapy中Spider的用法
Jun 28 Python
Python 40行代码实现人脸识别功能
Apr 02 #Python
Python可变参数用法实例分析
Apr 02 #Python
Python编程实现数学运算求一元二次方程的实根算法示例
Apr 02 #Python
Python中selenium实现文件上传所有方法整理总结
Apr 01 #Python
详解Python多线程Selenium跨浏览器测试
Apr 01 #Python
Python 基础之字符串string详解及实例
Apr 01 #Python
Python中格式化format()方法详解
Apr 01 #Python
You might like
在任意字符集下正常显示网页的方法一
2007/04/01 PHP
php的POSIX 函数以及进程测试的深入分析
2013/06/03 PHP
使用php测试硬盘写入速度示例
2014/01/27 PHP
jQuery结合PHP+MySQL实现二级联动下拉列表[实例]
2011/11/15 Javascript
js限制文本框只能输入数字方法小结
2014/06/16 Javascript
对JavaScript中this指针的新理解分享
2015/01/31 Javascript
js实现点击链接后延迟3秒再跳转的方法
2015/06/05 Javascript
三种Node.js写文件的方式
2016/03/08 Javascript
Bootstrap富文本组件wysiwyg数据保存到mysql的方法
2016/05/09 Javascript
js oncontextmenu事件使用详解
2017/03/25 Javascript
微信小程序授权获取用户详细信息openid的实例详解
2017/09/20 Javascript
Vue.js自定义事件的表单输入组件方法
2018/03/08 Javascript
基于JavaScript实现一个简单的Vue
2018/09/26 Javascript
手动下载Chrome并解决puppeteer无法使用问题
2018/11/12 Javascript
微信小程序实现打卡日历功能
2020/09/21 Javascript
在 Vue-CLI 中引入 simple-mock实现简易的 API Mock 接口数据模拟
2018/11/28 Javascript
vue动态注册组件实例代码详解
2019/05/30 Javascript
jquery实现有过渡效果的tab切换
2020/07/17 jQuery
[40:10]2015国际邀请赛全明星表演赛
2015/08/07 DOTA
python爬虫入门教程之点点美女图片爬虫代码分享
2014/09/02 Python
对Python信号处理模块signal详解
2019/01/09 Python
Python 移动光标位置的方法
2019/01/20 Python
解决python web项目意外关闭,但占用端口的问题
2019/12/17 Python
iPython pylab模式启动方式
2020/04/24 Python
HTML 5.1来了 9月份正式发布 更新内容预览
2016/04/26 HTML / CSS
Bobbi Brown芭比波朗美国官网:化妆师专业彩妆保养品品牌
2016/08/18 全球购物
Move Free官方海外旗舰店:美国骨关节健康专业品牌
2017/12/06 全球购物
网络工程师面试(三木通信技术有限公司)
2013/06/05 面试题
采购意向书范本
2014/03/31 职场文书
学雷锋活动倡议书
2014/08/30 职场文书
园艺专业毕业生求职信
2014/09/02 职场文书
合作经营协议书范本
2014/09/16 职场文书
四风个人对照检查材料思想汇报
2014/09/25 职场文书
张家口市高新区党工委群众路线教育实践活动整改方案
2014/10/25 职场文书
幼儿园新学期开学寄语
2015/05/27 职场文书
房屋所有权证明
2015/06/19 职场文书