Skip to content

Commit

Permalink
Merge pull request #88 from Alpha-Damyo/develop
Browse files Browse the repository at this point in the history
merge to main
  • Loading branch information
wjdwlghks authored Jun 9, 2024
2 parents bab418e + eec2ed8 commit 13316f4
Show file tree
Hide file tree
Showing 19 changed files with 240 additions and 85 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ dependencies {

// spring batch
implementation 'org.springframework.boot:spring-boot-starter-batch'

implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
}
// QueryDSL setting ------------------------------------------------------------------------------------------------
def generated = layout.buildDirectory.dir("generated/querydsl").get().asFile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.damyo.alpha.api.auth.controller.dto.SignUpRequest;
import com.damyo.alpha.api.auth.controller.dto.TokenResponse;
import com.damyo.alpha.api.auth.service.AuthService;
import com.damyo.alpha.api.user.domain.UserRepository;
import com.damyo.alpha.global.exception.error.ErrorResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
Expand All @@ -17,14 +18,19 @@
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.Map;
import java.util.UUID;

@RequestMapping("/api/auth")
@RequiredArgsConstructor
@RestController
@Slf4j
@Tag(name = "AuthController")
public class AuthController {

Expand All @@ -35,39 +41,48 @@ public class AuthController {
@Operation(summary = "회원가입", description = "토큰을 반환한다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "회원가입에 성공함", content = @Content(schema = @Schema(implementation = TokenResponse.class))),
@ApiResponse(responseCode = "A101", description = "이미 가입된 계정이 존재할 때(email 중복)", content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
@ApiResponse(responseCode = "A101", description = "이미 가입된 계정이 존재할 때(providerId 중복)", content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
public ResponseEntity<TokenResponse> signUp(
@Parameter(description = "프로필 사진", in = ParameterIn.DEFAULT)
@RequestPart(value = "image", required = false) MultipartFile image,
@Parameter(description = "회원가입 요청사항", in = ParameterIn.DEFAULT, required = true)
@RequestPart SignUpRequest signUpRequest) {

if(image != null) {
String profileUrl = s3ImageService.upload(image);
authService.signUp(signUpRequest, profileUrl);
}
else {
authService.signUp(signUpRequest, null);
String profileUrl = null;
if (image != null) {
profileUrl = s3ImageService.upload(image);
}

User user = authService.login(signUpRequest);
String token = authService.generateToken(user);
return ResponseEntity.ok().body(new TokenResponse(token));
User user = authService.signUp(signUpRequest, profileUrl);
String jwt = authService.generateToken(user.getId());
return ResponseEntity.ok().body(new TokenResponse(jwt));
}


@PostMapping("/login")
@PostMapping("/login/{provider}")
@Operation(summary = "로그인", description = "토큰을 반환한다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "회원가입에 성공함", content = @Content(schema = @Schema(implementation = TokenResponse.class))),
@ApiResponse(responseCode = "A102", description = "해당 이메일이 DB에 존재하지 않을 때.", content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
@ApiResponse(responseCode = "A102", description = "해당 토큰으로 받아온 providerId DB에 존재하지 않을 때.", content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
public ResponseEntity<TokenResponse> login(
@Parameter(description = "로그인 요청사항", in = ParameterIn.DEFAULT, required = true)
@RequestBody LoginRequest loginRequest) {
User user = authService.login(loginRequest);
String token = authService.generateToken(user);
return ResponseEntity.ok().body(new TokenResponse(token));
@RequestParam String token,
@PathVariable String provider) {
Map<String, Object> userInfo = authService.getUserInfo(provider, token);
String providerId = authService.getAttributesId(provider, userInfo);
UUID id = authService.checkIsMember(providerId);
String jwt = authService.generateToken(id);
return ResponseEntity.ok().body(new TokenResponse(jwt));
}

@GetMapping("/token")
@Operation(summary = "소셜 로그인 생략하고 토큰 발급받기(테스트용)", description = "바뀐 로그인 방식으로 회원가입된 3개의 계정에 대해 토큰을 발급받는다." +
"'106362899132468449802', '3399007981', 'tmO5sDw_IaLlon7-M7CesK43rgFDdAnogEKq-ubl_9c' 중에 하나를 골라 providerId에 넣는다")
public ResponseEntity<TokenResponse> getToken(@RequestParam String providerId) {
UUID id = authService.checkIsMember(providerId);
String jwt = authService.generateToken(id);
return ResponseEntity.ok().body(new TokenResponse(jwt));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@

@Schema(description = "회원가입 시 요청으로 오는 DTO")
public record SignUpRequest (
@Schema(description = "소셜 로그인 후 인증서버에서 받아오는 이메일", example = "[email protected]")
@Email String email,
@Schema(description = "소셜 로그인 후 발급 받은 토큰", example = "이거 지우고 소셜에서 받은 토큰 복붙하기")
@Email String token,
@Schema(description = "소셜 로그인 인증 제공자 (google, naver, kakao)", example = "kakao")
String provider,
@Schema(description = "사용자의 이름, 실명은 아니고 서비스에서 사용할 이름", example = "홍길동")
@NotBlank String name,
@Schema(description = "사용자의 성별", example = "남자")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

import static org.springframework.http.HttpStatus.NOT_FOUND;
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
import static org.springframework.http.HttpStatus.*;

@Getter
@RequiredArgsConstructor
public enum AuthErrorCode implements ErrorCode {
EMAIL_ALREADY_EXIST(NOT_FOUND, "A101","해당 이메일이 이미 존재합니다"),
EMAIL_NOT_FOUND(NOT_FOUND, "A102", "해당 이메일이 존재하지 않습니다"),
ACCOUNT_ALREADY_EXIST(NOT_FOUND, "A101","해당 계정이 이미 존재합니다"),
ACCOUNT_NOT_FOUND(NOT_FOUND, "A102", "해당 계정이 존재하지 않습니다"),
EXPIRED_TOKEN(UNAUTHORIZED, "A103", "이미 만료된 토큰입니다"),
INVALID_TOKEN(UNAUTHORIZED, "A104", "토큰이 유효하지 않습니다")
INVALID_TOKEN(UNAUTHORIZED, "A104", "토큰이 유효하지 않습니다"),
INVALID_PROVIDER(BAD_REQUEST, "A105", "인증 제공자가 올바르지 않습니다."),
FAIL_GET_INFO(BAD_REQUEST, "A106", "OAuth 토큰이 잘못되었거나 만료되었습니다."),
UNKNOWN_ERROR(BAD_REQUEST, "A107", "예상하지 못한 에러입니다.")
;

private final HttpStatus httpStatus;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.damyo.alpha.api.auth.exception;

import io.jsonwebtoken.JwtException;

public class TokenException extends JwtException {
public TokenException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,43 @@
package com.damyo.alpha.api.auth.jwt;

import com.damyo.alpha.api.auth.exception.AuthErrorCode;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import net.minidev.json.JSONObject;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;

import java.io.IOException;

import static com.damyo.alpha.api.auth.exception.AuthErrorCode.*;

@Slf4j
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final HandlerExceptionResolver resolver;

public JwtAuthenticationEntryPoint(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver) {
this.resolver = resolver;
}

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
resolver.resolveException(request, response, null, (Exception) request.getAttribute("exception"));
AuthErrorCode errorCode = (AuthErrorCode) request.getAttribute("exception");
if (errorCode.equals(EXPIRED_TOKEN)) {
setResponse(response, EXPIRED_TOKEN);
} else if (errorCode.equals(INVALID_TOKEN)) {
setResponse(response, INVALID_TOKEN);
} else {
log.info("unknown error message: " + errorCode.getMessage());
setResponse(response, UNKNOWN_ERROR);
}
}

private void setResponse(HttpServletResponse response, AuthErrorCode errorCode) throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(errorCode.getHttpStatus().value());

JSONObject responseJson = new JSONObject();
responseJson.put("code", errorCode.getExceptionCode());
responseJson.put("message", errorCode.getMessage());

response.getWriter().print(responseJson);
}
}
18 changes: 11 additions & 7 deletions src/main/java/com/damyo/alpha/api/auth/jwt/JwtProvider.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.damyo.alpha.api.auth.jwt;

import com.damyo.alpha.api.auth.exception.AuthErrorCode;
import com.damyo.alpha.api.auth.exception.TokenException;
import com.damyo.alpha.api.auth.service.UserDetailServiceImpl;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SecurityException;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
Expand All @@ -18,6 +21,7 @@
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.UUID;

@Slf4j
@RequiredArgsConstructor
Expand All @@ -26,7 +30,7 @@ public class JwtProvider {

@Value("${jwt.secret}")
private String secret;
private static final int EXPIRED_DURATION = 24;
private static final int EXPIRED_DURATION = 24 * 365;
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String GRANT_TYPE = "Bearer ";
private Key key;
Expand All @@ -38,9 +42,9 @@ private void init() {
key = Keys.hmacShaKeyFor(secret.getBytes());
}

public String generate(String email) {
public String generate(String id) {
Claims claims = Jwts.claims();
claims.put("email", email);
claims.put("id", id);
return generateToken(claims);
}

Expand Down Expand Up @@ -71,17 +75,17 @@ public String resolveToken(HttpServletRequest request) {
return null;
}

public String validateTokenAndGetEmail(String token) {
public String validateTokenAndGetId(String token) {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody()
.get("email", String.class);
.get("id", String.class);
}

public Authentication createAuthentication(String email) {
UserDetails userDetails = userDetailService.loadUserByUsername(email);
public Authentication createAuthentication(UUID id) {
UserDetails userDetails = userDetailService.loadUserByUsername(id.toString());
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
}
Original file line number Diff line number Diff line change
@@ -1,48 +1,54 @@
package com.damyo.alpha.api.auth.jwt;
package com.damyo.alpha.api.auth.jwt.filter;

import com.damyo.alpha.api.auth.exception.AuthException;
import com.damyo.alpha.api.auth.exception.TokenException;
import com.damyo.alpha.api.auth.jwt.JwtProvider;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.security.SecurityException;
import io.jsonwebtoken.security.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;

import java.io.IOException;
import java.util.UUID;

import static com.damyo.alpha.api.auth.exception.AuthErrorCode.*;
import static com.damyo.alpha.global.exception.error.CommonErrorCode.INTERNAL_SERVER_ERROR;


@RequiredArgsConstructor
@Component
@Slf4j
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtProvider jwtProvider;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = jwtProvider.resolveToken(request);
log.info(request.getRequestURI());
try {
String email = jwtProvider.validateTokenAndGetEmail(token);
Authentication authentication = jwtProvider.createAuthentication(email);
String id = jwtProvider.validateTokenAndGetId(token);
Authentication authentication = jwtProvider.createAuthentication(UUID.fromString(id));
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (ExpiredJwtException e) {
request.setAttribute("exception", new AuthException(EXPIRED_TOKEN));
request.setAttribute("exception", EXPIRED_TOKEN);
} catch (SecurityException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e) {
request.setAttribute("exception", new AuthException(INVALID_TOKEN));
log.info(e.getMessage());
request.setAttribute("exception", INVALID_TOKEN);
} catch (Exception e) {
request.setAttribute("exception", UNKNOWN_ERROR);
}

filterChain.doFilter(request, response);
}
}
Loading

0 comments on commit 13316f4

Please sign in to comment.