JWT(Json Web Token)란? Json 포맷을 이용하여 사용자에 대한 속성을 저장하는 Claim 기반의 Web Token. (설명 보러가기)
💡 프론트엔드가 굳이 서버에게 요청해서 email, username, picture 받아오는 이유
1. 신규 유저인지 기존 유저인지 확인하려면 DB 까지 갔다와야해서
2. 프론트엔드가 구글에 요청해서 email, username, picture 받아온 후 서버에게 로그인/회원가입 요청하면(나 이미 구글로그인 했어! email만 줄테니 로그인 된것으로 처리해줘!) 서버 입장에서 진짜 구글 로그인 한게 맞는지 믿을 수 없음 (보안 문제)
@GetMapping("/oauth/{socialLoginType}")
public CommonResponse accessOauth(@PathVariable("socialLoginType") String oauthType, @RequestParam("code") String code) {
GetSocialOAuthRes res = oAuthService.oAuthLogin(oauthType.toUpperCase(), code);
DevUser user = devUserService.findUserByEmail(res.getEmail());
if (user == null) {
return new CommonResponse(CommonCode.OAUTH_CHECK_SUCCESS, Map.of("userInfo", res));
} else {
// 유저가 이미 존재하는 경우 어떻게 Gateway에 데이터를 넘겨줄지에 따라 attribute 객체가 수정될 수 있음
return new CommonResponse(CommonCode.USER_ALREADY_EXIST, Map.of("userInfo", new GetSocialOAuthRes(user)));
}
}
@Service
@RequiredArgsConstructor
@Slf4j
public class OAuthService {
private final GoogleOauth googleOauth;
public GetSocialOAuthRes oAuthLogin(String socialLoginType, String code) throws CustomException {
GetSocialOAuthRes result;
switch (socialLoginType) {
case "GOOGLE": {
try {
//응답 객체가 JSON형식으로 되어 있으므로, 이를 deserialization해서 자바 객체에 담을 것이다.
// GoogleOAuthToken oAuthToken = googleOauth.getAccessToken(code);
//액세스 토큰을 다시 구글로 보내 구글에 저장된 사용자 정보가 담긴 응답 객체를 받아온다.
ResponseEntity<String> userInfoResponse = googleOauth.requestUserInfo(code);
//다시 JSON 형식의 응답 객체를 자바 객체로 역직렬화한다.
GoogleUser googleUser = googleOauth.getUserInfo(userInfoResponse);
log.info("googleUser: " + googleUser.getEmail());
result = new GetSocialOAuthRes(googleUser.email, googleUser.name, googleUser.getPicture());
break;
} catch (Exception e) {
log.error(">>>" + e.getMessage());
throw new CustomException(CommonCode.OAUTH_LOGIN_FAILED);
}
}
default: {
throw new CustomException(CommonCode.INVALID_SOCIAL_LOGIN_TYPE);
}
}
return result;
}
}
public class DevUserService {
private final DevUserRepository devUserRepository;
private final MongoTemplate mongoTemplate;
final static private String COLLECTION_NAME = "DevUser";
public DevUser findUserByEmail(String email) {
Query query = new Query(Criteria.where("email").is(email));
DevUser targetUser = mongoTemplate.findOne(query, DevUser.class);
return targetUser;
}
}
@GetMapping("/oauth/{socialLoginType}")
public CommonResponse accessOauth(@PathVariable("socialLoginType") String oauthType, @RequestParam("code") String code) {
String response = restClient.restTemplateGet(userUri, String.format("/auth/oauth/%s?code=%s", oauthType, code),null);
return authService.parseResponseWrapper(response, OAUTH);
}
@Slf4j
@Service
@AllArgsConstructor
public class AuthService {
private final Gson gson = new Gson();
private final JwtUtils jwtUtils;
public CommonResponse parseResponseWrapper(String response, String uri) {
HashMap responseEntity;
try {
responseEntity = gson.fromJson(response, HashMap.class);
Double codeDouble = (Double) responseEntity.get("code");
int code = codeDouble.intValue();
switch (code) {
case 200:
switch (uri) {
case SIGN_IN:
case OAUTH:
return parseSignInSuccess(responseEntity);
case SIGN_UP: return parseSignUpSuccess(responseEntity);
default: return parseChangePwSuccess(responseEntity);
}
case 201:
log.info("oauthSignUpNewUser >>> " + responseEntity);
return new CommonResponse(CommonCode.OAUTH_CHECK_SUCCESS, (LinkedTreeMap) responseEntity.get("attribute"));
default:
//에러는 음수대의 코드를 가지기 때문에 여기로 들어옴
Map map = (Map) responseEntity.get("attribute");
return new CommonResponse(CommonCode.of((code)),responseEntity.get("message").toString(), map);
}
} catch (Exception e) {
//CustomExceptionHandler에서 미처 잡지 못한 에러인 경우
log.info(">>> " + e);
return new CommonResponse(CommonCode.FAIL, Map.of("message", e.getMessage()));
}
}
private CommonResponse parseSignInSuccess(HashMap responseEntity) {
LinkedTreeMap attribute = (LinkedTreeMap) responseEntity.get("attribute");
String id = (String) attribute.get("id");
String email = (String) attribute.get("email");
String token = jwtUtils.generate(new TokenUser(id, email));
log.info("parseSignInSuccess >>> ", token);
return new CommonResponse(CommonCode.SUCCESS, Map.of("Authorization", token));
}
private CommonResponse parseSignUpSuccess(HashMap responseEntity) {
LinkedTreeMap attribute = (LinkedTreeMap) responseEntity.get("attribute");
return new CommonResponse(CommonCode.SUCCESS, attribute);
}
private CommonResponse parseChangePwSuccess(HashMap responseEntity) {
return new CommonResponse(CommonCode.SUCCESS);
}
}
200
: 로그인 성공 (기존 유저)201
: Oauth 로그인/회원가입 시도했는데 신규 유저인 경우음수
: 실패@Component
@Setter
@Getter
public class JwtProperties {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration-second}")
private long expirationSecond;
}
@Component
@RequiredArgsConstructor
public class JwtUtils implements InitializingBean {
private static final String EMAIL = "email";
private final JwtProperties jwtProperties;
private Algorithm algorithm;
private JWTVerifier jwtVerifier;
@Override
public void afterPropertiesSet() {
this.algorithm = Algorithm.HMAC512(jwtProperties.getSecret());
this.jwtVerifier = JWT.require(algorithm).acceptLeeway(5).build();
}
public boolean isValid(String token) {
try {
jwtVerifier.verify(token);
return true;
} catch (RuntimeException e){
return false;
}
}
public TokenUser decode(String token) {
jwtVerifier.verify(token);
DecodedJWT jwt = JWT.decode(token);
String id = jwt.getSubject();
String email = jwt.getClaim(EMAIL).asString();
return new TokenUser(id, email);
}
public String generate(TokenUser user) {
Date now = new Date();
Date expiresAt = new Date(now.getTime() + jwtProperties.getExpirationSecond() * 1000);
return JWT.create()
.withSubject(user.getId())
.withClaim(EMAIL, user.getEmail())
.withExpiresAt(expiresAt)
.withIssuedAt(now)
.sign(algorithm);
}
}
curl --location --request GET '<gateway 주소>:9999/api/auth/oauth/GOOGLE?code=<AccessToken>'
{
"code": -1005,
"message": "Oauth에서 프로필 정보를 가져오는데 실패했습니다.",
"attribute": null
}
{
"code": 201,
"message": "Oauth 로그인 확인",
"attribute": {
"userInfo": {
"email": "<메일>",
"userName": "<이름>",
"pictureUrl": <profile 사진 url>"
}
}
}