其实今天这篇博客就是纯水,至于水的原因也是因为自己对自己有承诺,从开始写博客开始,每月都要有至少有一篇博客,从15年开始写,写到现在一共写了 180多篇原创博客,自己觉得还是收获满满。
说了废话,开始正片。
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
官方网站 https://jwt.io/
JWT 作为当前最流行的无状态服务鉴权令牌实现,其核心原因,一个是因为其本身的标准化格式,二是因为有很强的开源组织支撑,多种的主流开发语言支撑。
可以通过该网站查询JWT 在各类开发语言中的第三方支撑库。
https://jwt.io/#libraries-io
有很多,仅Java一门语言,就有六种可供选择的三方支撑库。
JWT格式
eyJhbGciOiEIUzI1NiIsInR52CI6IkpXVCJ9.eyJzdWIiOiIxMjM0NER3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHG8DcEfxjoYZgeFONFh7HgQ
格式为 header.payload.signature,每部分通过.在JWT中进行分割。
jwt的头部承载两部分信息:
{
'typ': 'JWT',
'alg': 'rs256'
}
将以上json结构通过Base64编码后,即可获得第一段头部编码。
可以由三部分组成
三部分不是必须的,按照需求自行进行安排,假设生成如下载荷
{
"sub": "1234567890",
"name": "testUser",
"role": 'admin'
}
通过对该部分的内容进行头部约定的加密方式加密后,再将加密的结果通过Base64编码即可得到JWT中第二段内容。
建议但不强制使用。
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
签名的算法主要是确保数据不会被篡改。它主要是对前面所讲的两个部分进行签名,通过 JWT 头定义的算法生成哈希。哈希签名的过程如下:
此部分不详解,无非就是签名的算法过程,有兴趣的可以去实现一下这个签名过程。
采用gradle 做项目的脚手架工具,依赖使用如下
compile 'io.jsonwebtoken:jjwt:0.7.0'
package org.unreal.cloud.jwt
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.SignatureException
import org.joda.time.DateTime
object JwtTokenUtils {
private val expirationTime = 3600
fun getToken(tokenInfo: TokenInfo) : String{
return Jwts.builder()
//设置用户名
.setSubject(tokenInfo.userName)
//设置用户Id
.setId(tokenInfo.id)
//设置用户权限
.setAudience(tokenInfo.role)
//设置过期时间
.setExpiration(DateTime.now().plusSeconds(expirationTime).toDate())
//设置颁发时间
.setIssuedAt(DateTime.now().toDate())
//设置用户账号
.claim("account",tokenInfo.account)
.signWith(SignatureAlgorithm.RS256 , KeyHelper.getPrivateKey(JwtTokenUtils::class.java.getResource("/pri.key").path))
.compact()
}
fun parseToken(compactJws:String):TokenInfo{
try {
val parseClaimsJws = Jwts.parser().setSigningKey( KeyHelper.getPublicKey(JwtTokenUtils::class.java.getResource("/pub.key").path)).parseClaimsJws(compactJws)
val body = parseClaimsJws.body
return TokenInfo(body.id, body["account"].toString(),body.audience,body.subject)
}catch (e :SignatureException){
throw e
}
}
}
package org.unreal.cloud.jwt
import java.io.*
import java.security.*
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
/**
* Created by ace on 2017/9/10.
*/
object KeyHelper {
/**
* 获取公钥
* @param filename
* @return
* @throws Exception
*/
@Throws(Exception::class)
fun getPublicKey(filename: String): PublicKey {
val f = File(filename)
val fis = FileInputStream(f)
val dis = DataInputStream(fis)
val keyBytes = ByteArray(f.length().toInt())
dis.readFully(keyBytes)
dis.close()
val spec = X509EncodedKeySpec(keyBytes)
val kf = KeyFactory.getInstance("RSA")
return kf.generatePublic(spec)
}
/**
* 获取密钥
* @param filename
* @return
* @throws Exception
*/
@Throws(Exception::class)
fun getPrivateKey(filename: String): PrivateKey {
val f = File(filename)
val fis = FileInputStream(f)
val dis = DataInputStream(fis)
val keyBytes = ByteArray(f.length().toInt())
dis.readFully(keyBytes)
dis.close()
val spec = PKCS8EncodedKeySpec(keyBytes)
val kf = KeyFactory.getInstance("RSA")
return kf.generatePrivate(spec)
}
/**
* 生存rsa公钥和密钥
* @param publicKeyFilename
* @param privateKeyFilename
* @param password
* @throws IOException
* @throws NoSuchAlgorithmException
*/
@Throws(IOException::class, NoSuchAlgorithmException::class)
fun generateKey(publicKeyFilename: String, privateKeyFilename: String, password: String) {
val keyPairGenerator = KeyPairGenerator.getInstance("RSA")
val secureRandom = SecureRandom(password.toByteArray())
keyPairGenerator.initialize(1024, secureRandom)
val keyPair = keyPairGenerator.genKeyPair()
val publicKeyBytes = keyPair.public.encoded
var fos = FileOutputStream(publicKeyFilename)
fos.write(publicKeyBytes)
fos.close()
val privateKeyBytes = keyPair.private.encoded
fos = FileOutputStream(privateKeyFilename)
fos.write(privateKeyBytes)
fos.close()
}
//生成RSA 密钥对
fun main(args: Array<String>) {
generateKey("./pub.key", "./pri.key", "1*&623!f")
}
}
自定义的TokenInfo类,用于存放业务传递的Token信息,以及解密后的数据。
package org.unreal.cloud.jwt
data class TokenInfo(val id:String , val account:String , val role :String , val userName :String)
项目中使用的密钥对 需要放置到 固定位置。样例代码中 JwtTokenUtils::class.java.getResource("/pri.key").path
此处是将密钥放置到了 Resource 目录下,如需放置到其他地方请自行调整。
https://github.com/ChineseLincoln/unreal-cloud/tree/master/unreal-jwt
已经封装成微服务可用的组件。