Skip to content

Commit

Permalink
feat: Apple Login
Browse files Browse the repository at this point in the history
  • Loading branch information
Darren4641 committed Oct 24, 2023
1 parent b944a3d commit d771080
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 1 deletion.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ dependencies {
//security && JWT
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation group: 'com.auth0', name: 'java-jwt', version: '3.4.0'
//actuator
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.suite.suite_user_service.member.auth;

import com.suite.suite_user_service.member.auth.appleDto.KeyInfo;
import com.suite.suite_user_service.member.auth.appleDto.Keys;
import com.suite.suite_user_service.member.dto.ReqSignInMemberDto;
import com.suite.suite_user_service.member.handler.CustomException;
import com.suite.suite_user_service.member.handler.StatusCode;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

@Component
public class AppleAuth {
public static final String APPLE_KEY = "https://appleid.apple.com/auth/keys";
public static final int HEADER = 0;
public static final int PAYLOAD = 1;
public static final int SIGNATURE = 2;
public static String KID = "kid";
public static String ALG = "alg";
public static String ALGORITHM = "RSA";

public ReqSignInMemberDto getAppleMemberInfo(String identityToken) {
try {
RestTemplate restTemplate = new RestTemplate();
Keys keys = restTemplate.getForEntity(APPLE_KEY, Keys.class).getBody();

Map<String, String> headerKey = getTokenHeaderInfo(identityToken);

KeyInfo keyInfo = keys.getKeys().stream().filter(
key -> key.validateKey(headerKey.get(KID), headerKey.get(ALG))).findFirst().orElseThrow( ()-> new CustomException(StatusCode.FORBIDDEN));

PublicKey publicKey = getPublicKey(keyInfo);
Claims memberInfo = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(identityToken).getBody();
JSONObject claims = claimsToJSONObject(memberInfo);

return ReqSignInMemberDto.builder()
.email(claims.get("email").toString())
.password(claims.get("sub").toString())
.isOauth(true).build();
} catch (ParseException e) {
throw new CustomException(StatusCode.NOT_FOUND);
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new CustomException(StatusCode.FAILED_SIGNUP);
}
}

private Map<String, String> getTokenHeaderInfo(String identityToken) throws ParseException {
String[] decodeToken = identityToken.split("\\.");
String headerInfo = new String(Base64.getDecoder().decode(decodeToken[HEADER]));
JSONParser parser = new JSONParser();
JSONObject keyObject = (JSONObject) parser.parse(headerInfo);

Map<String, String> map = new HashMap<>();
map.put(KID, keyObject.get(KID).toString());
map.put(ALG, keyObject.get(ALG).toString());

return map;
}

private PublicKey getPublicKey(KeyInfo keyInfo) throws NoSuchAlgorithmException, InvalidKeySpecException {
// byte[] nBytes = Base64.getUrlDecoder().decode(keyInfo.getN().substring(1, keyInfo.getN().length() - 1));
// byte[] eBytes = Base64.getUrlDecoder().decode(keyInfo.getE().substring(1, keyInfo.getE().length() - 1));
byte[] nBytes = Base64.getUrlDecoder().decode(keyInfo.getN());
byte[] eBytes = Base64.getUrlDecoder().decode(keyInfo.getE());
BigInteger n = new BigInteger(1, nBytes);
BigInteger e = new BigInteger(1, eBytes);

RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n, e);
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
return keyFactory.generatePublic(publicKeySpec);
}

private JSONObject claimsToJSONObject(Claims claims) {
JSONObject jsonObject = new JSONObject();
jsonObject.putAll(claims);
return jsonObject;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.suite.suite_user_service.member.auth.appleDto;

import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class KeyInfo {
private String kty;
private String kid;
private String use;
private String alg;
private String n;
private String e;

public KeyInfo(String kty, String kid, String use, String alg, String n, String e) {
this.kty = kty;
this.kid = kid;
this.use = use;
this.alg = alg;
this.n = n;
this.e = e;
}

public boolean validateKey(String kid, String alg) {
if(this.kid.equals(kid) && this.alg.equals(alg)) return true;
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.suite.suite_user_service.member.auth.appleDto;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.util.List;

@Getter
@RequiredArgsConstructor
public class Keys {
private List<KeyInfo> keys;
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ public ResponseEntity<Message> loginAuthSuite(@RequestBody Map<String, String> t
return ResponseEntity.ok(memberService.getOauthSuiteToken(token.get("access_token"), userAgent, passwordEncoder));
}

@PostMapping("/auth/apple/signin")
public ResponseEntity<Message> loginAppleAuthSuite(@RequestBody Map<String, String> token, @RequestHeader("User-Agent") String userAgent) {
return ResponseEntity.ok(memberService.getAppleOauthSuiteToken(token.get("access_token"), userAgent, passwordEncoder));
}

@PostMapping("/id")
public ResponseEntity<Message> findSuiteId(@RequestBody Map<String, String> inputPhoneByMember) {
return ResponseEntity.ok(new Message(StatusCode.OK, memberService.lookupEmailByPhoneNumber(inputPhoneByMember.get("phoneNumber"))));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.suite.suite_user_service.member.dto;

import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class AppleAuthDto {
private String iss;
private String aud;
private String exp;
private String iat;
private String sub;
private String email;
private String email_verified;
private String auth_time;
private boolean nonce_supported;

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public interface MemberService {
Token getSuiteToken(ReqSignInMemberDto reqSignInMemberDto, String userAgent, PasswordEncoder passwordEncoder);

Message getOauthSuiteToken(String accessToken, String userAgent, PasswordEncoder passwordEncoder);

Message getAppleOauthSuiteToken(String accessToken, String userAgent, PasswordEncoder passwordEncoder);
Map<String, Object> saveMemberInfo(ReqSignUpMemberDto reqSignUpMemberDto);
void uploadImageS3(Long memberId, MultipartFile file);
ResMemberInfoDto getMemberInfo(AuthorizerDto authorizerDto);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.suite.suite_user_service.member.auth.AppleAuth;
import com.suite.suite_user_service.member.auth.GoogleAuth;

import com.suite.suite_user_service.member.dto.*;
Expand Down Expand Up @@ -49,6 +50,7 @@ public class MemberServiceImpl implements MemberService {
private final MemberInfoRepository memberInfoRepository;
private final JwtCreator jwtCreator;
private final GoogleAuth googleAuth;
private final AppleAuth appleAuth;
private final AmazonS3 amazonS3;
private final SnsClient snsClient;
private final SuiteUserProducer suiteUserProducer;
Expand Down Expand Up @@ -77,13 +79,20 @@ else if(member.getAccountStatus().equals(AccountStatus.DISABLED.getStatus()))
}

@Override
public Message getOauthSuiteToken( String accessToken, String userAgent, PasswordEncoder passwordEncoder) {
public Message getOauthSuiteToken(String accessToken, String userAgent, PasswordEncoder passwordEncoder) {
ReqSignInMemberDto reqSignInMemberDto = googleAuth.getGoogleMemberInfo(accessToken);

Optional<Token> token = memberRepository.findByEmail(reqSignInMemberDto.getEmail()).map(member -> verifyOauthAccount(reqSignInMemberDto, userAgent, passwordEncoder));
return token.map(suiteToken -> new Message(StatusCode.OK, suiteToken)).orElseGet(() -> new Message(StatusCode.CREATED, reqSignInMemberDto));
}

public Message getAppleOauthSuiteToken(String accessToken, String userAgent, PasswordEncoder passwordEncoder) {
ReqSignInMemberDto reqSignInMemberDto = appleAuth.getAppleMemberInfo(accessToken);

Optional<Token> token = memberRepository.findByEmail(reqSignInMemberDto.getEmail()).map(member -> verifyOauthAccount(reqSignInMemberDto, userAgent, passwordEncoder));
return token.map(suiteToken -> new Message(StatusCode.OK, suiteToken)).orElseGet(() -> new Message(StatusCode.CREATED, reqSignInMemberDto));
}

@Override
@Transactional
public Map<String, Object> saveMemberInfo(ReqSignUpMemberDto reqSignUpMemberDto) {
Expand Down

0 comments on commit d771080

Please sign in to comment.