feat:apple id Login

main
wangqing 8 months ago
parent 8b1eef8ff9
commit e225cbdc22

@ -19,6 +19,7 @@ public enum StatusCode implements IErrorCode {
USER_NICK_NAME_HAS_BEEN_EXIST(10006, "昵称已经存在"), USER_NICK_NAME_HAS_BEEN_EXIST(10006, "昵称已经存在"),
USER_INVALID_USER_ID(10007, "无效用户id"), USER_INVALID_USER_ID(10007, "无效用户id"),
USER_SENSITIVE_INFO(10008, "不能提交敏感信息"), USER_SENSITIVE_INFO(10008, "不能提交敏感信息"),
APPLE_lOGIN_FAILED(10008, "apple id登录失败"),
// music 模块错误码以20XXX不足5位补0; // music 模块错误码以20XXX不足5位补0;

@ -110,6 +110,16 @@
<artifactId>sensitive-word</artifactId> <artifactId>sensitive-word</artifactId>
<version>0.2.0</version> <version>0.2.0</version>
</dependency> </dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
<version>0.12.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.80</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
<finalName>app</finalName> <finalName>app</finalName>

@ -11,6 +11,9 @@ import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSONObject;
import com.luoo.user.dto.request.AppleLoginReq;
import com.luoo.user.util.IOSTokenUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -120,6 +123,36 @@ public class LoginController extends BaseController {
return Result.success(token); return Result.success(token);
} }
@ApiOperation(value = "2.1 appleId登录/注册", notes = "成功后返回authorization")
@PostMapping("/appleIdLogin")
@GlobalInterceptor(frequencyType = RequestFrequencyTypeEnum.HOUR, requestFrequencyThreshold = 12)
public Result<String> appleIdLogin(HttpServletRequest request,@RequestBody AppleLoginReq appleLoginReq) {
String identityToken = appleLoginReq.getIdentityToken();
// 解码后的消息体
JSONObject playloadObj = IOSTokenUtils.parserIdentityToken(identityToken);
Boolean success;
try {
success = IOSTokenUtils.verifyExc(identityToken, playloadObj);
} catch (Exception e) {
return Result.failed(StatusCode.APPLE_lOGIN_FAILED);
// throw new RuntimeException(e);
}
if (!success) {
// TODO 校验token失败具体操作
return Result.failed(StatusCode.APPLE_lOGIN_FAILED);
}
UserInfo loginUserInfo = new UserInfo();
loginUserInfo.setLastLoginIp(getIpAddr(request));
loginUserInfo.setLastUseDeviceBrand("iPhone");
String token = userInfoService.appleLoginOrRegister(loginUserInfo);
return Result.success(token);
}
/** /**
* authorization * authorization
*/ */

