iOS之JWT详解

2,617 阅读3分钟

释义

JSON Web Token(JWT)是一个开放标准,利用JSON对象安全地传输信息。大部分的语言或平台都可以按照其规定的标准去实现,本文以iOS为例。

而实际上,JWT就是一串字符串,不过包含了信息的token字符串。使用JWT就是为了安全,关于加密部分不清楚的可以查看这里

组成

它由三部分组成:Header(头部)、Payload(载荷)、Signature(签名)

Header

json内容:

{  
   "typ": "JWT",  
   "alg": "HS256"  
}
  • typ:类型,一般固定写JWT
  • alg:签名部分使用的算法,通常是2种,接收者可以解析此条信息来
    • HS256:对称加密算法,双方使用同一个密钥加密解密
    • RS256:非对称加密算法,私钥加密,公钥解密

{ "typ": "JWT", "alg": "HS256" }进行Base64编码得到一串字符串:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

这串字符串就是Header,不同语言不同的平台得到的字符串可能不一样,但是解码之后应该是一样的。

Payload

这部分就是主体信息,是个JSON对象,包含以下内容:

{
    "iss": "RC",
    "iat": 1441593502,
    "exp": 1441594722,
    "aud": "Server",
    "sub": "member",
    "info_A": "这是需要传递的信息A",
    "info_B": "这是需要传递的信息B",
    ...
}

以下7个字段是JWT的标准所定义的。

  • iss(issuer):签发者
  • iat(issued at):在什么时候签发的
  • exp(expires):什么时候过期,一般这里是一个时间戳
  • aud:接收者
  • sub:该JWT所面向的用户
  • nbf:生效时间
  • jti:编号

在此主体的JSON对象中,内容可以自行约定或添加,因为会暴露,所以不应该在载荷里面添加入任何敏感的内容,或者将内容加密后再添加。

将主体的JSON对象进行Base64编码也得到一串字符串:

eyJlbmQiOiIxNjc3ODYyMzA5Iiwic3RhcnQiOiIxNjc3Nzc1OTA5IiwidXNlcmUiOiIxMDExIiwiaWF0IjoiMTY3Nzc3NTkwOSJ9

这串字符串就是Payload

Signature

这部分是JWT的签名,也是保证整个JWT在传递过程中不会被篡改。

将前面得到的Header和Payload用点.拼接在一起(头部在前),即: Header.Payload

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbmQiOiIxNjc3ODYyMzA5Iiwic3RhcnQiOiIxNjc3Nzc1OTA5IiwidXNlcmUiOiIxMDExIiwiaWF0IjoiMTY3Nzc3NTkwOSJ9

将拼接的字符串使用HS256算法进行加密,当然还需要约定一个密钥,用密钥进行加密,得到字符串:

DI8b_CLI5MRikkp4fLRogSm-VNc0Fc1v2XZ6IgHZMMI

这串字符串就是Signature

此时我们将加密后的字符串用点.拼接在最后(Header.Payload.Signature)得到了完整的JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbmQiOiIxNjc3ODYyMzA5Iiwic3RhcnQiOiIxNjc3Nzc1OTA5IiwidXNlcmUiOiIxMDExIiwiaWF0IjoiMTY3Nzc3NTkwOSJ9.DI8b_CLI5MRikkp4fLRogSm-VNc0Fc1v2XZ6IgHZMMI

实际上 JWT = Header.Payload.Signature

整体:

jwt.png

关于其他加密方法看这里

完整代码

struct JWT {

    /// 生成JWT
    func creatJWT() -> String {
   
        // 头部
        let preHeader = ["typ":"JWT","alg":"HS256"]
        let header = base64String(data: preHeader)
       
        // 载荷
        // 当前时间
        let nowDate = Date().now
        // 当前时间戳
        let nowDateTimeInterval = CLongLong(round(nowDate.timeIntervalSince1970))
        // exp时间戳
        let expDate = nowDate.addingTimeInterval(TimeInterval(3600))
        let expDateTimeInterval = CLongLong(round(endDate.timeIntervalSince1970))
        let prePayload = [
            "iat" : "\(nowDateTimeInterval)",
            "exp" : "\(expDateTimeInterval)",
            "userid" : "001"
        ]
        let payload = base64String(data: prePayload)

        // 签名
        let preSign = header + "." + payload
        // 密钥
        let key = "key=world peace"
        let sign = hMac(string: preSign, key: key)
        return preSign + "." + sign
    }

    

    /// 编码
    private func base64String(data: [String : String]) -> String {
        let data = try? JSONSerialization.data(withJSONObject: data,options: [])
        let str = data!.base64EncodedString().replacingOccurrences(of: "+", with: "-")
            .replacingOccurrences(of: "/", with: "_")
            .replacingOccurrences(of: "=", with: "")
        return str
    }

    /// 256哈希
    private func hMac(string: String, key: String) -> String{
        guard let dataString = string.data(using: .utf8, allowLossyConversion: false),
              let datas = key.data(using: .utf8, allowLossyConversion: false) else {
            return ""
        }
        let ctx = UnsafeMutablePointer<CCHmacContext>.allocate(capacity: 1)
        defer { ctx.deallocate() }
        datas.withUnsafeBytes({ CCHmacInit(ctx, CCHmacAlgorithm(kCCHmacAlgSHA256), $0.baseAddress, size_t(datas.count)) })
        dataString.withUnsafeBytes({ CCHmacUpdate(ctx, $0.baseAddress, size_t(dataString.count)) })
        var hmac = Array<UInt8>(repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
        CCHmacFinal(ctx, &hmac)
        let strHmac = Data(hmac).base64EncodedString().
        replacingOccurrences(of: "+", with: "-").
        replacingOccurrences(of: "/", with: "_").
        replacingOccurrences(of: "=", with: "")
        return strHmac
    }
}