苹果注销功能实现 Revoke tokens(JAVA)

Revoke tokens

前言

由于苹果新政策要求在2022年6月30日之后,使用Apple ID登录的账号注销时都要调用苹果的注销Api,趁这两天事情不是特别多就把注销功能给写了,顺便记录一下。

一:实现思路

官方文档: Revoke tokens

1.1. 苹果授权登录后,会返回authorizationCode这个授权码,利用授权码调用API来生成访问令牌(access_token)和刷新令牌(refresh_token)。
access_token:调用注销Api时所需的参数
refresh_token:调用刷新Token Api时所需的参数

access_token在这个时候用不上,因为是有有效期的,我们只需要在注销的时候重新获取access_token就可以了。这个时候要将refresh_token与这个用户进行绑定,可以将其存入数据库中,为了注销的时候可以用refresh_token来获取到新的access_token
在这里插入图片描述
在这里插入图片描述
1.2.使用refresh_token来更新新的access_token
在这里插入图片描述

1.3.用access_token调用 Revoke tokens Api
在这里插入图片描述
在这里插入图片描述

二:代码实现

 maven
  <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
  </dependency>  
常量类
public class Const {
	 //client_id(应用ID)
    public static final String CLIENT_ID = "";

    //teamId(苹果开发人员帐户关联的10 个字符的团队ID)
    public static final String TEAM_ID = "";

    //Key Id (P8证书下载后可以得到keyId,跟苹果开发人员拿)
    public static final String KEY_ID = "";

    //keyValue(P8文件下载后可以得到keyValue,跟苹果开发人员拿)
    public static final String KEY_VALUE = "";
    
    //AUD(固定的)
    public static final String REVOKE_AUD = "https://appleid.apple.com";

    //生成或刷新token的URL
    public static final String AUTH_TOKEN_URL = "https://appleid.apple.com/auth/token";

    //撤销用户token请求url
    public static final String AUTH_REVOKE_URL = "https://appleid.apple.com/auth/revoke";
}
苹果授权登录成功后,根据authorizationCode授权码来获取refreshToken,将refreshToken与我们的用户绑定,可以存到数据库中
 	/**
     * 根据authorizationCode授权码来获取refreshToken
     */
	public String generateToken(String authorizationCode) {
        String refreshToken = null;
        try {
            MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
            map.add("client_id", Const.AUD);
            map.add("client_secret", generateClientSecret());
            map.add("grant_type", "authorization_code");
            map.add("code", authorizationCode);
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
            HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
            String value = restTemplate.postForEntity(Const.AUTH_TOKEN_URL, request, String.class).getBody();
            if (StringUtils.isNotBlank(value)) {
                JSONObject json = JSONObject.fromObject(value);
                refreshToken = json.getString("refresh_token");
            }
        } catch (HttpClientErrorException e) {
            throw new WZHException(e.getResponseBodyAsString());//WZHException是我自定义的异常类
        } catch (Exception e) {
            throw new WZHException(e.getMessage());
        }
        return refreshToken;
    }
client_secret秘钥的生成

在这里插入图片描述
在这里插入图片描述

   	/**
     * 生成客户端密钥
     */
    private static String generateClientSecret() throws Exception {
        Map<String, Object> header = new HashMap<>();
        header.put("kid", Const.KEY_ID); //P8文件下载得到的Key ID
        header.put("alg", SignatureAlgorithm.ES256.getValue()); //SHA256
        Map<String, Object> claims = new HashMap<>();
        long now = new Date().getTime() / 1000;
        claims.put("iss", Const.TEAM_ID);
        claims.put("iat", now);
        claims.put("exp", now + 648000); // 最长半年,单位秒
        claims.put("aud", Const.REVOKE_AUD);
        claims.put("sub", Const.CLIENT_ID);
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(Const.KEY_VALUE));//P8文件下载得到的Key Value
        KeyFactory keyFactory = KeyFactory.getInstance("EC");
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        return Jwts.builder().setHeader(header).setClaims(claims).signWith(SignatureAlgorithm.ES256, privateKey).compact();
    }
   
  
  
