-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: 회원 엔티티 및 테이블 추가 * feat: 회원을 등록하고 조회하는 기능 추가 세부사항 - 회원 엔티티 필드를 객체로 포장한 것으로 변경 - email 객체와 nickName 객체 변수명 수정 * feat: jwt 토큰을 생성하고 검증하는 기능 추가 * feat: 구글 로그인 기능 추가 세부사항 - 구글 access token 받아오는 기능 추가 - 구글 member info 받아오는 기능 추가 * refactor: 어노테이션 수정 세부사항 - MemberService transactional 어노테이션 추가 - GoogleProvider componenet 어노테이션 service 어노테이션으로 수정 * style: 사용하지 않는 import문 제거 * config: 의존성 추가 주석 영어로 수정 * refactor: TokenProvider의 secretKey 재할당 받지 못하게 수정 * refactor: 구글 api로 받은 객체 필드 카멜케이스로 이용할 수 있도록 수정 * refactor: GoogleInfoProvider 전반적인 부분 수정 세부사항 - memberInfo 요청을 보낼 때 accessToken을 url의 param으로 요청하는 것에서 header로 넣어서 요청하는 방식으로 수정 - 예외처리 세분화 (4XX 예외와 5XX 예외 분리) - accessToken요청 시 요청 param을 객체로 추출 - 메소드 반환의 형태가 ResponseEntity가 아닌 body를 반환 * refactor: parameter 타입을 reference 타입에서 primitive 타입으로 수정 * refactor: 코드 컨벤션에 맞게 수정 및 가독성 향상 세부사항 - @repository 붙이기 - 오타 수정 - 불필요한 공백제거 - final 키워드 붙이기 - 불필요한 @NoArgsConstructor 제거 - 예외 네이밍 수정 * refactor: token 예외 처리 세분화 세부사항 - 만료된 토큰 예외 추가 - 만료된 토큰에 대한 test 추가 * refactor: 코드 통일성 및 오타 수정 * chore: 파일 명 수정 (NickName -> Nickname) * refactor: 소셜 로그인 흐름 수정 세부사항 - 소셜 로그인 시 nickname을 입력받지 않고 email로 지정한 후 추후에 수정할 수 있도록 수정 * refactor: 코드 가독성 향상 및 오타 수정
- Loading branch information
1 parent
4873586
commit f71c43b
Showing
28 changed files
with
1,075 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
71 changes: 71 additions & 0 deletions
71
backend/src/main/java/shook/shook/auth/jwt/application/TokenProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package shook.shook.auth.jwt.application; | ||
|
||
import io.jsonwebtoken.Claims; | ||
import io.jsonwebtoken.ExpiredJwtException; | ||
import io.jsonwebtoken.Jwts; | ||
import io.jsonwebtoken.MalformedJwtException; | ||
import io.jsonwebtoken.SignatureAlgorithm; | ||
import io.jsonwebtoken.security.Keys; | ||
import java.security.Key; | ||
import java.util.Base64; | ||
import java.util.Date; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Component; | ||
import shook.shook.auth.jwt.exception.TokenException; | ||
|
||
@Component | ||
public class TokenProvider { | ||
|
||
private final long accessTokenValidTime; | ||
private final long refreshTokenValidTime; | ||
private final Key secretKey; | ||
|
||
public TokenProvider( | ||
@Value("${jwt.access-token-valid-time}") final long accessTokenValidTime, | ||
@Value("${jwt.refresh-token-valid-time}") final long refreshTokenValidTime, | ||
@Value("${jwt.secret-code}") final String secretCode | ||
) { | ||
this.accessTokenValidTime = accessTokenValidTime; | ||
this.refreshTokenValidTime = refreshTokenValidTime; | ||
this.secretKey = generateSecretKey(secretCode); | ||
} | ||
|
||
private Key generateSecretKey(final String secretCode) { | ||
final String encodedSecretCode = Base64.getEncoder().encodeToString(secretCode.getBytes()); | ||
return Keys.hmacShaKeyFor(encodedSecretCode.getBytes()); | ||
} | ||
|
||
public String createAccessToken(final long memberId) { | ||
return createToken(memberId, accessTokenValidTime); | ||
} | ||
|
||
public String createRefreshToken(final long memberId) { | ||
return createToken(memberId, refreshTokenValidTime); | ||
} | ||
|
||
private String createToken(final long memberId, final long validTime) { | ||
final Claims claims = Jwts.claims().setSubject("user"); | ||
claims.put("memberId", memberId); | ||
Date now = new Date(); | ||
return Jwts.builder() | ||
.setClaims(claims) | ||
.setIssuedAt(now) | ||
.setExpiration(new Date(now.getTime() + validTime)) | ||
.signWith(secretKey, SignatureAlgorithm.HS256) | ||
.compact(); | ||
} | ||
|
||
public Claims parseClaims(final String token) { | ||
try { | ||
return Jwts.parserBuilder() | ||
.setSigningKey(secretKey) | ||
.build() | ||
.parseClaimsJws(token) | ||
.getBody(); | ||
} catch (MalformedJwtException e) { | ||
throw new TokenException.NotIssuedTokenException(); | ||
} catch (ExpiredJwtException e) { | ||
throw new TokenException.ExpiredTokenException(); | ||
} | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
backend/src/main/java/shook/shook/auth/jwt/exception/TokenException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package shook.shook.auth.jwt.exception; | ||
|
||
public class TokenException extends RuntimeException { | ||
|
||
public static class NotIssuedTokenException extends TokenException { | ||
|
||
public NotIssuedTokenException() { | ||
super(); | ||
} | ||
} | ||
|
||
public static class ExpiredTokenException extends TokenException { | ||
|
||
public ExpiredTokenException() { | ||
super(); | ||
} | ||
} | ||
} |
89 changes: 89 additions & 0 deletions
89
backend/src/main/java/shook/shook/auth/oauth/application/GoogleInfoProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package shook.shook.auth.oauth.application; | ||
|
||
import java.util.Objects; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.http.HttpEntity; | ||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.http.HttpMethod; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.client.HttpClientErrorException; | ||
import org.springframework.web.client.HttpServerErrorException; | ||
import org.springframework.web.client.RestTemplate; | ||
import shook.shook.auth.oauth.application.dto.GoogleAccessTokenRequest; | ||
import shook.shook.auth.oauth.application.dto.GoogleAccessTokenResponse; | ||
import shook.shook.auth.oauth.application.dto.GoogleMemberInfoResponse; | ||
import shook.shook.auth.oauth.exception.OAuthException; | ||
|
||
@RequiredArgsConstructor | ||
@Component | ||
public class GoogleInfoProvider { | ||
|
||
private static final String TOKEN_PREFIX = "Bearer "; | ||
private static final String GRANT_TYPE = "authorization_code"; | ||
private static final String AUTHORIZATION_HEADER = "Authorization"; | ||
|
||
@Value("${oauth2.google.access-token-url}") | ||
private String GOOGLE_ACCESS_TOKEN_URL; | ||
|
||
@Value("${oauth2.google.member-info-url}") | ||
private String GOOGLE_MEMBER_INFO_URL; | ||
|
||
@Value("${oauth2.google.client-id}") | ||
private String GOOGLE_CLIENT_ID; | ||
|
||
@Value("${oauth2.google.client-secret}") | ||
private String GOOGLE_CLIENT_SECRET; | ||
|
||
@Value("${oauth2.google.redirect-uri}") | ||
private String LOGIN_REDIRECT_URL; | ||
|
||
private final RestTemplate restTemplate; | ||
|
||
public GoogleMemberInfoResponse getMemberInfo(final String accessToken) { | ||
try { | ||
final HttpHeaders headers = new HttpHeaders(); | ||
headers.set(AUTHORIZATION_HEADER, TOKEN_PREFIX + accessToken); | ||
final HttpEntity<Object> request = new HttpEntity<>(headers); | ||
|
||
final GoogleMemberInfoResponse responseEntity = restTemplate.exchange( | ||
GOOGLE_MEMBER_INFO_URL, | ||
HttpMethod.GET, | ||
request, | ||
GoogleMemberInfoResponse.class).getBody(); | ||
|
||
if (!Objects.requireNonNull(responseEntity).isVerifiedEmail()) { | ||
throw new OAuthException.InvalidEmailException(); | ||
} | ||
|
||
return responseEntity; | ||
} catch (HttpClientErrorException e) { | ||
throw new OAuthException.InvalidAccessTokenException(); | ||
} catch (HttpServerErrorException e) { | ||
throw new OAuthException.GoogleServerException(); | ||
} | ||
} | ||
|
||
public GoogleAccessTokenResponse getAccessToken(final String authorizationCode) { | ||
try { | ||
final GoogleAccessTokenRequest googleAccessTokenRequest = new GoogleAccessTokenRequest( | ||
authorizationCode, | ||
GOOGLE_CLIENT_ID, | ||
GOOGLE_CLIENT_SECRET, | ||
LOGIN_REDIRECT_URL, | ||
GRANT_TYPE); | ||
final HttpEntity<GoogleAccessTokenRequest> request = new HttpEntity<>( | ||
googleAccessTokenRequest); | ||
|
||
return Objects.requireNonNull(restTemplate.postForEntity( | ||
GOOGLE_ACCESS_TOKEN_URL, | ||
request, | ||
GoogleAccessTokenResponse.class).getBody()); | ||
|
||
} catch (HttpClientErrorException e) { | ||
throw new OAuthException.InvalidAuthorizationCodeException(); | ||
} catch (HttpServerErrorException e) { | ||
throw new OAuthException.GoogleServerException(); | ||
} | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
backend/src/main/java/shook/shook/auth/oauth/application/OAuthService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package shook.shook.auth.oauth.application; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import shook.shook.auth.jwt.application.TokenProvider; | ||
import shook.shook.auth.oauth.application.dto.GoogleAccessTokenResponse; | ||
import shook.shook.auth.oauth.application.dto.GoogleMemberInfoResponse; | ||
import shook.shook.auth.oauth.application.dto.LoginResponse; | ||
import shook.shook.member.application.MemberService; | ||
import shook.shook.member.domain.Email; | ||
import shook.shook.member.domain.Member; | ||
|
||
@RequiredArgsConstructor | ||
@Service | ||
public class OAuthService { | ||
|
||
private final MemberService memberService; | ||
private final GoogleInfoProvider googleInfoProvider; | ||
private final TokenProvider tokenProvider; | ||
|
||
public LoginResponse login(final String accessCode) { | ||
final GoogleAccessTokenResponse accessTokenResponse = | ||
googleInfoProvider.getAccessToken(accessCode); | ||
final GoogleMemberInfoResponse memberInfo = googleInfoProvider | ||
.getMemberInfo(accessTokenResponse.getAccessToken()); | ||
|
||
final String userEmail = memberInfo.getEmail(); | ||
final Member member = memberService.findByEmail(new Email(userEmail)) | ||
.orElseGet(() -> memberService.register(userEmail)); | ||
|
||
final long memberId = member.getId(); | ||
final String accessToken = tokenProvider.createAccessToken(memberId); | ||
final String refreshToken = tokenProvider.createRefreshToken(memberId); | ||
return new LoginResponse(accessToken, refreshToken); | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
backend/src/main/java/shook/shook/auth/oauth/application/dto/GoogleAccessTokenRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package shook.shook.auth.oauth.application.dto; | ||
|
||
import lombok.AccessLevel; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
@AllArgsConstructor | ||
@Getter | ||
public class GoogleAccessTokenRequest { | ||
|
||
private String code; | ||
private String clientId; | ||
private String clientSecret; | ||
private String redirectUri; | ||
private String grantType; | ||
} |
16 changes: 16 additions & 0 deletions
16
backend/src/main/java/shook/shook/auth/oauth/application/dto/GoogleAccessTokenResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package shook.shook.auth.oauth.application.dto; | ||
|
||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
import lombok.AccessLevel; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@AllArgsConstructor | ||
@NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
@Getter | ||
public class GoogleAccessTokenResponse { | ||
|
||
@JsonProperty("access_token") | ||
private String accessToken; | ||
} |
19 changes: 19 additions & 0 deletions
19
backend/src/main/java/shook/shook/auth/oauth/application/dto/GoogleMemberInfoResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package shook.shook.auth.oauth.application.dto; | ||
|
||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
import lombok.AccessLevel; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@AllArgsConstructor | ||
@NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
@Getter | ||
public class GoogleMemberInfoResponse { | ||
|
||
private String email; | ||
|
||
@JsonProperty("verified_email") | ||
private boolean verifiedEmail; | ||
|
||
} |
12 changes: 12 additions & 0 deletions
12
backend/src/main/java/shook/shook/auth/oauth/application/dto/LoginResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package shook.shook.auth.oauth.application.dto; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
|
||
@AllArgsConstructor | ||
@Getter | ||
public class LoginResponse { | ||
|
||
private String accessToken; | ||
private String refreshToken; | ||
} |
15 changes: 15 additions & 0 deletions
15
backend/src/main/java/shook/shook/auth/oauth/config/OAuthConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package shook.shook.auth.oauth.config; | ||
|
||
import org.springframework.boot.web.client.RestTemplateBuilder; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.web.client.RestTemplate; | ||
|
||
@Configuration | ||
public class OAuthConfig { | ||
|
||
@Bean | ||
public RestTemplate getRestTemplate() { | ||
return new RestTemplateBuilder().build(); | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
backend/src/main/java/shook/shook/auth/oauth/exception/OAuthException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package shook.shook.auth.oauth.exception; | ||
|
||
public class OAuthException extends RuntimeException { | ||
|
||
public static class InvalidEmailException extends OAuthException { | ||
|
||
public InvalidEmailException() { | ||
super(); | ||
} | ||
} | ||
|
||
public static class InvalidAccessTokenException extends OAuthException { | ||
|
||
public InvalidAccessTokenException() { | ||
super(); | ||
} | ||
} | ||
|
||
public static class InvalidAuthorizationCodeException extends OAuthException { | ||
|
||
public InvalidAuthorizationCodeException() { | ||
super(); | ||
} | ||
} | ||
|
||
public static class GoogleServerException extends OAuthException { | ||
|
||
public GoogleServerException() { | ||
super(); | ||
} | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
backend/src/main/java/shook/shook/auth/oauth/ui/OauthController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package shook.shook.auth.oauth.ui; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.RequestParam; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import shook.shook.auth.oauth.application.OAuthService; | ||
import shook.shook.auth.oauth.application.dto.LoginResponse; | ||
|
||
@RequiredArgsConstructor | ||
@RestController | ||
public class OauthController { | ||
|
||
private final OAuthService oAuthService; | ||
|
||
@GetMapping("/login/google") | ||
public ResponseEntity<LoginResponse> googleLogin( | ||
@RequestParam("code") final String accessCode) { | ||
final LoginResponse response = oAuthService.login(accessCode); | ||
return ResponseEntity.ok(response); | ||
} | ||
} |
Oops, something went wrong.