Skip to content

Commit

Permalink
Merge pull request #14 from Leets-Official/feat/#13/인증-인가-로직-구현
Browse files Browse the repository at this point in the history
Feat #14 인증 인가 로직 구현
  • Loading branch information
koreaioi authored Jan 7, 2025
2 parents 4055a3f + fb59c77 commit 2aade09
Show file tree
Hide file tree
Showing 26 changed files with 491 additions and 25 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ dependencies {
// Spring
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
// implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springframework.boot:spring-boot-starter-validation'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,25 @@

import com.gachtaxi.domain.members.service.AuthService;
import com.gachtaxi.global.auth.enums.OauthLoginStatus;
import com.gachtaxi.global.auth.jwt.dto.JwtTokenDto;
import com.gachtaxi.global.auth.jwt.service.JwtService;
import com.gachtaxi.global.common.response.ApiResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import static com.gachtaxi.domain.members.controller.ResponseMessage.*;
import static com.gachtaxi.global.auth.kakao.dto.KaKaoDTO.*;
import static com.gachtaxi.global.auth.kakao.dto.KaKaoDTO.OauthKakaoResponse;

@RequestMapping("/auth")
@RestController
@RequiredArgsConstructor
public class AuthController {

private final AuthService authService;
private final JwtService jwtService;

@GetMapping("/login/kakao")
public ApiResponse<OauthKakaoResponse> kakaoLogin(@RequestParam("code") String authcode, HttpServletResponse response) {
Expand All @@ -26,4 +30,13 @@ public ApiResponse<OauthKakaoResponse> kakaoLogin(@RequestParam("code") String a
: UN_REGISTER;
return ApiResponse.response(HttpStatus.OK, OAUTH_STATUS.getMessage(), res);
}

@PostMapping("/refresh")
public ApiResponse<Void> reissueRefreshToken(HttpServletRequest request, HttpServletResponse response) {
JwtTokenDto jwtTokenDto = jwtService.reissueJwtToken(request);

jwtService.setCookie(jwtTokenDto.refreshToken(), response);
jwtService.setHeader(jwtTokenDto.accessToken(), response);
return ApiResponse.response(HttpStatus.OK, REFRESH_TOKEN_REISSUE.getMessage());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public enum ResponseMessage {
REGISTER_SUCCESS("회원가입에 성공했습니다."),

// AuthController
REFRESH_TOKEN_REISSUE("토큰 재발급에 성공했습니다."),
LOGIN_SUCCESS("로그인 성공에 성공했습니다."),
UN_REGISTER("회원가입을 진행해주세요");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public record UserSignUpRequestDto(
@NotBlank String email,
@NotBlank String nickName,
@NotBlank String realName,
@NotNull Long studentNumber,
@NotNull Long studentNumber,
@NotNull Gender gender,
@NotNull Boolean termsAgreement,
@NotNull Boolean privacyAgreement,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
@Repository
public interface MemberRepository extends JpaRepository<Members, Long> {

Optional<Members> findByEmail(String email);

Optional<Members> findByStudentNumber(Long studentNumber);

Optional<Members> findByKakaoId(Long kakaoId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.gachtaxi.global.auth.jwt.annotation;

import org.springframework.security.core.annotation.AuthenticationPrincipal;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : id")
public @interface CurrentMemberId {
/*
* AuthenticationPrincipal의 id 필드를 반환
* 즉, JwtUserDetails의 id 필드를 반환
* JwtUserDetails의 id는 Userid
* */
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.gachtaxi.global.auth.jwt.authentication;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.gachtaxi.global.common.response.ApiResponse;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

import static com.gachtaxi.global.auth.jwt.exception.JwtErrorMessage.JWT_TOKEN_FORBIDDEN;

@Slf4j
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

private final static String LOG_FORMAT = "ExceptionClass: {}, Message: {}";
private final static String CONTENT_TYPE = "application/json";
private final static String CHAR_ENCODING = "UTF-8";

@Override
public void handle(HttpServletRequest request, HttpServletResponse response, org.springframework.security.access.AccessDeniedException accessDeniedException) throws IOException, ServletException {
setResponse(response);
log.error(LOG_FORMAT, accessDeniedException.getClass().getSimpleName(), accessDeniedException.getMessage());
}

private void setResponse(HttpServletResponse response) throws IOException {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType(CONTENT_TYPE);
response.setCharacterEncoding(CHAR_ENCODING);

String body = new ObjectMapper().writeValueAsString(ApiResponse.response(HttpStatus.FORBIDDEN, JWT_TOKEN_FORBIDDEN.getMessage()));
response.getWriter().write(body);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.gachtaxi.global.auth.jwt.authentication;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.gachtaxi.global.auth.jwt.exception.JwtErrorMessage;
import com.gachtaxi.global.common.response.ApiResponse;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Slf4j
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

private final static String LOG_FORMAT = "ExceptionClass: {}, Message: {}";
private final static String JWT_ERROR = "jwtError";
private final static String CONTENT_TYPE = "application/json";
private final static String CHAR_ENCODING = "UTF-8";

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
JwtErrorMessage jwtError = (JwtErrorMessage) request.getAttribute(JWT_ERROR);

if (jwtError != null) {
setResponse(response, jwtError.getMessage());
log.error(LOG_FORMAT, jwtError, jwtError.getMessage());
} else {
setResponse(response, authException.getMessage());
log.error(LOG_FORMAT, authException.getClass().getSimpleName(), authException.getMessage());
}
}

private void setResponse(HttpServletResponse response, String message) throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentType(CONTENT_TYPE);
response.setCharacterEncoding(CHAR_ENCODING);

String body = new ObjectMapper().writeValueAsString(ApiResponse.response(HttpStatus.UNAUTHORIZED, message));
response.getWriter().write(body);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.gachtaxi.global.auth.jwt.exception;

import com.gachtaxi.global.common.exception.BaseException;

import static com.gachtaxi.global.auth.jwt.exception.JwtErrorMessage.COOKIE_NOT_FOUND;
import static org.springframework.http.HttpStatus.BAD_REQUEST;

public class CookieNotFoundException extends BaseException {
public CookieNotFoundException() {
super(BAD_REQUEST, COOKIE_NOT_FOUND.getMessage());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@
@Getter
@AllArgsConstructor
public enum JwtErrorMessage {
COOKIE_NOT_FOUND("헤더에 쿠키가 없습니다."),
REDIS_NOT_FOUND("Redis 에서 찾을 수 없습니다."),

JWT_TOKEN_FORBIDDEN("권한이 없습니다."),
USER_NOT_FOUND_EMAIL("해당 이메일의 유저를 찾을 수 없습니다"),
JWT_TOKEN_NOT_FOUND("토큰을 찾을 수 없습니다"),
JWT_TOKEN_EXPIRED("만료된 토큰입니다."),
JWT_TOKEN_INVALID("유효하지 않은 토큰 입니다."),
JWT_TOKEN_UN_VALID("유효하지 않은 토큰 입니다."),
JWT_TOKEN_NOT_EXIST("헤더에 인증 토큰이 존재하지 않습니다");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.gachtaxi.global.auth.jwt.exception;

import com.gachtaxi.global.common.exception.BaseException;

import static com.gachtaxi.global.auth.jwt.exception.JwtErrorMessage.REDIS_NOT_FOUND;
import static org.springframework.http.HttpStatus.UNAUTHORIZED;

public class RefreshTokenNotFoundException extends BaseException {
public RefreshTokenNotFoundException() {
super(UNAUTHORIZED, REDIS_NOT_FOUND.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.gachtaxi.global.auth.jwt.exception;

import com.gachtaxi.global.common.exception.BaseException;

import static com.gachtaxi.global.auth.jwt.exception.JwtErrorMessage.JWT_TOKEN_EXPIRED;
import static org.springframework.http.HttpStatus.UNAUTHORIZED;

public class TokenExpiredException extends BaseException {
public TokenExpiredException() {
super(UNAUTHORIZED, JWT_TOKEN_EXPIRED.getMessage());
}
}

Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package com.gachtaxi.global.auth.jwt.exception;

import com.gachtaxi.global.common.exception.BaseException;
import static com.gachtaxi.global.auth.jwt.exception.JwtErrorMessage.JWT_TOKEN_UN_VALID;
import static org.springframework.http.HttpStatus.BAD_REQUEST;

import static com.gachtaxi.global.auth.jwt.exception.JwtErrorMessage.JWT_TOKEN_INVALID;
import static org.springframework.http.HttpStatus.UNAUTHORIZED;

public class TokenInvalidException extends BaseException {
public TokenInvalidException() {
super(BAD_REQUEST, JWT_TOKEN_UN_VALID.getMessage());
super(UNAUTHORIZED, JWT_TOKEN_INVALID.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.gachtaxi.global.auth.jwt.exception;

import com.gachtaxi.global.common.exception.BaseException;

import static com.gachtaxi.global.auth.jwt.exception.JwtErrorMessage.USER_NOT_FOUND_EMAIL;
import static org.springframework.http.HttpStatus.UNAUTHORIZED;

public class UserEmailNotFoundException extends BaseException {
public UserEmailNotFoundException() {
super(UNAUTHORIZED, USER_NOT_FOUND_EMAIL.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.gachtaxi.global.auth.jwt.filter;

import com.gachtaxi.global.auth.jwt.user.JwtUserDetails;
import com.gachtaxi.global.auth.jwt.util.JwtExtractor;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Optional;

import static com.gachtaxi.global.auth.jwt.exception.JwtErrorMessage.JWT_TOKEN_EXPIRED;
import static com.gachtaxi.global.auth.jwt.exception.JwtErrorMessage.JWT_TOKEN_NOT_FOUND;

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtExtractor jwtExtractor;

private final static String JWT_ERROR = "jwtError";

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Optional<String> token = jwtExtractor.extractJwtToken(request);

if (token.isEmpty()) {
request.setAttribute(JWT_ERROR, JWT_TOKEN_NOT_FOUND);
filterChain.doFilter(request, response);
return;
}

String accessToken = token.get();

if(jwtExtractor.isExpired(accessToken)){
request.setAttribute(JWT_ERROR, JWT_TOKEN_EXPIRED);
filterChain.doFilter(request, response);
return;
}

saveAuthentcation(accessToken);
filterChain.doFilter(request, response);
}

private void saveAuthentcation(String token) {
Long id = jwtExtractor.getId(token);
String email = jwtExtractor.getEmail(token);
String role = jwtExtractor.getRole(token);

UserDetails userDetails = JwtUserDetails.of(id, email, role);
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
Loading

0 comments on commit 2aade09

Please sign in to comment.