❗Spring Security 이론
https://radiant515.tistory.com/598
이론 정리를 바탕으로 Spring Security Filter를 작성하고자 한다.
❗SecurityConfig
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
private final JwtProvider jwtProvider;
/**
* 생략
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
http.httpBasic().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/swagger-ui/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/v3/api-docs").permitAll()
.antMatchers("/h2-console/**").permitAll()
.antMatchers("/now/user").hasRole("USER")
.antMatchers("/now/admin").hasRole("ADMIN")
.and()
.cors()
.and()
.exceptionHandling()
.accessDeniedHandler(new AccessDeniedHandlerImpl())
.authenticationEntryPoint(new AuthenticationEntryPointImpl())
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtProvider), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
/**
* 생략
*/
}
Http 요청이 들어오면 Spring Security가 설정된 프로젝트는 아래와 같은 절차를 거쳐 요청이 수행된다.
1. 요청 수신
HTTP 요청이 들어오면 Spring Security의 필터 체인이 이를 가로채서 처리한다. SecurityFilterChain은 여러 필터들로 구성되며, 각 필터는 요청을 검증하고 각 역할에 맞는 조치를 취한다.
2. CorsFilter: CORS 설정 적용
가장 먼저, CorsFilter가 실행된다. 이 필터는 CORS (Cross-Origin Resource Sharing) 설정을 처리하며 corsConfigurationSource 메서드에서 정의한 대로 CORS 정책을 적용합니다.
3. JwtAuthenticationFilter: JWT 인증 처리
그 다음, 커스텀 JwtAuthenticationFilter가 실행된다. 이 필터는 JWT (JSON Web Token)를 사용한 인증을 처리하고 필터 체인에서 UsernamePasswordAuthenticationFilter 앞에 위치합니다.
Authorization 헤더 검사 | 요청 헤더에서 JWT 토큰을 추출 |
토큰 검증 | JwtProvider를 사용해 토큰의 유효성을 검증 |
Authentication 객체 생성 | 토큰이 유효한 경우, Authentication 객체를 생성하고 이를 SecurityContextHolder에 설정 |
4. SessionManagementFilter: 세션 관리 설정
이 필터는 세션 관리 설정을 처리하고 설정에서 SessionCreationPolicy.STATELESS로 지정했으므로, 세션을 생성하지 않으며 모든 요청은 독립적으로 처리된다.
5. ExceptionTranslationFilter: 예외 처리
예외 처리 필터로 권한 부족 시 AccessDeniedHandlerImpl를, 인증 실패 시 AuthenticationEntryPointImpl를 사용하여 오류 응답을 생성한다.
6. FilterSecurityInterceptor: 접근 제어
이 필터는 요청 URL과 메서드를 기반으로 접근 권한을 검사하고 HttpSecurity 설정에서 정의한 접근 제어 규칙에 따라 요청을 허용하거나 거부한다.
현재 이 코드에서는 이와 같은 접근 권한을 부여하였다.
- /swagger-resources/**, /swagger-ui/**, /webjars/**, /v3/api-docs, /h2-console/** 등의 경로는 모두 접근 허용 (permitAll).
- /now/user는 USER 권한이 있는 사용자만 접근 가능 (hasRole("USER")).
- /now/admin는 ADMIN 권한이 있는 사용자만 접근 가능 (hasRole("ADMIN")).
7. 서블릿으로 요청 처리 및 응답 생성
필터 체인을 통과한 요청은 최종적으로 서블릿에 도달하여 실제 컨트롤러 로직이 실행된다. 여기서 클라이언트의 요청을 처리하고, 필요한 경우 데이터베이스와 상호작용하거나 응답을 생성한다.
8. 응답 생성 및 반환
컨트롤러 로직이 완료되면 응답이 생성되고 클라이언트에게 반환된다. 응답이 반환될 때에도 마찬가지로 필터 체인을 거쳐서 반환됩니다.
❗ ExceptionTranslationFilter
✏️ AuthenticationEntryPointImpl
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
AuthenticationException ex) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
httpServletResponse.setContentType("application/json");
httpServletResponse.setCharacterEncoding("utf-8");
ApiResponse<String> httpRes = new ApiResponse<>(HttpStatus.UNAUTHORIZED.value(),"인증에 실패하였습니다.");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(httpRes));
}
}
AuthenticationEntryPointImpl는 인증 실패 시에 발생하는 오류이다. 요청하는 API가 유저의 정보가 필요한데 유저 정보가 없어서 인증되지 않은 사용자에게 알리는 오류 메시지이다. 보호된 리소스에 접근하려고 하면 AuthenticationEntryPointImpl의 commence 메서드가 호출하여 401 Unauthorized 오류를 반환한다. 예시로 로그인이 되어야 마이 페이지에 접근할 수 있는데 로그인을 하지 않고 접근하면 이런 오류를 만날 수 있다.
✏️AccessDeniedHandlerImpl
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
AccessDeniedException accessDeniedException) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
httpServletResponse.setContentType("application/json");
httpServletResponse.setCharacterEncoding("utf-8");
ApiResponse<String> httpRes = new ApiResponse<>(HttpStatus.FORBIDDEN.value(), "권한이 존재하지 않습니다.");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(httpRes));
}
}
AccessDeniedHandlerImpl은 유저 정보는 있지만 접근 권한이 없는 경우에 발생한다. 접근 권한이 없는 리소스에 접근하려고 하면 AccessDeniedHandlerImpl의 handle 메서드가 호출하여 403 Forbidden 오류를 반환한다. 예를 들어, ADMIN 권한을 요구하는 API에 USER 권한을 갖고 접근한다면 이 오류를 반환해 준다.
'🔻Back-End > Features' 카테고리의 다른 글
[Features] ngrok 기반 로컬 LoadBalancer 구현 (0) | 2024.08.18 |
---|---|
[Features] S3 버킷 생성 및 SpringBoot 프로젝트에 연결 (0) | 2024.08.10 |
[Features] Spring Security 구현 - 3 (0) | 2024.08.03 |
[Features] Spring Security 구현 - 2 (0) | 2024.08.03 |
[Features] spring 이메일 인증 구현(구글 이메일 이용) (0) | 2023.01.19 |