@ -17,6 +17,8 @@ import client.vo.SimpleUser;
public interface UserInfoDao extends JpaRepository<UserInfo, String>, JpaSpecificationExecutor<UserInfo> { public interface UserInfoDao extends JpaRepository<UserInfo, String>, JpaSpecificationExecutor<UserInfo> {
public UserInfo findByMobile(String mobile); public UserInfo findByMobile(String mobile);
public UserInfo findByAppleId(String appleId);
@Modifying @Modifying
@Query(value = "update tb_user_info set follow_count=follow_count+? where id = ?", nativeQuery = true) @Query(value = "update tb_user_info set follow_count=follow_count+? where id = ?", nativeQuery = true)
void updatefollowcount(int x, String appUserInfoId); void updatefollowcount(int x, String appUserInfoId);

@ -0,0 +1,29 @@
package com.luoo.user.dto.request;
import annotation.VerifyParam;
import enums.VerifyRegexEnum;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
/**
*
*/
@Getter
@Setter
@ApiModel
public class AppleLoginReq implements Serializable {
private static final long serialVersionUID = 1L;
private String userIdentifier;
private String identityToken;
private String givenName;
private String familyName;
private String email;
private String authorizationCode;
}

@ -39,6 +39,11 @@ public class UserInfo implements Serializable {
*/ */
private String nickName; private String nickName;
/**
* apple id
*/
private String appleId;
/** /**
* *
*/ */

@ -220,6 +220,33 @@ public class UserInfoService {
+ jwtUtil.createJWT(userInfo.getId(), userInfo.getNickName(), Constants.TOKEN_ROLE_APP_USER, userInfo.getAvatar()); + jwtUtil.createJWT(userInfo.getId(), userInfo.getNickName(), Constants.TOKEN_ROLE_APP_USER, userInfo.getAvatar());
} }
public String appleLoginOrRegister(UserInfo loginUserInfo) {
UserInfo userInfo = userInfoDao.findByAppleId(loginUserInfo.getAppleId());
if (null == userInfo) {
userInfo = loginUserInfo;
userInfo.setId(String.valueOf(idWorker.nextId()));
userInfo.setNickName("雀乐-" + NickNameUtil.getRandomNickName());
Date curDate = new Date();
userInfo.setJoinTime(curDate);
userInfo.setLastLoginTime(curDate);
userInfo.setStatus(UserStatusEnum.ENABLE.getStatus());
userInfo.setAvatar(Constants.DEFAULT_USER_AVATAR);
userInfo.setThumbnail(Constants.DEFAULT_USER_THUMBNAIL);
userInfo.setSignature(Constants.DEFAULT_USER_SIGNATURE);
} else {
userInfo.setLastUseDeviceId(loginUserInfo.getLastUseDeviceId());
userInfo.setLastUseDeviceBrand(loginUserInfo.getLastUseDeviceBrand());
userInfo.setLastLoginIp(loginUserInfo.getLastLoginIp());
}
if (!UserStatusEnum.ENABLE.getStatus().equals(userInfo.getStatus())) {
return null;
}
userInfo.setLastLoginTime(new Date());
userInfoDao.save(userInfo);
return Constants.TOKEN_PREFIX
+ jwtUtil.createJWT(userInfo.getId(), userInfo.getNickName(), Constants.TOKEN_ROLE_APP_USER, userInfo.getAvatar());
}
public List<UserInfo> orderByField(List<String> idList) { public List<UserInfo> orderByField(List<String> idList) {
return userInfoDao.orderByField(idList); return userInfoDao.orderByField(idList);
} }

@ -0,0 +1,97 @@
package com.luoo.user.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwk.Jwk;
import io.jsonwebtoken.*;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.util.Base64;
import java.util.Map;
/**
* @Classname IOSToeknUtils
* @Description IOS token
* @Date 2024/03/23
* @Created by Wang Qing
*/
public class IOSTokenUtils {
private final static String authUrl = "https://appleid.apple.com/auth/keys";
private final static String authIss = "https://appleid.apple.com";
/**
* identityToken
* @param identityToken
* @return
*/
public static JSONObject parserIdentityToken(String identityToken) {
String[] arr = identityToken.split("\\.");
String firstDate = new String(Base64.getDecoder().decode(arr[0]), StandardCharsets.UTF_8);
String decode = new String(Base64.getDecoder().decode(arr[1]), StandardCharsets.UTF_8);
JSONObject claimObj = JSON.parseObject(decode);
// 将第一部分获取到的kid放入消息体中方便后续匹配对应的公钥使用
claimObj.put("kid", JSONObject.parseObject(firstDate).get("kid"));
return claimObj;
}
/**
* kid
* @param kid
* @return
*/
public static PublicKey getPublicKey(String kid) {
try {
RestTemplate restTemplate = new RestTemplate();
JSONObject data = restTemplate.getForObject(authUrl, JSONObject.class);
assert data != null;
JSONArray jsonArray = data.getJSONArray("keys");
for (Object obj : jsonArray) {
Map json = ((Map) obj);
// 获取kid对应的公钥
if (json.get("kid").equals(kid)) {
Jwk jwa = Jwk.fromValues(json);
return jwa.getPublicKey();
}
}
} catch (Exception e) {
}
return null;
}
/**
* identityToken
*
* @param identityToken
* @param jsonObject
* @return
* @throws Exception
*/
public static Boolean verifyExc(String identityToken, JSONObject jsonObject) throws Exception {
String kid = (String) jsonObject.get("kid");
PublicKey publicKey = getPublicKey(kid);
JwtParser jwtParser = Jwts.parser().setSigningKey(publicKey);
jwtParser.requireIssuer(authIss);
jwtParser.requireAudience((String) jsonObject.get("aud"));
jwtParser.requireSubject((String) jsonObject.get("sub"));
try {
Jws<Claims> claim = jwtParser.parseClaimsJws(identityToken);
if (claim != null && claim.getBody().containsKey("auth_time")) {
return true;
}
return false;
} catch (ExpiredJwtException e) {
return false;
} catch (Exception e) {
return false;
}
}
}

@ -0,0 +1,37 @@
package com.luoo.user.util;
import com.alibaba.fastjson.JSONObject;
import org.junit.Test;
public class IOSLoginTest {
@Test
public void test() {
// 请求的JWT
// String identityToken = "eyJraWQiOiJlWGF1bm1MIiwiYWxnIjoiUlMyNTYifQ." +
// "eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLmRpeWl5aW4ub25saW5lNTMiLCJl" +
// "eHAiOjE1OTc2NTAxNzQsImlhdCI6MTU5NzY0OTU3NCwic3ViIjoiMDAxMzc3LmQ0ZDVmMTAwODQ0ZTQzZjdiMWM1O" +
// "WRiMzUyZWZkZmI4LjAyNTkiLCJjX2hhc2giOiJkTDVRdld2VTNjVHBxczNSazlUTnRBIiwiZW1haWwiOiI0OTk4O" +
// "TY1MDdAcXEuY29tIiwiZW1haWxfdmVyaWZpZWQiOiJ0cnVlIiwiYXV0aF90aW1lIjoxNTk3NjQ5NTc0LCJub25jZV9" +
// "zdXBwb3J0ZWQiOnRydWV9." +
// "hM9HjNsMJW2PjYP7SfbzF-GqOt0VnMjYGq4BoU68rkQ-K2lPp_ae5ziX6Bbr3WHg" +
// "6cc3Z8OzGO63OfExvSj9gQTR596CZLvNGXhbI3piTK6597-cYsPCTbY7xHxgdHLuL8XhD-9dXPn9rouVYu4QA1" +
// "8JBQG1Q4sGsRzLEJ5DjOM9x1bkBz4Vu_5LEOefHFHkWN_RPCh_AOJGviDzm81kTkCTWn8jpm0tGdevMR93MOf44" +
// "f7bjP2T8yezl0Vbv09TrnkdAqG0BsihCD0VN9JV7X2eagyumoxTdFfoRiOflFKAaQqohVzcqy9tHOGm_6w5h8bsR" +
// "CmtBC4PnqIFqNy_AQ";
String identityToken = "eyJraWQiOiJweWFSUXBBYm5ZIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY24ubHVvby5JbmRpZU11c2ljIiwiZXhwIjoxNzExNDQ0NzE2LCJpYXQiOjE3MTEzNTgzMTYsInN1YiI6IjAwMTcxNy5iMmYxN2JjNzIxMjc0OWY4YTMwNDIzYmY5ZTNjNzZkZC4wOTE4IiwiY19oYXNoIjoiM3JKWlQ1R2poSWxPUjRMQll1NHpIdyIsImVtYWlsIjoicG9wZXllbGF1QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJhdXRoX3RpbWUiOjE3MTEzNTgzMTYsIm5vbmNlX3N1cHBvcnRlZCI6dHJ1ZSwicmVhbF91c2VyX3N0YXR1cyI6Mn0.ZV3Bb7CY6swmR_w4HGrFhAljzxU5wmUHQAK2j-gkFbT3fSoiPIXAen8dMXl5YmhMRarrb9mfDOwJol4_Tr5wmjTaBkFcclraD4DEXtjeK9gXZVZ0w0FKShU6PD6b9JXDOxVWmrOACgGKkbiYlfXqjI2Igh06sFMd4MZ62C-eUP9d7Hmz-xWsCsh1RPLjm1LEVh_o73vlFNEOqWdLoDS7Erx3R-JYYHnCCWdLjh3bBC2wv_yUJkDs9OCBPxujVDu-YTCm9_VUKZNtfStAAIHRXD-8USI0NIVjd-AYPSU-3gRNdHPGPW0agnDwt5dkR9S96hkq1qDFDJokkFCO930_zw";
// 解码后的消息体
JSONObject playloadObj = IOSTokenUtils.parserIdentityToken(identityToken);
Boolean success;
try {
success = IOSTokenUtils.verifyExc(identityToken, playloadObj);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (!success) {
// TODO 校验token失败具体操作
return;
}
}
}
Loading…
Cancel
Save