接口认证是所有系统的基础功能,也是最重要的安全手段。分布式环境下认证更是重中之重。简单来讲,常用的认证方式有2种,一种是token方式,即使用类似于UUID(或者雪花算法生产字符串),另一个就是JWT的方式。因为JWT的方式是专门为分布式场景设计的,所以有人也把JWT看做是分布式系统最合适的认证方式,没有之一。
什么是JWTJsonwebtoken(JWT),是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
简单来说就是JWT(JsonWebToken)是实现token技术的一种解决方案
为什么使用JWTtoken验证和session认证的区别
1传统的session认证
http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。2session缺点
基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户Session方式存储用户id的最大弊病在于要占用大量服务器内存,对于较大型应用而言可能还要保存许多的状态。
3基于session认证暴露的问题
Session需要在服务器保存,暂用资源
扩展性session认证保存在内存中,无法扩展到其他机器中
CSRF基于cookie来进行用户识别的,cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
基于token的鉴权机制基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。除了用户id之外,还可以存储其他的和用户相关的信息,例如用户角色,用户性别等。
请求流程
用户使用用户名密码来请求服务器
服务器进行验证用户的信息
服务器通过验证发送给用户一个token
客户端存储token,并在每次请求时附送上这个token值
服务端验证token值,并返回数据
JWT的结构一个JWT是下面的结构
加密后jwt信息如下所示,是由.分割的三部分组成,分别为Header、Payload、Signature
JWT的组成
Head-主要包含两个部分,alg指加密类型,可选值为HS256、RSA等等,typ=JWT为固定值,表示token的类型
Header:{"alg":"HS256","typ":"JWT"}Payload-Payload又被称为Claims包含您想要签署的任何信息
Claims:{"sub":"1234567890","name":"JohnDoe","admin":true}Payload-Payload又被称为Claims包含您想要签署的任何信息JWTPayload的组成Payload通常由三个部分组成,分别是RegisteredClaims;PublicClaims;PrivateClaims;每个声明,都有各自的字段。RegisteredClaimsiss【issuer】发布者的url地址sub【subject】该JWT所面向的用户,用于处理特定应用,不是常用的字段aud【audience】接受者的url地址exp【expiration】该jwt销毁的时间;unix时间戳nbf【notbefore】该jwt的使用时间不能早于该时间;unix时间戳iat【issuedat】该jwt的发布时间;unix时间戳jti【JWTID】该jwt的唯一ID编号
Signature对则为对Header、Payload的签名
Signature:base64UrlEncode(Header)+"."+base64UrlEncode(Claims)
头部、声明、签名用.号连在一起就得到了我们要的JWT也就是夏明这种类型的字符串
其实这些事一行的,我只是让看的更直白点将其割开了。JAVA实现
JAVA中使用JWT
Maven中引入方式
/groupIdartifactIdjjwt//version/depency
需要特别注意:JWT依赖于Jackson,需要在程序中加入Jackson的jar包且版本大于2.x
签发JWT
publicstaticStringcreateJWT(){SignatureAlgorithmsignatureAlgorithm=;SecretKeysecretKey=generalKey();JwtBuilderbuilder=().setId(id)//JWT_("")//接受者.setClaims(null)//自定义属性.setSubject("")//主题.setIssuer("")//签发者.setIssuedAt(newDate())//签发时间.setNotBefore(newDate())//失效时间.setExpiration(long)//过期时间.signWith(signatureAlgorithm,secretKey);//签名算法以及密匙();}验证JWT
publicstaticClaimsparseJWT(Stringjwt)throwsException{SecretKeysecretKey=generalKey();().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();}完整示例
;;;;;;;;;;;publicclassJwtUtil{/***由字符串生成加密key**@return*/publicSecretKeygeneralKey(){StringstringKey=_SECRET;//本地的密码解码byte[]encodedKey=(stringKey);//根据给定的字节数组使用AES加密算法构造一个密钥SecretKeykey=newSecretKeySpec(encodedKey,0,,"AES");returnkey;}/***创建jwt*@paramid*@paramissuer*@paramsubject*@paramttlMillis*@return*@throwsException*/publicStringcreateJWT(Stringid,Stringissuer,Stringsubject,longttlMillis)throwsException{//指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。SignatureAlgorithmsignatureAlgorithm=;//生成JWT的时间longnowMillis=();Datenow=newDate(nowMillis);//创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)MapString,Objectclaims=newHashMap();("uid","123456");("user_name","admin");("nick_name","X-rapido");//生成签名的时候使用的秘钥secret,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。//一旦客户端得知这个secret,那就意味着客户端是可以自我签发jwt了。SecretKeykey=generalKey();//下面就是在为payload添加各种标准声明和私有声明了JwtBuilderbuilder=()//这里其实就是new一个JwtBuilder,设置jwt的(claims)//如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的.setId(id)//设置jti(JWTID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。.setIssuedAt(now)//iat:jwt的签发时间.setIssuer(issuer)//issuer:jwt签发人.setSubject(subject)//sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。.signWith(signatureAlgorithm,key);//设置签名使用的签名算法和签名使用的秘钥//设置过期时间if(ttlMillis=0){longexpMillis=nowMillis+ttlMillis;Dateexp=newDate(expMillis);(exp);}();}/***解密jwt**@paramjwt*@return*@throwsException*/publicClaimsparseJWT(Stringjwt)throwsException{SecretKeykey=generalKey();//签名秘钥,和生成的签名的秘钥一模一样Claimsclaims=()//得到(key)//设置签名的秘钥.parseClaimsJws(jwt).getBody();//设置需要解析的jwtreturnclaims;}publicstaticvoidmain(String[]args){Useruser=newUser("tingfeng","bulingbuling","1056856191");Stringsubject=newGson().toJson(user);try{JwtUtilutil=newJwtUtil();Stringjwt=(_ID,"Anson",subject,_TTL);("JWT:"+jwt);("\n解密\n");Claimsc=(jwt);(());(());(());(());(("uid",));}catch(Exceptione){();}}};publicclassConstant{publicstaticfinalStringJWT_ID=().toString();/***加密密文*/publicstaticfinalStringJWT_SECRET="woyebuzhidaoxiediansha";publicstaticfinalintJWT_TTL=60*60*1000;//millisecond}输出示例
JWT:cIixcIndlY2hhdFwiOlwiYnVsaW5nYnVsaW5nXCIsXCJxcVwiOlwiMTA1Njg1NjE5MVwifSIsInVzZXJfbmFtZSI6ImFkbWluIiwibmlja19uYW1lIjoiWC1yYXBpZG8iLCJpc3MiOiJBbnNvbiIsImV4cCI6MTUyMjMxNDEyNCwiaWF0IjoxNTIyMzEwNTI0_W4MZLj9uBHSYalG6vmYwdpdTXg0otdwTmU4U解密a4d9204f-db37-4adf-8145-bdcf00331ff6ThuMar2916:02:04CST2018{"nickname":"tingfeng","wechat":"bulingbuling","qq":"1056856191"}Anson123456总结优点
因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
它不需要在服务端保存会话信息,所以它易于应用的扩展
安全相关
不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
保护好secret私钥,该私钥非常重要。
如果可以,请使用https协议
以上为全部内容。