上篇文章中目的是介绍 Json Web Token(以下简称 jwt) ,由于我对 Java 比较熟悉就介绍 Java 服务端 的实现方式,其他语言原理是相同的哈~

下面按照这几个方面来介绍它:

Java 基本实现

开源库 jjwt 的使用

源码解析 jjwt

废话不多说,撸起袖子就是干,上代码

Java 实现

private static final String MAC_INSTANCE_NAME = "HMacSHA256";public static String Hmacsha256(String secret, String message) throwsNoSuchAlgorithmException, InvalidKeyException {

Mac hmac_sha256=Mac.getInstance(MAC_INSTANCE_NAME);

SecretKeySpec key= newSecretKeySpec(secret.getBytes(), MAC_INSTANCE_NAME);

hmac_sha256.init(key);byte[] buff =hmac_sha256.doFinal(message.getBytes());returnBase64.encodeBase64URLSafeString(buff);

}//java jwt

public void testJWT() throwsInvalidKeyException, NoSuchAlgorithmException {

String secret= "eerp";

String header= "{\"type\":\"JWT\",\"alg\":\"HS256\"}";

String claim= "{\"iss\":\"cnooc\", \"sub\":\"yrm\", \"username\":\"yrm\", \"admin\":true}";

String base64Header=Base64.encodeBase64URLSafeString(header.getBytes());

String base64Claim=Base64.encodeBase64URLSafeString(claim.getBytes());

String signature= ShaUtil.Hmacsha256(secret, base64Header + "." +base64Claim);

String jwt= base64Header + "." + base64Claim + "." +signature;

System.out.println(jwt);

}

使用开源库 jjwt 实现 JWT

jjwt 是 java 对 JWT 的封装,下面演示 Java 如何使用 jjwt

添加依赖

有两种方法添加

1. 使用 Maven 仓库(推荐)

io.jsonwebtoken

jjwt

0.7.0

直接导入 Jar 包,注意:由于开源包使用的 Json 解析框架是 Jackson ,因此要同时导入相关 Jar 包,一套 jar 包我已经帮你们准备好了 >>下载Jar包<<

签发 JWT

public staticString createJWT() {

SignatureAlgorithm signatureAlgorithm=SignatureAlgorithm.HS256;

SecretKey secretKey=generalKey();

JwtBuilder builder=Jwts.builder()

.setId(id)//JWT_ID

.setAudience("") //接受者

.setClaims(null) //自定义属性

.setSubject("") //主题

.setIssuer("") //签发者

.setIssuedAt(new Date()) //签发时间

.setNotBefore(new Date()) //失效时间

.setExpiration(long) //过期时间

.signWith(signatureAlgorithm, secretKey); //签名算法以及密匙

returnbuilder.compact();

}

验证 JWT

public static Claims parseJWT(String jwt) throwsException {

SecretKey secretKey=generalKey();returnJwts.parser()

.setSigningKey(secretKey)

.parseClaimsJws(jwt)

.getBody();

}

一般我们把验证操作作为中间件或者拦截器就行了

Java 服务端Demo没有用流行框架,基础的 JSP + Servlet + JavaBean

下面贴出主要的类:

* TokenMgr.java

验证和签发的 JWT 的操作类

public classTokenMgr {public staticSecretKey generalKey() {byte[] encodedKey =Base64.decode(Constant.JWT_SECERT);

SecretKey key= new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");returnkey;

}/*** 签发JWT

*@paramid

*@paramsubject

*@paramttlMillis

*@return*@throwsException*/

public static String createJWT(String id, String subject, longttlMillis) {

SignatureAlgorithm signatureAlgorithm=SignatureAlgorithm.HS256;long nowMillis =System.currentTimeMillis();

Date now= newDate(nowMillis);

SecretKey secretKey=generalKey();

JwtBuilder builder=Jwts.builder()

.setId(id)

.setSubject(subject)

.setIssuedAt(now)

.signWith(signatureAlgorithm, secretKey);if (ttlMillis >= 0) {long expMillis = nowMillis +ttlMillis;

Date expDate= newDate(expMillis);

builder.setExpiration(expDate);

}returnbuilder.compact();

}/*** 验证JWT

*@paramjwtStr

*@return

*/

public staticCheckResult validateJWT(String jwtStr) {

CheckResult checkResult= newCheckResult();

Claims claims= null;try{

claims=parseJWT(jwtStr);

checkResult.setSuccess(true);

checkResult.setClaims(claims);

}catch(ExpiredJwtException e) {

checkResult.setErrCode(Constant.JWT_ERRCODE_EXPIRE);

checkResult.setSuccess(false);

}catch(SignatureException e) {

checkResult.setErrCode(Constant.JWT_ERRCODE_FAIL);

checkResult.setSuccess(false);

}catch(Exception e) {

checkResult.setErrCode(Constant.JWT_ERRCODE_FAIL);

checkResult.setSuccess(false);

}returncheckResult;

}/***

* 解析JWT字符串

*@paramjwt

*@return*@throwsException*/

public static Claims parseJWT(String jwt) throwsException {

SecretKey secretKey=generalKey();returnJwts.parser()

.setSigningKey(secretKey)

.parseClaimsJws(jwt)

.getBody();

}/*** 生成subject信息

*@paramuser

*@return

*/

public staticString generalSubject(SubjectModel sub){returnGsonUtil.objectToJsonStr(sub);

}

}

