我该如何在Swift中解码JWT(JSON web token)令牌?

39

我有一个像这样的JWT令牌

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwgYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

我该如何解码它,以便我可以获取负载,如下所示

{ "sub": "1234567890", "name": "John Doe", "admin": true }


1
感谢@SiddharthaChikatamalla的提问...让我的谷歌搜索时间少了很多! - PhillipJacobs
8个回答

83

如果您愿意使用库,我建议使用这个https://github.com/auth0/JWTDecode.swift

然后导入该库import JWTDecode并执行。

let jwt = try decode(jwt: token)

由于你不想使用该库,我提取了必要的部分以使其正常工作。
func decode(jwtToken jwt: String) -> [String: Any] {
  let segments = jwt.components(separatedBy: ".")
  return decodeJWTPart(segments[1]) ?? [:]
}

func base64UrlDecode(_ value: String) -> Data? {
  var base64 = value
    .replacingOccurrences(of: "-", with: "+")
    .replacingOccurrences(of: "_", with: "/")

  let length = Double(base64.lengthOfBytes(using: String.Encoding.utf8))
  let requiredLength = 4 * ceil(length / 4.0)
  let paddingLength = requiredLength - length
  if paddingLength > 0 {
    let padding = "".padding(toLength: Int(paddingLength), withPad: "=", startingAt: 0)
    base64 = base64 + padding
  }
  return Data(base64Encoded: base64, options: .ignoreUnknownCharacters)
}

func decodeJWTPart(_ value: String) -> [String: Any]? {
  guard let bodyData = base64UrlDecode(value),
    let json = try? JSONSerialization.jsonObject(with: bodyData, options: []), let payload = json as? [String: Any] else {
      return nil
  }

  return payload
}

这样调用:

decode(jwtToken: TOKEN)

谢谢您的回复。但我不能使用JWT库,我正在一个框架中实现它,我想要一个简单的实现。在Android中,我使用以下代码:String[] split = JWTEncoded.split("\."); String body = getJson(split[1]); Log.d("JWT_DECODED", "Body: " + body);private static String getJson(String strEncoded) throws UnsupportedEncodingException{ byte[] decodedBytes = Base64.decode(strEncoded, Base64.URL_SAFE); return new String(decodedBytes, "UTF-8"); } - Siddhartha Chikatamalla
有没有至少支持Swift 4的其他库? - Bartłomiej Semańczyk
2
不错的代码片段。我从中创建了一个具有静态函数的JWTDecoder结构体,并在需要时添加了throws。无需用不必要的框架堵塞您的项目。 - Au Ris

46

在维克托的代码基础上进行迭代:

  • 使用嵌套函数使代码更加模块化
  • 如果传递了错误的令牌或其他错误,利用异常处理。
  • 简化填充计算并利用填充函数。

希望这对您有用:

func decode(jwtToken jwt: String) throws -> [String: Any] {

    enum DecodeErrors: Error {
        case badToken
        case other
    }

    func base64Decode(_ base64: String) throws -> Data {
        let base64 = base64
            .replacingOccurrences(of: "-", with: "+")
            .replacingOccurrences(of: "_", with: "/")
        let padded = base64.padding(toLength: ((base64.count + 3) / 4) * 4, withPad: "=", startingAt: 0)
        guard let decoded = Data(base64Encoded: padded) else {
            throw DecodeErrors.badToken
        }
        return decoded
    }

    func decodeJWTPart(_ value: String) throws -> [String: Any] {
        let bodyData = try base64Decode(value)
        let json = try JSONSerialization.jsonObject(with: bodyData, options: [])
        guard let payload = json as? [String: Any] else {
            throw DecodeErrors.other
        }
        return payload
    }

    let segments = jwt.components(separatedBy: ".")
    return try decodeJWTPart(segments[1])
}

5
在我看来,这是最佳答案。 - Jon Vogel
4
JWT使用“base64url”编码(而不是普通的“base64”编码),这就是为什么需要替换“-”和“_”。如果你只解析声明部分,很少会在纯文本中看到它们(它们不经常出现),但如果你解析原始数据(比如签名),你会一直看到它们。 - Matt Gallagher
1
好的,感谢反馈,已更新代码以实现该功能。 - possen
1
自2019年至今的完美答案,感谢@possen。 - undefined

