Skip to content

Commit b5e1262

Browse files
authored
[Refactor] security 구조 변경 (#27)
* refactor: security 구조 변경 (#24) * refactor: auth URI 변경 (#24) * refactor: 리이슈 트리거를 상태 코드로 변경 (#24) * refactor: token entity 구조 변경 (#24)
1 parent ed3e6f3 commit b5e1262

23 files changed

+139
-105
lines changed

src/main/java/project/backend/business/auth/AuthService.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010
import org.springframework.stereotype.Service;
1111
import org.springframework.transaction.annotation.Transactional;
1212
import project.backend.business.auth.implement.KakaoLoginManager;
13-
import project.backend.common.auth.token.BlacklistToken;
13+
import project.backend.entity.token.BlacklistToken;
1414
import project.backend.business.auth.request.TokenServiceRequest;
1515
import project.backend.common.error.CustomException;
1616
import project.backend.common.error.ErrorCode;
1717
import project.backend.repository.auth.BlacklistTokenRedisRepository;
1818
import project.backend.repository.auth.RefreshTokenRedisRepository;
1919
import project.backend.entity.user.User;
20-
import project.backend.common.auth.token.RefreshToken;
20+
import project.backend.entity.token.RefreshToken;
2121
import project.backend.business.auth.implement.TokenProvider;
2222
import project.backend.business.auth.response.TokenServiceResponse;
2323

src/main/java/project/backend/business/auth/implement/KakaoLoginManager.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import org.springframework.util.MultiValueMap;
1919
import org.springframework.web.client.HttpClientErrorException;
2020
import org.springframework.web.client.RestTemplate;
21-
import project.backend.common.auth.oauth.KakaoUserInfo;
21+
import project.backend.security.oauth.KakaoUserInfo;
2222
import project.backend.common.error.exception.UserNotAuthenticatedException;
2323
import project.backend.entity.user.User;
2424
import project.backend.repository.user.UserRepository;

src/main/java/project/backend/business/auth/implement/TokenProvider.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import org.springframework.security.core.authority.SimpleGrantedAuthority;
2424
import org.springframework.stereotype.Component;
2525
import project.backend.business.auth.response.TokenServiceResponse;
26-
import project.backend.common.auth.oauth.KakaoUserDetails;
26+
import project.backend.security.oauth.KakaoUserDetails;
2727
import project.backend.repository.auth.BlacklistTokenRedisRepository;
2828

2929
@Slf4j

src/main/java/project/backend/business/auth/oauth/KakaoUserDetailsService.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
99
import org.springframework.security.oauth2.core.user.OAuth2User;
1010
import org.springframework.stereotype.Service;
11-
import project.backend.common.auth.oauth.KakaoUserDetails;
12-
import project.backend.common.auth.oauth.KakaoUserInfo;
11+
import project.backend.security.oauth.KakaoUserDetails;
12+
import project.backend.security.oauth.KakaoUserInfo;
1313
import project.backend.entity.user.User;
1414
import project.backend.repository.user.UserRepository;
1515

src/main/java/project/backend/common/config/SecurityConfig.java

+32-31
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
import org.springframework.security.web.SecurityFilterChain;
1818
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
1919
import project.backend.business.auth.oauth.KakaoUserDetailsService;
20-
import project.backend.common.auth.jwt.JwtAccessDeniedHandler;
21-
import project.backend.common.auth.jwt.JwtAuthenticationFailEntryPoint;
22-
import project.backend.common.auth.jwt.JwtFilter;
20+
import project.backend.security.jwt.JwtAccessDeniedHandler;
21+
import project.backend.security.jwt.JwtAuthenticationFailEntryPoint;
22+
import project.backend.security.jwt.JwtFilter;
2323
import project.backend.common.error.ExceptionHandlerFilter;
2424

2525
@Configuration
@@ -28,36 +28,37 @@
2828
@Profile("dev")
2929
public class SecurityConfig {
3030

31-
private final JwtAuthenticationFailEntryPoint jwtAuthenticationFailEntryPoint;
32-
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
33-
private final JwtFilter jwtFilter;
34-
private final KakaoUserDetailsService kakaoUserDetailsService;
31+
private final JwtAuthenticationFailEntryPoint jwtAuthenticationFailEntryPoint;
32+
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
33+
private final JwtFilter jwtFilter;
34+
private final KakaoUserDetailsService kakaoUserDetailsService;
35+
private final ExceptionHandlerFilter exceptionHandlerFilter;
3536

36-
@Bean
37-
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
38-
http.formLogin(AbstractHttpConfigurer::disable)
39-
.httpBasic(AbstractHttpConfigurer::disable)
40-
.csrf(AbstractHttpConfigurer::disable)
41-
.cors(withDefaults())
42-
.headers(headers -> headers.frameOptions(FrameOptionsConfig::disable))
43-
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
44-
.oauth2Login(oauth -> oauth.userInfoEndpoint(config -> config.userService(kakaoUserDetailsService)))
45-
.authorizeHttpRequests(request -> request
46-
.requestMatchers("/v1/auth/**").permitAll()
47-
.requestMatchers("/v1/exception/**").permitAll()
48-
.requestMatchers(HttpMethod.POST, "/post").permitAll()
49-
.requestMatchers(HttpMethod.PATCH, "/post/*/summary").permitAll()
50-
.anyRequest().authenticated()
51-
)
52-
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
53-
.addFilterBefore(new ExceptionHandlerFilter(), JwtFilter.class) // JwtFilter 에서 CustomException 사용하기 위해 추가
54-
.exceptionHandling(exceptionHandling -> {
55-
exceptionHandling.authenticationEntryPoint(jwtAuthenticationFailEntryPoint);
56-
exceptionHandling.accessDeniedHandler(jwtAccessDeniedHandler);
57-
});
37+
@Bean
38+
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
39+
http.formLogin(AbstractHttpConfigurer::disable)
40+
.httpBasic(AbstractHttpConfigurer::disable)
41+
.csrf(AbstractHttpConfigurer::disable)
42+
.cors(withDefaults())
43+
.headers(headers -> headers.frameOptions(FrameOptionsConfig::disable))
44+
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
45+
.oauth2Login(oauth -> oauth.userInfoEndpoint(config -> config.userService(kakaoUserDetailsService)))
46+
.authorizeHttpRequests(request -> request
47+
.requestMatchers("/auth/**").permitAll()
48+
.requestMatchers("/exception/**").permitAll()
49+
.requestMatchers(HttpMethod.POST, "/post").permitAll()
50+
.requestMatchers(HttpMethod.PATCH, "/post/*/summary").permitAll()
51+
.anyRequest().authenticated()
52+
)
53+
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
54+
.addFilterBefore(exceptionHandlerFilter, JwtFilter.class) // ExceptionHandlerFilter 의존성 주입으로 사용
55+
.exceptionHandling(exceptionHandling -> {
56+
exceptionHandling.authenticationEntryPoint(jwtAuthenticationFailEntryPoint);
57+
exceptionHandling.accessDeniedHandler(jwtAccessDeniedHandler);
58+
});
5859

59-
return http.build();
60-
}
60+
return http.build();
61+
}
6162

6263
@Bean
6364
public PasswordEncoder passwordEncoder() {

src/main/java/project/backend/common/error/ErrorCode.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ public enum ErrorCode {
88
NONE_AUTHENTICATED("인증 정보가 없습니다.", HttpStatus.UNAUTHORIZED),
99
NOT_AUTHENTICATED("유효하지 않는 인증 정보입니다.", HttpStatus.UNAUTHORIZED),
1010
USER_NOT_FOUND("존재하지 않는 유저입니다.", HttpStatus.UNAUTHORIZED),
11-
INVALID_PASSWORD("올바르지 않은 비밀번호입니다.", HttpStatus.UNAUTHORIZED),
1211
BAD_REQUEST("잘못된 요청입니다.", HttpStatus.BAD_REQUEST),
1312
INVALID_REFRESH_TOKEN("유효하지 않은 리프레시 토큰입니다.", HttpStatus.UNAUTHORIZED),
14-
INVALID_ACCESS_TOKEN("유효하지 않은 엑세스 토큰입니다.", HttpStatus.UNAUTHORIZED);
13+
INVALID_ACCESS_TOKEN("유효하지 않은 엑세스 토큰입니다.", HttpStatus.UNAUTHORIZED),
14+
TOKEN_INVALID("유효하지 않은 토큰입니다.", HttpStatus.UNAUTHORIZED),
15+
ACCESS_DENIED("접근 권한이 없습니다.", HttpStatus.FORBIDDEN);
1516

1617
private final String message;
1718
private final HttpStatus httpStatus;

src/main/java/project/backend/common/error/ExceptionHandlerFilter.java

+17-13
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,38 @@
22

33
import com.fasterxml.jackson.databind.ObjectMapper;
44
import jakarta.servlet.FilterChain;
5-
import jakarta.servlet.ServletException;
65
import jakarta.servlet.http.HttpServletRequest;
76
import jakarta.servlet.http.HttpServletResponse;
8-
import lombok.extern.slf4j.Slf4j;
9-
import org.springframework.http.HttpStatus;
10-
import org.springframework.web.filter.OncePerRequestFilter;
11-
127
import java.io.IOException;
138
import java.util.HashMap;
149
import java.util.Map;
15-
10+
import java.util.Objects;
11+
import lombok.extern.slf4j.Slf4j;
12+
import org.springframework.http.HttpStatus;
13+
import org.springframework.lang.Nullable;
14+
import org.springframework.stereotype.Component;
15+
import org.springframework.web.filter.OncePerRequestFilter;
1616

1717
@Slf4j
18+
@Component
1819
public class ExceptionHandlerFilter extends OncePerRequestFilter {
1920
@Override
20-
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
21+
protected void doFilterInternal(
22+
@Nullable HttpServletRequest request,
23+
@Nullable HttpServletResponse response,
24+
@Nullable FilterChain filterChain
25+
) throws IOException {
2126
try {
22-
filterChain.doFilter(request, response);
27+
Objects.requireNonNull(filterChain).doFilter(request, response);
2328
} catch (CustomException e) {
24-
setErrorResponse(e.getErrorCode().getHttpStatus(), response, e);
29+
setErrorResponse(e.getErrorCode().getHttpStatus(), Objects.requireNonNull(response), e);
2530
} catch (Exception e) {
26-
setErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, response, e);
31+
setErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, Objects.requireNonNull(response), e);
2732
}
2833
}
2934

3035
public void setErrorResponse(HttpStatus status, HttpServletResponse response, Throwable ex) throws IOException {
31-
log.error("[ExceptionHandlerFilter] errMsg : " + ex.getMessage());
36+
log.error("[ExceptionHandlerFilter] errMsg : {}", ex.getMessage());
3237

3338
response.setStatus(status.value());
3439
response.setContentType("application/json; charset=UTF-8");
@@ -44,5 +49,4 @@ public void setErrorResponse(HttpStatus status, HttpServletResponse response, Th
4449
new ObjectMapper().writeValueAsString(errorResponse)
4550
);
4651
}
47-
48-
}
52+
}