SignFilter.java

验证 Token 的过滤器

PS:Token 可以放在 URL、Cookie、请求头Auth或者body中以一种特定格式解析,这里只是规定把 Token 放在 URL 或者表单示例。

CheckResult:验证结果模型,包含成功Claim、通过状态、失败码。由于验证结果基本三种状态:通过,不通过,通过但过期,因此多出失败码来区分开。其实验证结果状态还有很多,据需求决定。

public class SignFilter implementsFilter {

@Overridepublic voiddestroy() {

}

@Overridepublic voiddoFilter(ServletRequest arg0, ServletResponse arg1,

FilterChain arg2)throwsIOException, ServletException {

HttpServletRequest httpServletRequest=(HttpServletRequest) arg0;

HttpServletResponse httpServletResponse=(HttpServletResponse) arg1;

String tokenStr= httpServletRequest.getParameter("token");if (tokenStr == null || tokenStr.equals("")) {

PrintWriter printWriter=httpServletResponse.getWriter();

printWriter.print(ResponseMgr.err());

printWriter.flush();

printWriter.close();return;

}//验证JWT的签名,返回CheckResult对象

CheckResult checkResult =TokenMgr.validateJWT(tokenStr);if(checkResult.isSuccess()) {

Claims claims=checkResult.getClaims();

SubjectModel model= GsonUtil.jsonStrToObject(claims.getSubject(), SubjectModel.class);

httpServletRequest.setAttribute("tokensub", model);

httpServletRequest.getRequestDispatcher("/success.jsp").forward(httpServletRequest, httpServletResponse);

}else{switch(checkResult.getErrCode()) {//签名过期,返回过期提示码

caseConstant.JWT_ERRCODE_EXPIRE:

PrintWriter printWriter=httpServletResponse.getWriter();

printWriter.print(ResponseMgr.loginExpire());

printWriter.flush();

printWriter.close();break;//签名验证不通过

caseConstant.JWT_ERRCODE_FAIL:

PrintWriter printWriter2=httpServletResponse.getWriter();

printWriter2.print(ResponseMgr.noAuth());

printWriter2.flush();

printWriter2.close();break;default:break;

}

}

}

@Overridepublic void init(FilterConfig arg0) throwsServletException {

}

}

web.xml

LoginServlet

com.paul.sertest.servlet.LoginServlet

LoginServlet

/api/login

CorsFilter

com.paul.sertest.filter.CorsFilter

CorsFilter

