Sign in with Apple(苹果授权登陆) java jwt方式验证

本文章借鉴的原文链接:https://blog.csdn.net/wpf199402076118/article/details/99677412

苹果授权登陆方式

  1. PC/M端授权登陆,采用协议类似于oauth2协议
  2. App端授权登陆,提供两种后端验证方式

开发者后台配置

详细配置参考该文档,手把手教学
https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple

1、 PC/M接入方式

https://appleid.apple.com/auth/authorize?response_type=code&client_id=&redirect_uri=&state=1234
参考上面的后台配置,其中client_id对应的是Services ID,redirect_uri就是后台配置的接收code码的地址
2、APP端客户端授权登陆功能开发,可以参考如下文档

https://www.jianshu.com/p/23b46dea2076

重点讲解苹果授权登陆后端如何验证

1、基于JWT的算法验证

使用到的Apple公钥接口:https://appleid.apple.com/auth/keys
详细接口文档说明参见:https://developer.apple.com/documentation/signinwithapplerestapi/fetch_apple_s_public_key_for_verifying_token_signature
接口返回值:

{
    "keys":[
        {
            "kty":"RSA",
            "kid":"AIDOPK1",
            "use":"sig",
            "alg":"RS256",
            "n":"lxrwmuYSAsTfn-lUu4goZSXBD9ackM9OJuwUVQHmbZo6GW4Fu_auUdN5zI7Y1dEDfgt7m7QXWbHuMD01HLnD4eRtY-RNwCWdjNfEaY_esUPY3OVMrNDI15Ns13xspWS3q-13kdGv9jHI28P87RvMpjz_JCpQ5IM44oSyRnYtVJO-320SB8E2Bw92pmrenbp67KRUzTEVfGU4-obP5RZ09OxvCr1io4KJvEOjDJuuoClF66AT72WymtoMdwzUmhINjR0XSqK6H0MdWsjw7ysyd_JhmqX5CAaT9Pgi0J8lU_pcl215oANqjy7Ob-VMhug9eGyxAWVfu_1u6QJKePlE-w",
            "e":"AQAB"
        }
    ]
}

kid,为密钥id标识,签名算法采用的是RS256(RSA 256 + SHA 256),kty常量标识使用RSA签名算法,其公钥参数为n和e,其值采用了BASE64编码,使用时需要先解码

使用方式:APP内苹果授权登陆会提供如下几个参数:userID、email、fullName、authorizationCode、

  • identityToken
  • userID:授权的用户唯一标识
  • email、fullName:授权的用户资料
  • authorizationCode:授权code
  • identityToken:授权用户的JWT凭证

下面针对identityToken后端验证做简要说明:

  • dentityToken参考样例(dentityToken为ios传给后端,后端也只用到了这一个jwt串):
// jwt格式 (这个jwt串为ios传给后端)
eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLnNreW1pbmcuZGV2aWNlbW9uaXRvciIsImV4cCI6MTU2NTY2ODA4NiwiaWF0IjoxNTY1NjY3NDg2LCJzdWIiOiIwMDEyNDcuOTNiM2E3OTlhN2M4NGMwY2I0NmNkMDhmMTAwNzk3ZjIuMDcwNCIsImNfaGFzaCI6Ik9oMmFtOWVNTldWWTNkcTVKbUNsYmciLCJhdXRoX3RpbWUiOjE1NjU2Njc0ODZ9.e-pdwK4iKWErr_Gcpkzo8JNi_MWh7OMnA15FvyOXQxTx0GsXzFT3qE3DmXqAar96nx3EqsHI1Qgquqt2ogyj-lLijK_46ifckdqPjncTEGzVWkNTX8uhY7M867B6aUnmR7u-cf2HsmhXrvgsJLGp2TzCI3oTp-kskBOeCPMyTxzNURuYe8zabBlUy6FDNIPeZwZXZqU0Fr3riv2k1NkGx5MqFdUq3z5mNfmWbIAuU64Z3yKhaqwGd2tey1Xxs4hHa786OeYFF3n7G5h-4kQ4lf163G6I5BU0etCRSYVKqjq-OL-8z8dHNqvTJtAYanB3OHNWCHevJFHJ2nWOTT3sbw

