Apple登录
目前的APP在苹果上如果要使用第三方登录就必须实现apple登录,本文在Spring Security的基础上整合apple登录。
苹果文档
没有例子,没有代码,是真的烦。
注意:使用苹果登录时尽量不要在登录后要求用户绑定手机号,尽量实现隐式注册的效果,否则也可能会被驳回。
代码
maven依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
<version>${ jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${ jjwt.version}</version>
</dependency>
我这里的版本号是0.9.0,只是提取到配置里面了。
解析类
import lombok.Data;
@Data
public class Key {
private String kty;
private String kid;
private String use;
private String alg;
private String n;
private String e;
}
token验证工具类
验证方法封装成了一个接口
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.RSAPublicKeySpec;
import java.util.List;
import org.apache.commons.codec.binary.Base64;
import org.springframework.web.client.RestTemplate;
import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
public class AppleImpl implements Apple {
@Override
public Boolean sign(String openId, String token) {
if (token.split("\\.").length > 1) {
String first = new String(Base64.decodeBase64(token.split("\\.")[0]));
String kId = JSONObject.parseObject(first).get("kid").toString();
String claim = new String(Base64.decodeBase64(token.split("\\.")[1]));
// 官方文档:当前值为开发人员账户中的client_id,我的项目解析出来的是包名
String aud = JSONObject.parseObject(claim).get("aud").toString();
// 对应用户openId(唯一)
String sub = JSONObject.parseObject(claim).get("sub").toString();
try {
// 验证token真实性且解析出的openId==前端申请到的openId
return verify(getPublicKey(kId), token, aud, sub) && openId.equals(sub);
} catch (Exception e) {
}
}
return null;
}
/** * * @param key * 公钥 * @param jwt * token * @param audience * client_id * @param subject * openId */
public static Boolean verify(PublicKey key, String jwt, String audience, String subject) throws Exception {
JwtParser jwtParser = Jwts.parser().setSigningKey(key);
jwtParser.requireIssuer("https://appleid.apple.com");
jwtParser.requireAudience(audience);
jwtParser.requireSubject(subject);
try {
Jws<Claims> claim = jwtParser.parseClaimsJws(jwt);
if (claim != null && claim.getBody().containsKey("auth_time")) {
return true;
}
} catch (ExpiredJwtException e) {
throw new Exception("登录失败");
}
return false;
}
public static PublicKey getPublicKey(String kId) {
try {
RestTemplate restTemplate = new RestTemplate();
String str = restTemplate.getForObject("https://appleid.apple.com/auth/keys", String.class);
JSONObject data = JSONObject.parseObject(str);
List<Key> keys = JSONObject.parseArray(data.getString("keys"), Key.class);
// 返回两个Key,一般第一个就可以解析成功,不成功再使用第二个再次尝试,此处根据kId匹配,可以避免这个情况
for (Key key : keys) {
if (key.getKid().equals(kId)) {
BigInteger modulus = new BigInteger(1, Base64.decodeBase64(key.getN()));
BigInteger publicExponent = new BigInteger(1, Base64.decodeBase64(key.getE()));
RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, publicExponent);
KeyFactory kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(spec);
}
}
} catch (Exception e) {
}
return null;
}
}
至此基本就可以实现apple登录,至于openId和token的获取方式不同的项目传输方式也不一样,只要能拿到就行了。
Security整合
我这里是老版本的security,同时依赖了social,最新版只需要security就可以,为了稳定暂时没升级。
修改OpenIdAuthenticationProvider
类,可以看我前面的文章了解一下我的代码结构。
思路:
1、判断登陆类型
2、如果是苹果登陆解析token
3、解析成功隐式注册用户、解析失败进入失败处理器
上述部分是纯粹代码逻辑,所以代码就不放了
解决某些项目必须绑定手机号的问题
这种情况发生在token解析成功的时候,在不改动自己原有代码的情况下取巧:
前端传递版本号,将高于当前版本的隐式注册(即送审的版本、送审时设置成手动更新、送审完成后后端关闭隐式注册)
更优的方法:
合理使用security的角色权限,对于所有的第三方登录全都使用隐式注册,但是没有数据添加权限!!!在需要执行操作时提示绑定手机号授予新权限。
还没有评论,来说两句吧...