/api/*

SignFilter

com.paul.sertest.filter.SignFilter

encoding

utf-8

SignFilter

/api/check/*

/api/bussin/*

其中 CorsFilter 对 API 接口的响应头添加 Content-Type : text/json 以及编码格式等等,所以它对 /SERTEXT/api/* 的地址进行拦截,避免影响请求静态页面;Filter 的执行顺序是根据解析 web.xml 文件中节点的 先后顺序 决定的,需要把 CorsFilter 放首位,因为假如某处抛出异常会导致返回数据乱码。

看看 jjwt 的源码

PS:源码从 GIT 仓库 Clone 下来就行了

从使用示例代码看得出 jjwt 使用了 Builder模式 以及灵活多变的 链式调用 ,builder() 出 JwtBuilder 对象。

在进行一系列链式 set 方法后执行 compact() 方法返回我们想要的结果,来看看它到底是怎么签名的:

DefaultJwtBuilder.java

@OverridepublicString compact() {

...

...//进行参数判断

Header header=ensureHeader();

Key key= this.key;if (key == null && !Objects.isEmpty(keyBytes)) {

key= newSecretKeySpec(keyBytes, algorithm.getJcaName());

}

JwsHeader jwsHeader;if (header instanceofJwsHeader) {

jwsHeader=(JwsHeader)header;

}else{

jwsHeader= newDefaultJwsHeader(header);

}//构造密匙对象

if (key != null) {

jwsHeader.setAlgorithm(algorithm.getValue());

}else{//no signature - plaintext JWT:

jwsHeader.setAlgorithm(SignatureAlgorithm.NONE.getValue());

}if (compressionCodec != null) {

jwsHeader.setCompressionAlgorithm(compressionCodec.getAlgorithmName());

}

String base64UrlEncodedHeader= base64UrlEncode(jwsHeader, "Unable to serialize header to json.");

String base64UrlEncodedBody;if (compressionCodec != null) {byte[] bytes;try{

bytes= this.payload != null ?payload.getBytes(Strings.UTF_8) : toJson(claims);

}catch(JsonProcessingException e) {throw new IllegalArgumentException("Unable to serialize claims object to json.");

}

base64UrlEncodedBody=TextCodec.BASE64URL.encode(compressionCodec.compress(bytes));

}else{

base64UrlEncodedBody= this.payload != null ?TextCodec.BASE64URL.encode(this.payload) :

base64UrlEncode(claims,"Unable to serialize claims object to json.");

}//这里已经组成了实现 Header 和 Playload 部分

String jwt = base64UrlEncodedHeader + JwtParser.SEPARATOR_CHAR +base64UrlEncodedBody;if (key != null) { //jwt must be signed:

JwtSigner signer=createSigner(algorithm, key);

String base64UrlSignature=signer.sign(jwt);

jwt+= JwtParser.SEPARATOR_CHAR +base64UrlSignature;

}else{//no signature (plaintext), but must terminate w/ a period, see// https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-6.1

jwt +=JwtParser.SEPARATOR_CHAR;

}returnjwt;

}

首先会进行 payload 以及 key 的判断,原则是 payload 与 自定义 claims 不能为 null 以及不能同时赋值参数,key 和 keyBytes 不能同时存在;然后通过 ensureHeader() 获取 Header 对象。

protectedHeader ensureHeader() {if (this.header == null) {this.header = newDefaultHeader();

}return this.header;

}

如果没有设置自定义 Header ,则实例一个默认 Header 对象 —- DefaultHeader,其中 Header 接口是个继承 Map 接口的集合,符合了 header 部分键值对形式。

然后 Header 实例会被“转换”为 JwsHeader 实例,其中 JwsHeader 接口继承 Header 接口,多定义了“签名”和“密匙ID”这个两个属性。

JwsHeader jwsHeader;if (header instanceofJwsHeader) {

jwsHeader=(JwsHeader)header;

}else{

jwsHeader= newDefaultJwsHeader(header);

}

最终通过 base64UrlEncode() 方法的到 base64url 编码后的 header 字符串。

protectedString base64UrlEncode(Object o, String errMsg) {byte[] bytes;try{//使用 Jackson 框架将对象序列化

bytes =toJson(o);

}catch(JsonProcessingException e) {throw newIllegalStateException(errMsg, e);

}//将 byte 数组转化为 base64url 编码的 byte 数组

returnTextCodec.BASE64URL.encode(bytes);

}

接着同理将 payload 或 claims base64url 编码组成 playload 部分。

最后就是签名部分了。createSigner(algorithm, key) 方法实例一个 DefaultJwtSigner 对象,该对象进行统一的签名和编码操作,它的构造函数会传入签名算法枚举 SignatureAlgorithm 对象,定义所有算法的名字、描述、组类等等。

public enumSignatureAlgorithm {/**JWA name for {@codeNo digital signature or MAC performed}*/NONE("none", "No digital signature or MAC performed", "None", null, false),/**JWA algorithm name for {@codeHMAC using SHA-256}*/HS256("HS256", "HMAC using SHA-256", "HMAC", "HmacSHA256", true),

........