如何验证?

验证苹果登录主要是通过ios端传给后端的jwt串验证是否在有效期内

  • 上面说到请求苹果的公钥接口会返回一些参数,我们主要用2个参数 n 和 e 来算出publicKey 作为jwt签名解开jwt
  • 那么通过n和e算出publicKey 我们用到了一个工具类 hutool(因为封装的好比较方便,并且不需要三方依赖)
  • 引入hutool和jwt的maven依赖
        
            cn.hutool
            hutool-all
            4.5.16
        
        
            io.jsonwebtoken
            jjwt
            0.6.0
        

然后使用hutool

        //n为请求苹果接口返回的n值
       String n="lxrwmuYSAsTfn-lUu4goZSXBD9ackM9OJuwUVQHmbZo6GW4Fu_auUdN5zI7Y1dEDfgt7m7QXWbHuMD01HLnD4eRtY-RNwCWdjNfEaY_esUPY3OVMrNDI15Ns13xspWS3q-13kdGv9jHI28P87RvMpjz_JCpQ5IM44oSyRnYtVJO-320SB8E2Bw92pmrenbp67KRUzTEVfGU4-obP5RZ09OxvCr1io4KJvEOjDJuuoClF66AT72WymtoMdwzUmhINjR0XSqK6H0MdWsjw7ysyd_JhmqX5CAaT9Pgi0J8lU_pcl215oANqjy7Ob-VMhug9eGyxAWVfu_1u6QJKePlE-w";
       //e为请求苹果接口返回的e值
       String e="AQAB";
       //苹果请求回来的n和e为base64加密过,导入import cn.hutool.core.codec.Base64Decoder包,解密base64为数组
       byte[] nDecode = Base64Decoder.decode(n.getBytes());
       byte[] eDecode = Base64Decoder.decode(e.getBytes());
       //通过RSAPublicKeySpec构造器生成对象
       RSAPublicKeySpec rsaPublicKeySpec = new RSAPublicKeySpec(new BigInteger(1, nDecode), new BigInteger(1, eDecode));
       //传入hutool工具类中生成publicKey   import cn.hutool.crypto.SecureUtil;
       PublicKey publicKey = SecureUtil.generatePublicKey("RSA", rsaPublicKeySpec);
       //ios传给后端的jwt串
       String jwt="eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLnNreW1pbmcuZGV2aWNlbW9uaXRvciIsImV4cCI6MTU2NTY2ODA4NiwiaWF0IjoxNTY1NjY3NDg2LCJzdWIiOiIwMDEyNDcuOTNiM2E3OTlhN2M4NGMwY2I0NmNkMDhmMTAwNzk3ZjIuMDcwNCIsImNfaGFzaCI6Ik9oMmFtOWVNTldWWTNkcTVKbUNsYmciLCJhdXRoX3RpbWUiOjE1NjU2Njc0ODZ9.e-pdwK4iKWErr_Gcpkzo8JNi_MWh7OMnA15FvyOXQxTx0GsXzFT3qE3DmXqAar96nx3EqsHI1Qgquqt2ogyj-lLijK_46ifckdqPjncTEGzVWkNTX8uhY7M867B6aUnmR7u-cf2HsmhXrvgsJLGp2TzCI3oTp-kskBOeCPMyTxzNURuYe8zabBlUy6FDNIPeZwZXZqU0Fr3riv2k1NkGx5MqFdUq3z5mNfmWbIAuU64Z3yKhaqwGd2tey1Xxs4hHa786OeYFF3n7G5h-4kQ4lf163G6I5BU0etCRSYVKqjq-OL-8z8dHNqvTJtAYanB3OHNWCHevJFHJ2nWOTT3sbw";
       //import io.jsonwebtoken.Jwts;解jwt,publicKey作为签名
       Claims body = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(jwt).getBody();

此时按照上面的jwt值运行会抛出:
io.jsonwebtoken.ExpiredJwtException: JWT expired at 2019-08-13T11:48:06+0800. Current time: 2019-12-17T12:14:49+0800
说明该jwt已经过期,可以自己catch住这个异常给前端返回code和message

  • 如果使用真实jwt串解开,可以使用 body.getSubject() 获取用户的唯一标识

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