Skip to content

Commit

Permalink
refactor: 로그인 구현 방식 전면 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
prislewarz committed Jan 18, 2024
1 parent 0d3faa0 commit 6aae646
Show file tree
Hide file tree
Showing 26 changed files with 804 additions and 325 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ dependencies {
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.2'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.0.4.RELEASE'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
developmentOnly 'org.springframework.boot:spring-boot-devtools'

compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
implementation 'org.hibernate:hibernate-entitymanager:5.4.27.Final'

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.example.solutionchallenge.common.config;

import com.example.solutionchallenge.common.config.exception.ExceptionHandlerFilter;
import com.example.solutionchallenge.enums.UserRole;
import com.example.solutionchallenge.filter.JwtFilter;
import com.example.solutionchallenge.service.JwtTokenService;
import com.example.solutionchallenge.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import static org.springframework.security.config.Customizer.withDefaults;

@RequiredArgsConstructor
@Configuration
public class SecurityConfig {
private final JwtTokenService jwtTokenService;
private final UserService userService;

@Bean
public AuthenticationManager authenticationManager(
final AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}

@Bean
public SecurityFilterChain configure(final HttpSecurity http) throws Exception {
return http.cors(withDefaults())
.csrf((csrf) -> csrf.disable())
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/login/**", "/token/refresh").permitAll()
.requestMatchers("/user/**").hasAuthority(UserRole.USER.getRole())
.anyRequest().authenticated())
.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.formLogin(httpSecurityFormLoginConfigurer -> httpSecurityFormLoginConfigurer.disable()) // 로그인 폼 미사용
.httpBasic(httpSecurityHttpBasicConfigurer -> httpSecurityHttpBasicConfigurer.disable()) // http basic 미사용
.addFilterBefore(new JwtFilter(jwtTokenService, userService), UsernamePasswordAuthenticationFilter.class) // JWT Filter 추가
.addFilterBefore(new ExceptionHandlerFilter(), JwtFilter.class) // Security Filter 에서 CustomException 사용하기 위해 추가
.build();
}

@Bean
public WebSecurityCustomizer webSecurityCustomizer(){
// 아래 url은 filter 에서 제외
return web ->
web.ignoring()
.requestMatchers("/login/**", "/token/refresh");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,54 @@

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;

@Getter
@RequiredArgsConstructor
public enum ErrorCode {

POSTS_EMPTY_TITLE(400, "제목을 입력하세요.", 417),
POSTS_EMPTY_DEPARTURE(400, "출발지를 입력하세요", 418),
POSTS_EMPTY_DESTINATION(400, "목적지를 입력하세요", 419),
POSTS_EMPTY_DEPARTURE_TIME(400, "출발시각을 입력하세요", 420),
POSTS_ALREADY_FINISH(400, "이미 마감된 공고입니다.", 421),

PARTICIPATION_DETAILS_ALREADY_JOIN(400, "이미 참가 신청이 되었습니다.", 422),
PARTICIPATION_DETAILS_ALREADY_CANCEL(400, "이미 참가 취소가 되었습니다.", 423),

TOTAL_DETAIL_EMPTY_DISTANCE(400, "최종 거리를 입력하세요.", 424),
TOTAL_DETAIL_EMPTY_PAYMENT(400, "최종 금액을 입력하세요.", 425),

UNKNOWN_ERROR(500, "토큰이 존재하지 않습니다.", 1001),
WRONG_TYPE_TOKEN(500, "변조된 토큰입니다.", 1002),
EXPIRED_TOKEN(500, "만료된 토큰입니다.", 1003),
UNSUPPORTED_TOKEN(500, "변조된 토큰입니다.", 1004),
ACCESS_DENIED(500, "권한이 없습니다.", 1005),
NO_INFO(500, "토큰에 해당하는 정보가 없습니다.", 1006);

NO_INFO(500, "토큰에 해당하는 정보가 없습니다.", 1006),
UNAUTHORIZED("인증되지 않은 요청입니다.", HttpStatus.UNAUTHORIZED),
INVALID_ACCESS_TOKEN("유효하지 않은 액세스 토큰입니다.", HttpStatus.UNAUTHORIZED),
INVALID_REFRESH_TOKEN("유효하지 않은 리프레시 토큰입니다.", HttpStatus.UNAUTHORIZED),
BAD_REQUEST("잘못된 요청입니다.", HttpStatus.BAD_REQUEST),
NOT_EXIST_USER("존재하지 않는 유저입니다.", HttpStatus.UNAUTHORIZED);

private final int status;
private final String message;
private final HttpStatus httpStatus;
private final int code;

ErrorCode(int status, String message, int code) {
this.status = status;
this.message = message;
this.code = code;
this.httpStatus = null;
}

ErrorCode(String message, HttpStatus httpStatus) {
this.message = message;
this.httpStatus = httpStatus;
this.status = httpStatus.value();
this.code = -1;
}

public String getMessage() {
return message;
}

public HttpStatus getHttpStatus() {
return httpStatus;
}

public int getStatus() {
return status;
}

public int getCode() {
return code;
}
}

Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
package com.example.solutionchallenge.common.config.exception;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class ErrorResponse {

private static final ObjectMapper objectMapper = new ObjectMapper();
private String errMsg;
private String message;
private int code;
private int status;

public ErrorResponse(String errMsg) {
this.errMsg = errMsg;
}

@Builder
public ErrorResponse(String message, int code, int status) {
this.message = message;
Expand All @@ -25,4 +34,7 @@ public static ErrorResponse of(ErrorCode errorCode) {
.build();
}

public String convertToJson() throws JsonProcessingException {
return objectMapper.writeValueAsString(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.example.solutionchallenge.common.config.exception;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

public class ExceptionHandlerFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {

try {
filterChain.doFilter(request, response);
} catch (ApiException ex) {
setErrorResponse(ex.getErrorCode().getHttpStatus(), response, ex);
} catch (Exception ex) {
setErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, response, ex);
}
}

public void setErrorResponse(HttpStatus status, HttpServletResponse response, Throwable ex) throws IOException {
logger.error("[ExceptionHandlerFilter] errMsg : " + ex.getMessage());

response.setStatus(status.value());
response.setContentType("application/json; charset=UTF-8");

response.getWriter().write(
new ErrorResponse(ex.getMessage())
.convertToJson()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.example.solutionchallenge.common.config.exception;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(ApiException.class)
public ResponseEntity<ErrorResponse> handleCustomException(ApiException ex) {
logger.error("[CustomException] errCode : " + ex.getErrorCode());
logger.error("[CustomException] errMsg : " + ex.getMessage());
return new ResponseEntity(
new ErrorResponse(ex.getMessage()),
ex.getErrorCode().getHttpStatus()
);
}

@ExceptionHandler(RuntimeException.class)
public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException ex) {
logger.error("[RuntimeException] errMsg : " + ex.getMessage());
return new ResponseEntity(
new ErrorResponse(ex.getMessage()),
HttpStatus.INTERNAL_SERVER_ERROR
);
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(RuntimeException ex) {
logger.error("[Exception] errMsg : " + ex.getMessage());
return new ResponseEntity(
new ErrorResponse(ex.getMessage()),
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}

This file was deleted.

Loading

0 comments on commit 6aae646

Please sign in to comment.