-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from 28th-meetup/feat/auth
feat: 인증/인가 추가
- Loading branch information
Showing
34 changed files
with
1,066 additions
and
1 deletion.
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
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,47 @@ | ||
package com.kusitms.jipbap.auth; | ||
|
||
import com.kusitms.jipbap.auth.dto.*; | ||
import com.kusitms.jipbap.security.Auth; | ||
import com.kusitms.jipbap.security.AuthInfo; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
import javax.validation.Valid; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
@RequestMapping("/auth") | ||
public class AuthController { | ||
|
||
private final AuthService authService; | ||
|
||
@Operation(summary = "일반 회원 가입") | ||
@PostMapping("/signUp") | ||
@ResponseStatus(HttpStatus.OK) | ||
public void signUp(@Valid @RequestBody SignUpRequestDto dto) { | ||
authService.signUp(dto); | ||
} | ||
|
||
@Operation(summary = "로그인") | ||
@PostMapping("/signIn") | ||
@ResponseStatus(HttpStatus.OK) | ||
public SignInResponseDto signIn(@Valid @RequestBody SignInRequestDto dto) { | ||
return authService.signIn(dto.getEmail(), dto.getPassword()); | ||
} | ||
|
||
@Operation(summary = "카카오 회원 가입(로그인)") | ||
@PostMapping("/kakao") | ||
@ResponseStatus(HttpStatus.OK) | ||
public SignInResponseDto kakaoVerification(@RequestBody KakaoSignInRequestDto dto) { | ||
return authService.kakaoAutoSignIn(authService.getKakaoProfile(dto.getToken())); | ||
} | ||
|
||
@Operation(summary = "액세스 토큰 재발급 - 헤더에 refreshToken 정보 포함하여 요청") | ||
@PostMapping("/reissue") | ||
public ReissueResponseDto reissue(@Auth AuthInfo authInfo) { | ||
return authService.reissue(authInfo.getEmail(), authInfo.getToken()); | ||
} | ||
|
||
} |
50 changes: 50 additions & 0 deletions
50
src/main/java/com/kusitms/jipbap/auth/AuthExceptionHandler.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,50 @@ | ||
package com.kusitms.jipbap.auth; | ||
|
||
import com.kusitms.jipbap.auth.exception.*; | ||
import com.kusitms.jipbap.common.response.ErrorCode; | ||
import com.kusitms.jipbap.common.response.ErrorResponse; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.web.bind.annotation.ExceptionHandler; | ||
import org.springframework.web.bind.annotation.ResponseStatus; | ||
import org.springframework.web.bind.annotation.RestControllerAdvice; | ||
|
||
@Slf4j | ||
@RestControllerAdvice | ||
public class AuthExceptionHandler { | ||
@ExceptionHandler(EmailExistsException.class) | ||
@ResponseStatus(HttpStatus.BAD_REQUEST) | ||
public ErrorResponse<?> handleEmailExistsException(EmailExistsException e, HttpServletRequest request) { | ||
log.warn("Auth-001> 요청 URI: " + request.getRequestURI() + ", 에러 메세지: " + e.getMessage()); | ||
return new ErrorResponse<>(ErrorCode.EMAIL_EXISTS_ERROR); | ||
} | ||
|
||
@ExceptionHandler(InvalidEmailException.class) | ||
@ResponseStatus(HttpStatus.BAD_REQUEST) | ||
public ErrorResponse<?> handleInvalidEmailException(InvalidEmailException e, HttpServletRequest request) { | ||
log.warn("Auth-002> 요청 URI: " + request.getRequestURI() + ", 에러 메세지: " + e.getMessage()); | ||
return new ErrorResponse<>(ErrorCode.INVALID_EMAIL_ERROR); | ||
} | ||
|
||
@ExceptionHandler(InvalidPasswordException.class) | ||
@ResponseStatus(HttpStatus.BAD_REQUEST) | ||
public ErrorResponse<?> handleInvalidPasswordException(InvalidPasswordException e, HttpServletRequest request) { | ||
log.warn("Auth-003> 요청 URI: " + request.getRequestURI() + ", 에러 메세지: " + e.getMessage()); | ||
return new ErrorResponse<>(ErrorCode.INVALID_PASSWORD_ERROR); | ||
} | ||
|
||
@ExceptionHandler(RefreshTokenNotFoundException.class) | ||
@ResponseStatus(HttpStatus.BAD_REQUEST) | ||
public ErrorResponse<?> handleRefreshTokenNotFoundException(RefreshTokenNotFoundException e, HttpServletRequest request) { | ||
log.warn("Auth-004> 요청 URI: " + request.getRequestURI() + ", 에러 메세지: " + e.getMessage()); | ||
return new ErrorResponse<>(ErrorCode.INVALID_REFRESH_TOKEN_ERROR); | ||
} | ||
|
||
@ExceptionHandler(JsonException.class) | ||
@ResponseStatus(HttpStatus.BAD_REQUEST) | ||
public ErrorResponse<?> handleJsonException(JsonException e, HttpServletRequest request) { | ||
log.warn("Auth-005> 요청 URI: " + request.getRequestURI() + ", 에러 메세지: " + e.getMessage()); | ||
return new ErrorResponse<>(ErrorCode.INTERNAL_SERVER_ERROR); | ||
} | ||
} |
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,185 @@ | ||
package com.kusitms.jipbap.auth; | ||
|
||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.DeserializationFeature; | ||
import com.fasterxml.jackson.databind.JsonMappingException; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.kusitms.jipbap.auth.dto.KakaoProfileDto; | ||
import com.kusitms.jipbap.auth.dto.ReissueResponseDto; | ||
import com.kusitms.jipbap.auth.dto.SignInResponseDto; | ||
import com.kusitms.jipbap.auth.dto.SignUpRequestDto; | ||
import com.kusitms.jipbap.auth.exception.*; | ||
import com.kusitms.jipbap.security.jwt.JwtTokenProvider; | ||
import com.kusitms.jipbap.security.jwt.TokenInfo; | ||
import com.kusitms.jipbap.user.Role; | ||
import com.kusitms.jipbap.user.User; | ||
import com.kusitms.jipbap.user.UserRepository; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
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.http.ResponseEntity; | ||
import org.springframework.security.crypto.password.PasswordEncoder; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
import org.springframework.util.MultiValueMap; | ||
import org.springframework.web.client.RestTemplate; | ||
|
||
import java.lang.reflect.Member; | ||
|
||
@Slf4j | ||
@Service | ||
@RequiredArgsConstructor | ||
public class AuthService { | ||
|
||
private final UserRepository userRepository; | ||
private final PasswordEncoder passwordEncoder; | ||
private final JwtTokenProvider tokenProvider; | ||
|
||
@Value("${secret.pwd}") | ||
private String KAKAO_SECRET_SERVER_PWD; | ||
private final String INAPP = "IN_APP"; | ||
private final String KAKAO = "KAKAO"; | ||
|
||
/** | ||
* 회원 가입 | ||
* @param dto | ||
*/ | ||
@Transactional | ||
public void signUp(SignUpRequestDto dto) { | ||
|
||
if(userRepository.existsByEmail(dto.getEmail())) throw new EmailExistsException("이미 가입한 이메일입니다."); | ||
userRepository.save( | ||
User.builder() | ||
.id(null) | ||
.email(dto.getEmail()) | ||
.password(passwordEncoder.encode(dto.getPassword())) | ||
.username(dto.getUsername()) | ||
.address(dto.getAddress()) | ||
.phoneNum(dto.getPhoneNum()) | ||
.role(dto.getRole()) | ||
.refreshToken(null) | ||
.oauth(INAPP) | ||
.build() | ||
); | ||
|
||
} | ||
|
||
/** | ||
* 로그인 | ||
* @param email | ||
* @param password | ||
* @return | ||
*/ | ||
@Transactional | ||
public SignInResponseDto signIn(String email, String password) { | ||
User user = userRepository.findByEmail(email).orElseThrow(()->new InvalidEmailException("회원정보가 존재하지 않습니다.")); | ||
if(!passwordEncoder.matches(password, user.getPassword())) { | ||
if(user.getOauth().equals("KAKAO")) { | ||
throw new InvalidPasswordException("카카오 계정입니다. 카카오 로그인으로 시도해보세요."); | ||
} | ||
throw new InvalidPasswordException("잘못된 비밀번호입니다."); | ||
} | ||
|
||
TokenInfo accessToken = tokenProvider.createAccessToken(user.getEmail(), user.getRole()); | ||
TokenInfo refreshToken = tokenProvider.createRefreshToken(user.getEmail(), user.getRole()); | ||
user.updateRefreshToken(refreshToken.getToken()); | ||
return new SignInResponseDto( | ||
user.getId(), user.getEmail(), user.getImage(), user.getRole(), accessToken.getToken(), refreshToken.getToken(), accessToken.getExpireTime(), refreshToken.getExpireTime() | ||
); | ||
|
||
} | ||
|
||
/** | ||
* 카카오 api 사용하여 유저 정보 받기 | ||
* @param token | ||
* @return | ||
*/ | ||
@Transactional | ||
public KakaoProfileDto getKakaoProfile(String token) { | ||
RestTemplate rt = new RestTemplate(); | ||
HttpHeaders headers = new HttpHeaders(); | ||
headers.add("Authorization", "Bearer "+token); | ||
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8"); | ||
|
||
HttpEntity<MultiValueMap<String, String>> kakaoProfileRequest = new HttpEntity<>(headers); | ||
ResponseEntity<String> response = rt.exchange( | ||
"https://kapi.kakao.com/v2/user/me", | ||
HttpMethod.POST, | ||
kakaoProfileRequest, | ||
String.class | ||
); | ||
|
||
ObjectMapper objectMapper = new ObjectMapper(); | ||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); | ||
KakaoProfileDto profile; | ||
|
||
try { | ||
profile = objectMapper.readValue(response.getBody(), KakaoProfileDto.class); | ||
} catch(JsonMappingException e) { | ||
throw new JsonException("Json Mapping 오류가 발생했습니다."); | ||
} catch(JsonProcessingException e) { | ||
throw new JsonException("Json Processing 오류가 발생했습니다."); | ||
} | ||
|
||
return profile; | ||
} | ||
|
||
/** | ||
* 카카오 회원가입, 로그인 | ||
* @param profile | ||
* @return | ||
*/ | ||
@Transactional | ||
public SignInResponseDto kakaoAutoSignIn(KakaoProfileDto profile) { | ||
User kakaoUser = User.builder() | ||
.email(profile.getKakao_account().getEmail()) | ||
.username(profile.getProperties().getNickname()) | ||
.image(profile.getKakao_account().getProfile().getProfile_image_url()) | ||
.password(KAKAO_SECRET_SERVER_PWD) | ||
.oauth(KAKAO) | ||
.build(); | ||
|
||
User findUser = userRepository.findByEmail(kakaoUser.getEmail()).orElse(null); | ||
|
||
if(findUser != null) { | ||
log.info(profile.getKakao_account().getEmail()+": 기존 회원이 아니므로 자동 회원가입 후 로그인을 진행합니다."); | ||
signUp(new SignUpRequestDto( | ||
kakaoUser.getEmail(), | ||
kakaoUser.getPassword(), | ||
kakaoUser.getUsername(), | ||
kakaoUser.getAddress(), | ||
kakaoUser.getPhoneNum(), | ||
Role.USER, | ||
kakaoUser.getImage() | ||
)); | ||
findUser.updateOAuth(KAKAO); | ||
} else { | ||
log.info(profile.getKakao_account().getEmail()+": 기존 회원이므로 자동 로그인을 진행합니다."); | ||
} | ||
return signIn(kakaoUser.getEmail(), kakaoUser.getPassword()); | ||
} | ||
|
||
/** | ||
* refreshtoken 갱 | ||
* @param email | ||
* @param refreshToken | ||
* @return | ||
*/ | ||
@Transactional | ||
public ReissueResponseDto reissue(String email, String refreshToken) { | ||
User user = userRepository.findByEmail(email).orElseThrow(()->new InvalidEmailException("회원정보가 존재하지 않습니다.")); | ||
if(!user.getRefreshToken().equals(refreshToken)) { | ||
throw new RefreshTokenNotFoundException("리프레쉬 토큰에서 유저정보를 찾을 수 없습니다."); | ||
} | ||
tokenProvider.validateToken(refreshToken); | ||
|
||
TokenInfo newAccessToken = tokenProvider.createAccessToken(user.getEmail(), user.getRole()); | ||
TokenInfo newRefreshToken = tokenProvider.createRefreshToken(user.getEmail(), user.getRole()); | ||
return new ReissueResponseDto( | ||
newAccessToken.getToken(), newRefreshToken.getToken() | ||
); | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
src/main/java/com/kusitms/jipbap/auth/dto/KakaoProfileDto.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 com.kusitms.jipbap.auth.dto; | ||
|
||
import lombok.Data; | ||
|
||
@Data | ||
public class KakaoProfileDto { | ||
|
||
private Long id; | ||
private Properties properties; | ||
private KakaoAccount kakao_account; | ||
private String connected_at; | ||
|
||
@Data | ||
public class Properties { | ||
private String nickname; | ||
} | ||
|
||
@Data | ||
public class KakaoAccount { | ||
private Boolean has_email; | ||
private Boolean email_needs_agreement; | ||
private Boolean is_email_valid; | ||
private Boolean is_email_verified; | ||
private String email; | ||
private Profile profile; | ||
|
||
@Data | ||
public class Profile { | ||
private String profile_image_url; | ||
} | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
src/main/java/com/kusitms/jipbap/auth/dto/KakaoSignInRequestDto.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 com.kusitms.jipbap.auth.dto; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Data; | ||
import lombok.NoArgsConstructor; | ||
|
||
import javax.validation.constraints.NotBlank; | ||
|
||
@Data | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
public class KakaoSignInRequestDto { | ||
@NotBlank | ||
private String token; | ||
} |
13 changes: 13 additions & 0 deletions
13
src/main/java/com/kusitms/jipbap/auth/dto/ReissueResponseDto.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,13 @@ | ||
package com.kusitms.jipbap.auth.dto; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Data; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Data | ||
@AllArgsConstructor | ||
@NoArgsConstructor | ||
public class ReissueResponseDto { | ||
private String accessToken; | ||
private String refreshToken; | ||
} |
19 changes: 19 additions & 0 deletions
19
src/main/java/com/kusitms/jipbap/auth/dto/SignInRequestDto.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 com.kusitms.jipbap.auth.dto; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Data; | ||
import lombok.NoArgsConstructor; | ||
|
||
import javax.validation.constraints.Email; | ||
import javax.validation.constraints.NotBlank; | ||
|
||
@Data | ||
@AllArgsConstructor | ||
@NoArgsConstructor | ||
public class SignInRequestDto { | ||
@NotBlank | ||
private String email; | ||
@NotBlank | ||
private String password; | ||
} |
Oops, something went wrong.