Skip to content

Commit

Permalink
Merge pull request #15 from capstone-maru/feat/#13-errorhandler
Browse files Browse the repository at this point in the history
feat #13 - 시큐리티 부분의 예외 처리 핸들링 할 수 있도록 설정 및 구현
  • Loading branch information
leejh7 authored Mar 15, 2024
2 parents a739deb + c185866 commit cfa37d5
Show file tree
Hide file tree
Showing 13 changed files with 317 additions and 4 deletions.
27 changes: 27 additions & 0 deletions src/main/java/org/capstone/maru/controller/LoginController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.capstone.maru.controller;

import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping
public class LoginController {

@GetMapping("/login")
public String socialLogin() {
return "카카오 로그인 url: login-kakao | 네이버 로그인 url: login-naver";
}

@GetMapping("/login-kakao")
public void loginKakao(HttpServletResponse response) throws IOException {
response.sendRedirect("oauth2/authorization/kakao");
}

@GetMapping(value = "/login-naver")
public void loginNaver(HttpServletResponse response) throws IOException {
response.sendRedirect("oauth2/authorization/naver");
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
package org.capstone.maru.controller;


import lombok.RequiredArgsConstructor;
import org.capstone.maru.security.principal.SharedPostPrincipal;
import org.capstone.maru.service.MemberAccountService;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
public class MainController {

private final MemberAccountService memberAccountService;

@GetMapping("/")
public String root() {
return "health check";
}

@GetMapping("/test")
public String test(@AuthenticationPrincipal SharedPostPrincipal sharedPostPrincipal) {

return sharedPostPrincipal.getName();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.capstone.maru.exception;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.resource.NoResourceFoundException;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler({NoResourceFoundException.class})
public ResponseEntity<RestErrorResponse> handleNoResourceFoundException(
NoResourceFoundException ex
) {
log.error("NoResourceFoundException occur!: {}", ex.getMessage());

return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(RestErrorResponse.of(RestErrorCode.NOT_FOUND));
}
}
27 changes: 27 additions & 0 deletions src/main/java/org/capstone/maru/exception/RestErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.capstone.maru.exception;

import lombok.Getter;

@Getter
public enum RestErrorCode {

DUPLICATE_VALUE(409, "C001", "이미 존재하는 값입니다."),
UNAUTHORIZED(401, "C001", "인증 되지 않은 사용자입니다."),
NOT_FOUND(404, "C001", "존재하지 않는 url 입니다."),
;

// 에러 코드의 '코드 상태'을 반환한다.
private final int status;

// 에러 코드의 '코드간 구분 값'을 반환한다.
private final String code;

// 에러 코드의 '코드 메시지'을 반환한다.
private final String message;

RestErrorCode(final int status, final String code, final String message) {
this.status = status;
this.code = code;
this.message = message;
}
}
49 changes: 49 additions & 0 deletions src/main/java/org/capstone/maru/exception/RestErrorResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.capstone.maru.exception;

import lombok.Builder;

/**
* @param status 에러 상태 코드
* @param code 에러 구분 코드
* @param errorMsg 에러 메시지
* @param reason 에러 이유
*/
@Builder
public record RestErrorResponse(
int status,
String code,
String errorMsg,
String reason
) {

/**
* Global Exception 전송 타입
*
* @param code ErrorCode
* @return ErrorResponse
*/
public static RestErrorResponse of(final RestErrorCode code) {
return RestErrorResponse.builder()
.status(code.getStatus())
.code(code.getCode())
.errorMsg(code.getMessage())
.build();
}

/**
* Global Exception 전송 타입
*
* @param code ErrorCode
* @param reason String
* @return ErrorResponse
*/
public static RestErrorResponse of(final RestErrorCode code, final String reason) {
return RestErrorResponse.builder()
.status(code.getStatus())
.code(code.getCode())
.errorMsg(code.getMessage())
.reason(reason)
.build();
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package org.capstone.maru.repository;

import java.util.Optional;
import org.capstone.maru.domain.MemberAccount;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberAccountRepository extends JpaRepository<MemberAccount, String> {

Optional<MemberAccount> findByEmail(String email);
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,43 @@
package org.capstone.maru.config;
package org.capstone.maru.security.config;


import lombok.extern.slf4j.Slf4j;
import org.capstone.maru.security.service.CustomOAuth2UserService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

@Slf4j
@Configuration
@EnableWebSecurity
public class SecurityConfig {

private final AuthenticationEntryPoint authEntryPoint;

private final AuthenticationFailureHandler authFailureHandler;

public SecurityConfig(
@Qualifier("customAuthenticationEntryPoint") AuthenticationEntryPoint authEntryPoint,
@Qualifier("customAuthenticationFailureHandler") AuthenticationFailureHandler authFailureHandler
) {
this.authEntryPoint = authEntryPoint;
this.authFailureHandler = authFailureHandler;
}

@Bean
@ConditionalOnProperty(name = "spring.h2.console.enabled", havingValue = "true")
public WebSecurityCustomizer configureH2ConsoleEnable() {
return web -> web.ignoring()
.requestMatchers(PathRequest.toH2Console());
.requestMatchers(PathRequest.toH2Console());
}

@Bean
Expand All @@ -33,14 +50,23 @@ public SecurityFilterChain securityFilterChain(
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.requestMatchers(
HttpMethod.GET,
"/"
"/", "/login", "login-kakao", "login-naver", "/oauth2/**", "/login/oauth2/**",
"/errorTest"
).permitAll()
.requestMatchers(
HttpMethod.POST,
"/login"
).permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oAuth -> oAuth
.userInfoEndpoint(userInfo -> userInfo
.userService(customOAuth2UserService)
)
.failureHandler(authFailureHandler)
)
.exceptionHandling(hc -> hc
.authenticationEntryPoint(authEntryPoint)
)
.csrf(
csrf -> csrf
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.capstone.maru.security.exception;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;

@Component("customAuthenticationEntryPoint")
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

private final HandlerExceptionResolver resolver;

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

@Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException
) throws IOException, ServletException {
//-- 인증 처리가 안된 사용자가 인증이 필요한 URL에 접근했을 때 작동되는 로직 입력 --//
resolver.resolveException(request, response, null, authException);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.capstone.maru.security.exception;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;

@Slf4j
@Component("customAuthenticationFailureHandler")
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {

private final HandlerExceptionResolver resolver;

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

@Override
public void onAuthenticationFailure(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception
) throws IOException, ServletException {
//-- 로그인이 실패했을 때 작동되는 로직 입력 --//
resolver.resolveException(request, response, null, exception);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.capstone.maru.security.exception;

import lombok.extern.slf4j.Slf4j;
import org.capstone.maru.exception.RestErrorCode;
import org.capstone.maru.exception.RestErrorResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice
public class GlobalSecurityExceptionHandler {

@ExceptionHandler({AuthenticationException.class})
@ResponseBody
public ResponseEntity<RestErrorResponse> handleAuthenticationException(
AuthenticationException ex
) {
log.error("AuthenticationException occur!: {}", ex.getMessage());

return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(RestErrorResponse.of(RestErrorCode.UNAUTHORIZED));
}

@ExceptionHandler({MemberAccountExistentException.class})
@ResponseBody
public ResponseEntity<RestErrorResponse> handleAlreadyHaveMemberAccountException(
MemberAccountExistentException ex
) {
log.error("MemberAccountExistentException occur!: {}", ex.getMessage());

return ResponseEntity.status(ex.getErrorCode().getStatus())
.body(RestErrorResponse.of(ex.getErrorCode(), ex.getReason()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.capstone.maru.security.exception;

import lombok.Getter;
import org.capstone.maru.exception.RestErrorCode;
import org.springframework.security.core.AuthenticationException;

@Getter
public class MemberAccountExistentException extends AuthenticationException {

private final RestErrorCode errorCode;
private final String reason;

public MemberAccountExistentException(String msg, Throwable cause) {
this(RestErrorCode.DUPLICATE_VALUE, msg);
cause.fillInStackTrace();
}

public MemberAccountExistentException(String msg) {
this(RestErrorCode.DUPLICATE_VALUE, msg);
}

public MemberAccountExistentException(RestErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
this.reason = "이미 가입한 회원 계정이 존재합니다.";
}

public MemberAccountExistentException(RestErrorCode errorCode, String reason) {
super(errorCode.getMessage());
this.errorCode = errorCode;
this.reason = reason;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,6 @@ private SocialType getSocialType(String registrationId) {
private String getMemberId(String registrationId, OAuth2Response oAuth2Response) {
return registrationId + "_" + oAuth2Response.id();
}


}
Loading

0 comments on commit cfa37d5

Please sign in to comment.