10
    func decode(_ token: String) -> [String: AnyObject]? {
    let string = token.components(separatedBy: ".")
    let toDecode = string[1] as String


    var stringtoDecode: String = toDecode.replacingOccurrences(of: "-", with: "+") // 62nd char of encoding
    stringtoDecode = stringtoDecode.replacingOccurrences(of: "_", with: "/") // 63rd char of encoding
    switch (stringtoDecode.utf16.count % 4) {
    case 2: stringtoDecode = "\(stringtoDecode)=="
    case 3: stringtoDecode = "\(stringtoDecode)="
    default: // nothing to do stringtoDecode can stay the same
        print("")
    }
    let dataToDecode = Data(base64Encoded: stringtoDecode, options: [])
    let base64DecodedString = NSString(data: dataToDecode!, encoding: String.Encoding.utf8.rawValue)

    var values: [String: AnyObject]?
    if let string = base64DecodedString {
        if let data = string.data(using: String.Encoding.utf8.rawValue, allowLossyConversion: true) {
            values = try! JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String : AnyObject]
        }
    }
    return values
}

4
通常情况下,如果答案包含代码意图的解释和为何能解决问题且不引入其他问题的原因,那么答案会更加有帮助。 - Tim Diekmann
不想使用外部库,这个方法非常好用,谢谢 :) - XFawkes

8
我有解决办法。
 static func getJwtBodyString(tokenstr: String) -> NSString {

    var segments = tokenstr.components(separatedBy: ".")
    var base64String = segments[1]
    print("\(base64String)")
    let requiredLength = Int(4 * ceil(Float(base64String.characters.count) / 4.0))
    let nbrPaddings = requiredLength - base64String.characters.count
    if nbrPaddings > 0 {
        let padding = String().padding(toLength: nbrPaddings, withPad: "=", startingAt: 0)
        base64String = base64String.appending(padding)
    }
    base64String = base64String.replacingOccurrences(of: "-", with: "+")
    base64String = base64String.replacingOccurrences(of: "_", with: "/")
    let decodedData = Data(base64Encoded: base64String, options: Data.Base64DecodingOptions(rawValue: UInt(0)))
  //  var decodedString : String = String(decodedData : nsdata as Data, encoding: String.Encoding.utf8)

    let base64Decoded: String = String(data: decodedData! as Data, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue))!
    print("\(base64Decoded)")
    return base64String as NSString
}

这对我非常有效。谢谢你。

1
最后的返回语句应该返回“base64Decoded”,而不是“base64String”(似乎是输入字符串?) - Isaac

4
如果您需要使用库,我建议选择知名度高的大公司推出的库。IBM 推出了 Kitura – Swift 后端框架,因此它对于 JWT 的编码和解码实现必须是一流的。
链接: https://github.com/IBM-Swift/Swift-JWT 带过期时间的令牌的简单用法:
import SwiftJWT

struct Token: Decodable {
    let jwtString: String

    func abc() {

        do {
            let newJWT = try JWT<MyJWTClaims>(jwtString: jwtString)
            print(newJWT.claims.exp)
        } catch {
            print("OH NOES")
        }


    }
}

struct MyJWTClaims: Claims {
    let exp: Date
}

3

我重构了 @Viktor Gardart 的代码

使用方法如下

let token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhY2NvdW56IiwianRpIjoiZTA0NGEyMTAtZjVmZi00Yjc2LWI2MzMtNTk0NjYzMWE0MjRjLWQxYTc3bXlpdGE0YnZnaG4yd2YiLCJleHAiOjE2NDk2NDI3MTF9.FO-AQhZ18qogsSbeTUY78EqhfL9xp9iUG3OlpOdxemE"

let jsonWebToken = JSONWebToken(jsonWebToken: token)
let expirationTime = jsonWebToken?.payload.expirationTime


JSONWebToken.swift

import Foundation

struct JSONWebToken {
    let header: JSONWebTokenHeader
    let payload: JSONWebTokenPayload
    let signature: String
}

extension JSONWebToken {

