java jwidnow_Web安全通讯之JWT的Java实现
上篇文章中目的是介绍 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实现相关推荐
- Web安全通讯之JWT的Java实现
上篇文章中目的是介绍 Json Web Token(以下简称 jwt) ,由于我对 Java 比较熟悉就介绍 Java 服务端 的实现方式,其他语言原理是相同的哈~ PS:如果不清楚JWT,请先看 & ...
- 基于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≥ ...
- java 接口 token_Java中使用JWT生成Token进行接口鉴权实现方法
先介绍下利用JWT进行鉴权的思路: 1.用户发起登录请求. 2.服务端创建一个加密后的JWT信息,作为Token返回. 3.在后续请求中JWT信息作为请求头,发给服务端. 4.服务端拿到JWT之后进行 ...
- JWT|概述|JWT结构|JWT在java中的使用|JWT工具类的封装|JWT在springboot中的使用|JWT与拦截器的配合
JWT ! 前记: 官网:https://jwt.io/ jwt有人说是用计算力换空间(相对于session) 小程序后台要求全部用springboot实现..登录状态的管理:本来想用自己随便生成UU ...
- 基于java的串口通讯(附带实例+说明文档+测试工具)
在步入正题前,发个牢骚. 前天总公司的一个技术经理下达一个任务,实现java程序与串口的通信.半天做出来了(见附件),经理看了,在电话里说是直接从网上下载的,颇为不屑. 说实话,当时真TM火大!虽然现 ...
- java与modbusTcp通讯
java与modbusTcp通讯用途 工厂中通讯模块大多数都用modbus或者opcua与plc建立通讯,实现工厂设备的控制及数据采集等 modbous协议通讯byte数据解析含义 读取线圈的byte ...
- java netty 即时通讯_netty百万并发-javanetty如何处理1m连接
在某些极端的情况下,这些语言中的每一种都不完全相同,所以我使用的Java语言,有100万个并发连接,这有点奇怪,更有希望.nginx 并发 netty并发. 这一次使用了方便的netty NIO框架( ...
- java SpringMVC mybatis 多数据源 代码生成器 SSM java redis shiro ehcache
获取[下载地址] QQ: 313596790 A 调用摄像头拍照,自定义裁剪编辑头像,头像图片色度调节 B 集成代码生成器 [正反双向](单表.主表.明细表.树形表,快速开发利器)+快速表单构建器 ...
- java future用法_纯干货:Java学习过程中的21个知识点和技术点
我们在Java学习过程中要学会抓重点,善于总结,Java学习过程中常见的21个知识点和技术点你知道吗?下面和千锋广州小编一起来看看吧! 1. JVM相关 对于刚刚接触Java的人来说,JVM相关的知识 ...
最新文章
- [JavaScript] 日期时间戳的使用与计算
- mysql正则提取字符串_mysql字符串查找截取与正则表达式的联合应用
- MyBatis-学习笔记01【01.Mybatis课程介绍及环境搭建】
- HDU - 4686 Arc of Dream(矩阵快速幂,水题)
- 精彩回放 | 玩转 VS Code 物联网开发
- MySQL检索数据(过滤+通配符+正则表达式)
- php临时文件删除,php删除临时文件的代码示例
- Tomcat学习总结(13)—— Tomcat常用参数配置说明
- 线性回归(五)---弹性网络回归
- 人人都能有数字替身:量子动力FACEGOOD发布AI交互实时数字人
- 湖南附中模拟day1 瞭望塔
- 感悟:number 2
- 整人小程序【转自CSDN】
- 计算机文件夹操作有哪些,电脑操作常用的快捷键有哪些
- python中if满足条件后退出程序_Python的流程控制:if条件判断
- unity 物理碰撞
- logback配置 (分文件夹、可配路径)_hanCSDN_20180906
- 数据存储设备的发展历史
- ai 计算机视觉_人工智能中的计算机视觉
- java foreach标签_Java中Velocity foreach循环标签详解