diff --git a/luoo_common/src/main/java/api/StatusCode.java b/luoo_common/src/main/java/api/StatusCode.java
index 8dcc0bc..a09598e 100644
--- a/luoo_common/src/main/java/api/StatusCode.java
+++ b/luoo_common/src/main/java/api/StatusCode.java
@@ -19,6 +19,7 @@ public enum StatusCode implements IErrorCode {
USER_NICK_NAME_HAS_BEEN_EXIST(10006, "昵称已经存在"),
USER_INVALID_USER_ID(10007, "无效用户id"),
USER_SENSITIVE_INFO(10008, "不能提交敏感信息"),
+ APPLE_lOGIN_FAILED(10008, "apple id登录失败"),
// music 模块错误码以20XXX,不足5位补0;
diff --git a/luoo_user/pom.xml b/luoo_user/pom.xml
index 08991bd..8c72348 100644
--- a/luoo_user/pom.xml
+++ b/luoo_user/pom.xml
@@ -110,6 +110,16 @@
sensitive-word
0.2.0
+
+ com.auth0
+ jwks-rsa
+ 0.12.0
+
+
+ com.alibaba
+ fastjson
+ 1.2.80
+
app
diff --git a/luoo_user/src/main/java/com/luoo/user/controller/LoginController.java b/luoo_user/src/main/java/com/luoo/user/controller/LoginController.java
index 42da70b..efdc0fc 100644
--- a/luoo_user/src/main/java/com/luoo/user/controller/LoginController.java
+++ b/luoo_user/src/main/java/com/luoo/user/controller/LoginController.java
@@ -11,6 +11,9 @@ import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
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.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
@@ -120,6 +123,36 @@ public class LoginController extends BaseController {
return Result.success(token);
}
+ @ApiOperation(value = "2.1 appleId登录/注册", notes = "成功后返回authorization")
+ @PostMapping("/appleIdLogin")
+ @GlobalInterceptor(frequencyType = RequestFrequencyTypeEnum.HOUR, requestFrequencyThreshold = 12)
+ public Result 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 续期
*/
diff --git a/luoo_user/src/main/java/com/luoo/user/dao/UserInfoDao.java b/luoo_user/src/main/java/com/luoo/user/dao/UserInfoDao.java
index 1beaa18..6a496dc 100644
--- a/luoo_user/src/main/java/com/luoo/user/dao/UserInfoDao.java
+++ b/luoo_user/src/main/java/com/luoo/user/dao/UserInfoDao.java
@@ -17,6 +17,8 @@ import client.vo.SimpleUser;
public interface UserInfoDao extends JpaRepository, JpaSpecificationExecutor {
public UserInfo findByMobile(String mobile);
+
+ public UserInfo findByAppleId(String appleId);
@Modifying
@Query(value = "update tb_user_info set follow_count=follow_count+? where id = ?", nativeQuery = true)
void updatefollowcount(int x, String appUserInfoId);
diff --git a/luoo_user/src/main/java/com/luoo/user/dto/request/AppleLoginReq.java b/luoo_user/src/main/java/com/luoo/user/dto/request/AppleLoginReq.java
new file mode 100644
index 0000000..996a1b3
--- /dev/null
+++ b/luoo_user/src/main/java/com/luoo/user/dto/request/AppleLoginReq.java
@@ -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;
+
+}
diff --git a/luoo_user/src/main/java/com/luoo/user/pojo/UserInfo.java b/luoo_user/src/main/java/com/luoo/user/pojo/UserInfo.java
index 3a20879..1f4e595 100644
--- a/luoo_user/src/main/java/com/luoo/user/pojo/UserInfo.java
+++ b/luoo_user/src/main/java/com/luoo/user/pojo/UserInfo.java
@@ -39,6 +39,11 @@ public class UserInfo implements Serializable {
*/
private String nickName;
+ /**
+ * apple id
+ */
+ private String appleId;
+
/**
* 头像
*/
diff --git a/luoo_user/src/main/java/com/luoo/user/service/UserInfoService.java b/luoo_user/src/main/java/com/luoo/user/service/UserInfoService.java
index 9225966..9a2136b 100644
--- a/luoo_user/src/main/java/com/luoo/user/service/UserInfoService.java
+++ b/luoo_user/src/main/java/com/luoo/user/service/UserInfoService.java
@@ -220,6 +220,33 @@ public class UserInfoService {
+ 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 orderByField(List idList) {
return userInfoDao.orderByField(idList);
}
diff --git a/luoo_user/src/main/java/com/luoo/user/util/IOSTokenUtils.java b/luoo_user/src/main/java/com/luoo/user/util/IOSTokenUtils.java
new file mode 100644
index 0000000..3b50092
--- /dev/null
+++ b/luoo_user/src/main/java/com/luoo/user/util/IOSTokenUtils.java
@@ -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 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;
+ }
+ }
+}
+
diff --git a/luoo_user/src/test/java/com/luoo/user/util/IOSLoginTest.java b/luoo_user/src/test/java/com/luoo/user/util/IOSLoginTest.java
new file mode 100644
index 0000000..d0efd3e
--- /dev/null
+++ b/luoo_user/src/test/java/com/luoo/user/util/IOSLoginTest.java
@@ -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;
+ }
+
+ }
+}