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; + } + + } +}