Revoke tokens的第一种方式:使用refreshToken进行注销
	 public Response<String> revokeToken() {
        LoginUser loginUser = getFrontUser();//从缓存获取当前登录的信息
        try {
            User user = userService.getById(loginUser.getId());//获取用户的refreshToken
            MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
            map.add("client_id", Const.CLIENT_ID);
            map.add("client_secret", generateClientSecret());
            map.add("token_type_hint", "refresh_token");
            map.add("token", user.getRefreshToken());
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
            HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
            ResponseEntity<String> values = restTemplate.postForEntity(Const.AUTH_REVOKE_URL, request, String.class);
            log.info("注销token成功");//Apple官网上请求只有200与400,这里没有抛异常,证明是成功注销的
            return Response.success("Revoke tokens success.");
        } catch (HttpClientErrorException e) {
            throw new WZHException(e.getResponseBodyAsString());
        } catch (Exception e) {
            throw new WZHException(e.getMessage());
        }
    }
Revoke tokens的第二种方式:使用refreshToken来刷新accessToken,再用accessToken来进行注销(个人推荐)
   /**
     * 根据refreshToken刷新accessToken
     */
    private String refreshToken(String refreshToken) {
        String accessToken = null;
        try {
            MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
            map.add("client_id", Const.CLIENT_ID);
            map.add("client_secret", generateClientSecret());
            map.add("grant_type", "refresh_token");
            map.add("refresh_token", refreshToken);
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
            HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
            String value = restTemplate.postForEntity(Const.AUTH_TOKEN_URL, request, String.class).getBody();
            if (StringUtils.isNotBlank(value)) {
                JSONObject json = JSONObject.fromObject(value);
                accessToken = json.getString("access_token");
            }
        } catch (HttpClientErrorException e) {
            throw new WZHException(e.getResponseBodyAsString(), 401);
        } catch (Exception e) {
            throw new WZHException(e.getMessage(), 401);
        }
        log.info("刷新token成功");
        return accessToken;
    }
  /**
     * 撤销用户token
     */
    public Response<String> revokeToken() {
        LoginUser loginUser = getFrontUser();//从缓存获取当前登录的信息
        try {
            User user = userService.getById(loginUser.getId());
            String accessToken = refreshToken(user.getRefreshToken());//更新accessToken
            MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
            map.add("client_id", Const.CLIENT_ID);
            map.add("client_secret", generateClientSecret());
            map.add("token_type_hint", "access_token");
            map.add("token", accessToken);
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
            HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
            ResponseEntity<String> values = restTemplate.postForEntity(Const.AUTH_REVOKE_URL, request, String.class);
            log.info("注销token成功");//Apple官网上请求只有200与400,这里没有抛异常,证明是成功注销的
            return Response.success("Revoke tokens success.");
        } catch (HttpClientErrorException e) {
            throw new WZHException(e.getResponseBodyAsString());
        } catch (Exception e) {
            throw new WZHException(e.getMessage());
        }
    }
注意事项
  • Revoke tokens成功之后,数据库上用户绑定的refreshToken就无效了,如果再次用refreshToken去更新accessToken时,会抛{\"error\":\"invalid_grant\",\"error_description\":\"The token has expired or has been revoked.\"}异常,所以要根据公司具体的业务来,如果注销后可以重新登录的话,就在注销成功的时候将用户绑定的refreshToken清除掉,然后下次登录的时候重新利用authorizationCode来生成refreshToken

  • revokeToken时,只要不抛异常,就证明是成功的操作,如果accessToken是错的,苹果的Api也会返回成功,因为苹果会认为这是被注销过的,所以我们的只要确保accessToken的值是没问题的就可以了

  • Apple账户修改了密码之后,refreshToken的值也会失效,所以在注销的时候,如果发现refreshToken已经失效了,得将用户绑定的refreshToken进行清除掉,同时得有个动作让用户进行重新授权来更新refreshToken。

评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值