......../*** JWA algorithm name for {@codeRSASSA-PSS using SHA-512 and MGF1 with SHA-512}. This is not a JDK standard

* algorithm and requires that a JCA provider like BouncyCastle be in the classpath. BouncyCastle will be used

* automatically if found in the runtime classpath.*/PS512("PS512", "RSASSA-PSS using SHA-512 and MGF1 with SHA-512", "RSA", "SHA512withRSAandMGF1", false);

}

那 DefaultJwtSigner 怎么分别实现具体算法呢?

public class DefaultJwtSigner implementsJwtSigner {private static final Charset US_ASCII = Charset.forName("US-ASCII");private finalSigner signer;publicDefaultJwtSigner(SignatureAlgorithm alg, Key key) {this(DefaultSignerFactory.INSTANCE, alg, key);

}publicDefaultJwtSigner(SignerFactory factory, SignatureAlgorithm alg, Key key) {

Assert.notNull(factory,"SignerFactory argument cannot be null.");this.signer =factory.createSigner(alg, key);

}

@OverridepublicString sign(String jwtWithoutSignature) {byte[] bytesToSign =jwtWithoutSignature.getBytes(US_ASCII);byte[] signature =signer.sign(bytesToSign);returnTextCodec.BASE64URL.encode(signature);

}

}

构造函数 DefaultJwtSigner 中有个单例签名工厂 —- DefaultSignerFactory,让我们来看看这个工厂都做了些什么

public class DefaultSignerFactory implementsSignerFactory {public static final SignerFactory INSTANCE = newDefaultSignerFactory();

@OverridepublicSigner createSigner(SignatureAlgorithm alg, Key key) {

Assert.notNull(alg,"SignatureAlgorithm cannot be null.");

Assert.notNull(key,"Signing Key cannot be null.");switch(alg) {caseHS256:caseHS384:caseHS512:return newMacSigner(alg, key);caseRS256:caseRS384:caseRS512:casePS256:casePS384:casePS512:return newRsaSigner(alg, key);caseES256:caseES384:caseES512:return newEllipticCurveSigner(alg, key);default:throw new IllegalArgumentException("The '" + alg.name() + "' algorithm cannot be used for signing.");

}

}

}

原来在工厂中根据不同算法实例化不同的签名对象 Signer,看来具体签名算法就是放在 Signer 接口的实现类了,由于我们上面使用 HMacSHA256 算法,关心 MacSigner 类就好了,让我们看看它是怎么做的:

public class MacSigner extends MacProvider implementsSigner {

... ...

@Overridepublic byte[] sign(byte[] data) {

Mac mac=getMacInstance();returnmac.doFinal(data);

}protected Mac getMacInstance() throwsSignatureException {try{returndoGetMacInstance();

}catch(NoSuchAlgorithmException e) {

String msg= "Unable to obtain JCA MAC algorithm '" + alg.getJcaName() + "': " +e.getMessage();throw newSignatureException(msg, e);

}catch(InvalidKeyException e) {

String msg= "The specified signing key is not a valid " + alg.name() + " key: " +e.getMessage();throw newSignatureException(msg, e);

}

}protected Mac doGetMacInstance() throwsNoSuchAlgorithmException, InvalidKeyException {

Mac mac=Mac.getInstance(alg.getJcaName());

mac.init(key);returnmac;

}

}

是不是很熟悉?也是通过 Mac.getInstance(ALG_NAME) 获取 Mac 对象后调用其 mac.doFinal(data) 获取签名后的 byte 数组,最后转字符串啦,签名代码到这里基本结束了。

上面提到的类如图:

JJWT 的验证代码就不讲解了,原理是:取出 header 部分和 playload 部分,根据 header 定义的算法再一次签名,比较这个签名是否和 JWT 自带的签名是否完全相同,验证是否成功。