    init?(jsonWebToken: String) {
        let encodedData = { (string: String) -> Data? in
            var encodedString = string.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
        
            switch (encodedString.utf16.count % 4) {
            case 2:     encodedString = "\(encodedString)=="
            case 3:     encodedString = "\(encodedString)="
            default:    break
            }
        
            return Data(base64Encoded: encodedString)
        }

        let components = jsonWebToken.components(separatedBy: ".")
    
        guard components.count == 3, 
            let headerData = encodedData(components[0] as String), 
            let payloadData = encodedData(components[1] as String) else { return nil }

        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601

        do {
            header    = try decoder.decode(JSONWebTokenHeader.self, from: headerData)
            payload   = try decoder.decode(JSONWebTokenPayload.self, from: payloadData)
            signature = components[2] as String
    
        } catch {
            print(error.localizedDescription)
            return nil
        }
    }
}


JSONWebTokenHeader.swift

import Foundation

struct JSONWebTokenHeader {
    let type: String
    let algorithm: String
}

extension JSONWebTokenHeader: Codable {

    private enum Key: String, CodingKey {
        case type      = "typ"
        case algorithm = "alg"
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: Key.self)
    
        do { try container.encode(type,      forKey: .type) }      catch { throw error }
        do { try container.encode(algorithm, forKey: .algorithm) } catch { throw error }
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: Key.self)

        do { type      = try container.decode(String.self, forKey: .type) }      catch { throw error }
        do { algorithm = try container.decode(String.self, forKey: .algorithm) } catch { throw error }
    }
}


JSONWebTokenPayload.swift

import Foundation

struct JSONWebTokenPayload {
    let issuer: String
    let expirationTime: Double
    let jsonWebTokenID: String
}

extension JSONWebTokenPayload: Codable {

    private enum Key: String, CodingKey {
        case issuer         = "iss"
        case expirationTime = "exp"
        case jsonWebTokenID = "jti"
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: Key.self)
    
        do { try container.encode(issuer,         forKey: .issuer) }         catch { throw error }
        do { try container.encode(expirationTime, forKey: .expirationTime) } catch { throw error }
        do { try container.encode(jsonWebTokenID, forKey: .jsonWebTokenID) } catch { throw error }
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: Key.self)

        do { issuer         = try container.decode(String.self, forKey: .issuer) }         catch { throw error }
        do { expirationTime = try container.decode(Double.self, forKey: .expirationTime) } catch { throw error }
        do { jsonWebTokenID = try container.decode(String.self, forKey: .jsonWebTokenID) } catch { throw error }
    }
}

很不幸,大多数我尝试的令牌都无法正常工作(对于etest令牌给定的情况下,它可以正常工作)。 - undefined

3

面向老用户的Objective-C版本:

NSLog(@"credential is %@", credential.identityToken);
NSString * string = [[NSString alloc] initWithData:credential.identityToken encoding:NSUTF8StringEncoding];
NSArray * segments = [string componentsSeparatedByString:@"."];
NSMutableDictionary * JSON = [NSMutableDictionary new];
for (int n = 0; n < segments.count; n++){
    
    NSString * value = segments[n];
    NSString * base64 = [value stringByReplacingOccurrencesOfString:@"-" withString:@"+"];
    base64 = [base64 stringByReplacingOccurrencesOfString:@"_" withString:@"/"];
    NSUInteger length = [base64 lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
    int requiredLength = 4 * ceil((float)length/4.0f);
    int paddingLength = requiredLength - (int)length;
    for (int n = 0; n < paddingLength; n++){
        base64 = [base64 stringByAppendingString:@"="];
    }
    NSData * data = [[NSData alloc] initWithBase64EncodedString:base64 options:0];
    
    NSError * error;
    NSDictionary * local = [NSJSONSerialization JSONObjectWithData:data
                                                           options:NSJSONReadingAllowFragments
                                                             error:&error];
    [JSON addEntriesFromDictionary:local];
}

NSLog(@"JSON is %@", JSON);

1
老而弥坚,经典不衰... - apollosoftware.org

3
如果您正在使用CocoaPods,请将以下内容添加到您的Podfile中,或者克隆该项目并直接使用它。这里提供了一种快速实现方式:JSONWebToken
do {
  // the token that will be decoded
  let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
  let payload = try JWT.decode(token, algorithm: .hs256("secret".data(using: .utf8)!))
  print(payload)
} catch {
  print("Failed to decode JWT: \(error)")
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接