Spring boot JWT koltin 模块实现

前言

其实今天这篇博客就是纯水,至于水的原因也是因为自己对自己有承诺,从开始写博客开始,每月都要有至少有一篇博客,从15年开始写,写到现在一共写了 180多篇原创博客,自己觉得还是收获满满。
说了废话,开始正片。

文章目录

  • 前言
  • JWT
  • JWT 组成
    • 头部
    • 载荷
      • 标准中注册的声明
      • 公共的声明
      • 私有的声明
    • 签名
  • 依赖
  • JwtTokenUtils.kt
  • KeyHelper.kt
  • TokenInfo.kt
  • 特别说明,密钥存放位置
  • GitHub 项目Demo

JWT

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
官方网站 https://jwt.io/

JWT 作为当前最流行的无状态服务鉴权令牌实现,其核心原因,一个是因为其本身的标准化格式,二是因为有很强的开源组织支撑,多种的主流开发语言支撑。

可以通过该网站查询JWT 在各类开发语言中的第三方支撑库。
https://jwt.io/#libraries-io

有很多,仅Java一门语言,就有六种可供选择的三方支撑库。

JWT 组成

JWT格式

eyJhbGciOiEIUzI1NiIsInR52CI6IkpXVCJ9.eyJzdWIiOiIxMjM0NER3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHG8DcEfxjoYZgeFONFh7HgQ
  • 头部(header)
  • 载荷(payload)
  • 签名(signature)

格式为 header.payload.signature,每部分通过.在JWT中进行分割。

头部

jwt的头部承载两部分信息:

  1. 声明类型,这里是jwt
  2. 声明加密的算法,默认算法HMAC SHA256,可以使用RSA等其他支持算法
{
  'typ': 'JWT',
  'alg': 'rs256'
}

将以上json结构通过Base64编码后,即可获得第一段头部编码。

载荷

可以由三部分组成

  1. 标准中注册的声明
  2. 公共的声明
  3. 私有的声明

三部分不是必须的,按照需求自行进行安排,假设生成如下载荷

{
  "sub": "1234567890",
  "name": "testUser",
  "role": 'admin'
}

通过对该部分的内容进行头部约定的加密方式加密后,再将加密的结果通过Base64编码即可得到JWT中第二段内容。

标准中注册的声明

建议但不强制使用。

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

公共的声明

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密

私有的声明

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

签名

签名的算法主要是确保数据不会被篡改。它主要是对前面所讲的两个部分进行签名,通过 JWT 头定义的算法生成哈希。哈希签名的过程如下:

  1. 指定密码,密码保存在服务器中,不能向客户端公开;
  2. 使用 JWT 头指定的算法进行签名,进行签名前需要对 JWT 头和有效载荷进行 Base64URL 编码,JWT 头和邮箱载荷编码后的结果之间需要用 . 来连接。

此部分不详解,无非就是签名的算法过程,有兴趣的可以去实现一下这个签名过程。

依赖

采用gradle 做项目的脚手架工具,依赖使用如下

compile 'io.jsonwebtoken:jjwt:0.7.0'

JwtTokenUtils.kt

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
        }

    }
}

KeyHelper.kt

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.kt

自定义的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 目录下,如需放置到其他地方请自行调整。

GitHub 项目Demo

https://github.com/ChineseLincoln/unreal-cloud/tree/master/unreal-jwt

已经封装成微服务可用的组件。

你可能感兴趣的:(kotlin,java)