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解析成功的时候,在不改动自己原有代码的情况下取巧:
前端传递版本号,将高于当前版本的隐式注册(即送审的版本、送审时设置成手动更新、送审完成后后端关闭隐式注册)
Apple登录_第1张图片

更优的方法:

合理使用security的角色权限,对于所有的第三方登录全都使用隐式注册,但是没有数据添加权限!!!在需要执行操作时提示绑定手机号授予新权限。

你可能感兴趣的:(Spring,Security)