java jwidnow_Web安全通讯之JWT的Java实现相关推荐

  1. Web安全通讯之JWT的Java实现

    上篇文章中目的是介绍 Json Web Token(以下简称 jwt) ,由于我对 Java 比较熟悉就介绍 Java 服务端 的实现方式,其他语言原理是相同的哈~ PS:如果不清楚JWT,请先看 & ...

  2. 基于javaweb的网上图书商城系统(java+ssm+jsp+mysql+redis+jwt+shiro+rabbitmq+easyui)

    基于javaweb的网上图书商城系统(java+ssm+jsp+mysql+redis+jwt+shiro+rabbitmq+easyui) 运行环境 Java≥8.MySQL≥5.7.Tomcat≥ ...

  3. java 接口 token_Java中使用JWT生成Token进行接口鉴权实现方法

    先介绍下利用JWT进行鉴权的思路: 1.用户发起登录请求. 2.服务端创建一个加密后的JWT信息,作为Token返回. 3.在后续请求中JWT信息作为请求头,发给服务端. 4.服务端拿到JWT之后进行 ...

  4. JWT|概述|JWT结构|JWT在java中的使用|JWT工具类的封装|JWT在springboot中的使用|JWT与拦截器的配合

    JWT ! 前记: 官网:https://jwt.io/ jwt有人说是用计算力换空间(相对于session) 小程序后台要求全部用springboot实现..登录状态的管理:本来想用自己随便生成UU ...

  5. 基于java的串口通讯(附带实例+说明文档+测试工具)

    在步入正题前,发个牢骚. 前天总公司的一个技术经理下达一个任务,实现java程序与串口的通信.半天做出来了(见附件),经理看了,在电话里说是直接从网上下载的,颇为不屑. 说实话,当时真TM火大!虽然现 ...

  6. java与modbusTcp通讯

    java与modbusTcp通讯用途 工厂中通讯模块大多数都用modbus或者opcua与plc建立通讯,实现工厂设备的控制及数据采集等 modbous协议通讯byte数据解析含义 读取线圈的byte ...

  7. java netty 即时通讯_netty百万并发-javanetty如何处理1m连接

    在某些极端的情况下,这些语言中的每一种都不完全相同,所以我使用的Java语言,有100万个并发连接,这有点奇怪,更有希望.nginx 并发 netty并发. 这一次使用了方便的netty NIO框架( ...

  8. java SpringMVC mybatis 多数据源 代码生成器 SSM java redis shiro ehcache

    获取[下载地址]   QQ: 313596790 A 调用摄像头拍照,自定义裁剪编辑头像,头像图片色度调节 B 集成代码生成器 [正反双向](单表.主表.明细表.树形表,快速开发利器)+快速表单构建器 ...

  9. java future用法_纯干货:Java学习过程中的21个知识点和技术点

    我们在Java学习过程中要学会抓重点,善于总结,Java学习过程中常见的21个知识点和技术点你知道吗?下面和千锋广州小编一起来看看吧! 1. JVM相关 对于刚刚接触Java的人来说,JVM相关的知识 ...

最新文章

  1. [JavaScript] 日期时间戳的使用与计算
  2. mysql正则提取字符串_mysql字符串查找截取与正则表达式的联合应用
  3. MyBatis-学习笔记01【01.Mybatis课程介绍及环境搭建】
  4. HDU - 4686 Arc of Dream(矩阵快速幂,水题)
  5. 精彩回放 | 玩转 VS Code 物联网开发
  6. MySQL检索数据(过滤+通配符+正则表达式)
  7. php临时文件删除,php删除临时文件的代码示例
  8. Tomcat学习总结(13)—— Tomcat常用参数配置说明
  9. 线性回归(五)---弹性网络回归
  10. 人人都能有数字替身:量子动力FACEGOOD发布AI交互实时数字人
  11. 湖南附中模拟day1 瞭望塔
  12. 感悟:number 2
  13. 整人小程序【转自CSDN】
  14. 计算机文件夹操作有哪些,电脑操作常用的快捷键有哪些
  15. python中if满足条件后退出程序_Python的流程控制:if条件判断
  16. unity 物理碰撞
  17. logback配置 (分文件夹、可配路径)_hanCSDN_20180906
  18. 数据存储设备的发展历史
  19. ai 计算机视觉_人工智能中的计算机视觉
  20. java foreach标签_Java中Velocity foreach循环标签详解

热门文章

  1. 《去中国的小船》后感
  2. 达人评测i7 13700K 和R9 7900X选哪个好
  3. 好用的前端flex布局,通用的flex布局CSS代码
  4. 写文案的技巧如何写出高质量文案
  5. Linux系统安装VNC
  6. SharePoint 是什么?
  7. 全网最具深度的三次握手、四次挥手讲解,知乎上已获万赞
  8. p40pro鸿蒙系统好用吗,华为p40pro可以升级鸿蒙系统吗?
  9. 基于Simulink、Simscape、S-Function联合的机械臂仿真
  10. 三、 网际互联的网络层——网际控制【计算机网络全景梳理系列】