src/main/java/project/backend/common/auth/token/BlacklistToken.java src/main/java/project/backend/entity/token/BlacklistToken.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package project.backend.common.auth.token;
1+
package project.backend.entity.token;
22

33
import lombok.Builder;
44
import lombok.Getter;

src/main/java/project/backend/common/auth/token/RefreshToken.java src/main/java/project/backend/entity/token/RefreshToken.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package project.backend.common.auth.token;
1+
package project.backend.entity.token;
22

33
import java.util.Collection;
44
import org.springframework.data.annotation.Id;

src/main/java/project/backend/presentation/auth/controller/AuthController.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
@RestController
1919
@RequiredArgsConstructor
20-
@RequestMapping("/v1/auth")
20+
@RequestMapping("/auth")
2121
public class AuthController {
2222

2323
private final AuthService authService;

src/main/java/project/backend/presentation/post/controller/PostController.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
import project.backend.business.post.PostService;
99
import project.backend.business.post.dto.PostDetailDto;
1010
import project.backend.business.post.dto.PostListDto;
11-
import project.backend.common.auth.aop.AssignCurrentUserInfo;
12-
import project.backend.common.auth.aop.AssignOrNullCurrentUserInfo;
13-
import project.backend.common.auth.aop.CurrentUserInfo;
11+
import project.backend.security.aop.AssignCurrentUserInfo;
12+
import project.backend.security.aop.AssignOrNullCurrentUserInfo;
13+
import project.backend.security.aop.CurrentUserInfo;
1414
import project.backend.presentation.post.dto.request.SummaryUrlRequest;
1515
import project.backend.presentation.post.dto.request.UpdatePostRequest;
1616
import project.backend.presentation.post.dto.response.CreateUpdatePostResponse;

src/main/java/project/backend/presentation/user/controller/UserController.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
import org.springframework.web.bind.annotation.RequestMapping;
77
import org.springframework.web.bind.annotation.RestController;
88
import project.backend.business.user.UserService;
9-
import project.backend.common.auth.aop.AssignCurrentUserInfo;
10-
import project.backend.common.auth.aop.CurrentUserInfo;
9+
import project.backend.security.aop.AssignCurrentUserInfo;
10+
import project.backend.security.aop.CurrentUserInfo;
1111
import project.backend.entity.user.User;
1212

1313
@RestController

src/main/java/project/backend/repository/auth/BlacklistTokenRedisRepository.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package project.backend.repository.auth;
22

33
import org.springframework.data.repository.CrudRepository;
4-
import project.backend.common.auth.token.BlacklistToken;
4+
import project.backend.entity.token.BlacklistToken;
55

66
public interface BlacklistTokenRedisRepository extends CrudRepository<BlacklistToken, String> {
77

src/main/java/project/backend/repository/auth/RefreshTokenRedisRepository.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import org.springframework.data.redis.core.RedisHash;
44
import org.springframework.data.repository.CrudRepository;
5-
import project.backend.common.auth.token.RefreshToken;
5+
import project.backend.entity.token.RefreshToken;
66

77
@RedisHash
88
public interface RefreshTokenRedisRepository extends CrudRepository<RefreshToken, Long> {

src/main/java/project/backend/common/auth/aop/AssignCurrentUserInfo.java src/main/java/project/backend/security/aop/AssignCurrentUserInfo.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package project.backend.common.auth.aop;
1+
package project.backend.security.aop;
22

33
import java.lang.annotation.ElementType;
44
import java.lang.annotation.Retention;

src/main/java/project/backend/common/auth/aop/AssignCurrentUserInfoAspect.java src/main/java/project/backend/security/aop/AssignCurrentUserInfoAspect.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package project.backend.common.auth.aop;
1+
package project.backend.security.aop;
22

33
import java.lang.reflect.Method;
44
import java.util.Arrays;
@@ -12,14 +12,14 @@
1212
import org.springframework.stereotype.Component;
1313
import project.backend.common.error.CustomException;
1414
import project.backend.common.error.ErrorCode;
15-
import project.backend.common.auth.oauth.KakaoUserDetails;
15+
import project.backend.security.oauth.KakaoUserDetails;
1616

1717
@Aspect
1818
@Component
1919
public class AssignCurrentUserInfoAspect {
2020

2121
// @AssignCurrentUserInfo 가 있는 메서드 실행 전에 현재 유저의 ID를 CurrentUserInfo 객체에 할당
22-
@Before("@annotation(project.backend.common.auth.aop.AssignCurrentUserInfo)")
22+
@Before("@annotation(project.backend.security.aop.AssignCurrentUserInfo)")
2323
public void assignUserId(JoinPoint joinPoint) {
2424
Arrays.stream(joinPoint.getArgs())
2525
.forEach(arg -> getMethod(arg.getClass())
@@ -31,7 +31,7 @@ public void assignUserId(JoinPoint joinPoint) {
3131

3232
// Login 했을 경우 현재 유저의 ID를 CurrentUserInfo 객체에 할당
3333
// Login 하지 않았을 경우 CurrentUserInfo 객체에 null 할당
34-
@Before("@annotation(project.backend.common.auth.aop.AssignOrNullCurrentUserInfo)")
34+
@Before("@annotation(project.backend.security.aop.AssignOrNullCurrentUserInfo)")
3535
public void assignUserIdOrNull(JoinPoint joinPoint) {
3636
Arrays.stream(joinPoint.getArgs())
3737
.forEach(arg -> getMethod(arg.getClass())

src/main/java/project/backend/common/auth/aop/AssignOrNullCurrentUserInfo.java src/main/java/project/backend/security/aop/AssignOrNullCurrentUserInfo.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package project.backend.common.auth.aop;
1+
package project.backend.security.aop;
22

33
import java.lang.annotation.ElementType;
44
import java.lang.annotation.Retention;

src/main/java/project/backend/common/auth/aop/CurrentUserInfo.java src/main/java/project/backend/security/aop/CurrentUserInfo.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package project.backend.common.auth.aop;
1+
package project.backend.security.aop;
22

33
import jakarta.validation.constraints.Null;
44
import lombok.AccessLevel;
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,29 @@
1-
package project.backend.common.auth.jwt;
1+
package project.backend.security.jwt;
22

33
import jakarta.servlet.http.HttpServletRequest;
44
import jakarta.servlet.http.HttpServletResponse;
55
import java.io.IOException;
6+
import java.util.Map;
67
import org.springframework.security.access.AccessDeniedException;
78
import org.springframework.security.web.access.AccessDeniedHandler;
89
import org.springframework.stereotype.Component;
10+
import project.backend.common.error.ErrorCode;
11+
import com.fasterxml.jackson.databind.ObjectMapper;
912

1013
@Component
1114
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
1215

13-
private static final String EXCEPTION_ACCESS_HANDLER = "/exception/access-denied";
14-
1516
@Override
1617
public void handle(HttpServletRequest request, HttpServletResponse response,
1718
AccessDeniedException accessDeniedException) throws IOException {
18-
if (!request.isSecure()) {
19-
String redirectUrl =
20-
"https://" + request.getServerName() + EXCEPTION_ACCESS_HANDLER;
21-
response.sendRedirect(redirectUrl);
22-
} else {
23-
response.sendRedirect(EXCEPTION_ACCESS_HANDLER);
24-
}
19+
20+
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
21+
response.setContentType("application/json; charset=UTF-8");
22+
23+
ObjectMapper objectMapper = new ObjectMapper();
24+
response.getWriter().write(objectMapper.writeValueAsString(Map.of(
25+
"status", HttpServletResponse.SC_FORBIDDEN,
26+
"error", ErrorCode.ACCESS_DENIED.getMessage()
27+
)));
2528
}
2629
}
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,29 @@
1-
package project.backend.common.auth.jwt;
1+
package project.backend.security.jwt;
22

33
import jakarta.servlet.http.HttpServletRequest;
44
import jakarta.servlet.http.HttpServletResponse;
55
import java.io.IOException;
6+
import java.util.Map;
67
import org.springframework.security.core.AuthenticationException;
78
import org.springframework.security.web.AuthenticationEntryPoint;
89
import org.springframework.stereotype.Component;
10+
import project.backend.common.error.ErrorCode;
11+
import com.fasterxml.jackson.databind.ObjectMapper;
912

1013
@Component
1114
public class JwtAuthenticationFailEntryPoint implements AuthenticationEntryPoint {
1215

13-
private static final String EXCEPTION_ENTRY_POINT = "/exception/entry-point";
14-
1516
@Override
1617
public void commence(HttpServletRequest request, HttpServletResponse response,
1718
AuthenticationException authException) throws IOException {
18-
if (!request.isSecure()) {
19-
String redirectUrl =
20-
"https://" + request.getServerName() + EXCEPTION_ENTRY_POINT;
21-
response.sendRedirect(redirectUrl);
22-
} else {
23-
response.sendRedirect(EXCEPTION_ENTRY_POINT);
24-
}
19+
20+
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
21+
response.setContentType("application/json; charset=UTF-8");
22+
23+
ObjectMapper objectMapper = new ObjectMapper();
24+
response.getWriter().write(objectMapper.writeValueAsString(Map.of(
25+
"status", HttpServletResponse.SC_UNAUTHORIZED,
26+
"error", ErrorCode.NONE_AUTHENTICATED.getMessage()
27+
)));
2528
}
2629
}

0 commit comments

Comments
 (0)