
antMatchers 같은 URL 별 접근 제한@PreAuthorize , @Secured 같은 메소드 레벨 보안**[어떻게 Spring Security에서 session을 관리할까?]
SecurityContextHolderFilterSecurityContextPersistenceFilterSessionManagementFilterSecurityContextRepository 에 보안 컨텍스트가 있다면 → 필터는 아무것도 하지 않는다.SecurityContextHoler 에 익명 사용자가 아닌 Authentication 객체가 있다면 → 필터는 해당 사용자가 이전 필터에서 인증되었다고 간주SessionAuthenticationStrategy 를 호출| Spring Security 5 | Spring Security 6 | |
|---|---|---|
| 인증 시점 감지 | SessionManagementFilter 로 감지 | 인증 메커니즘이 직접 처리 |
| 세션 읽기 | 매 요청마다 HttpSession 조회 | 필요 시에만 조회 |
| 전략 호출 | 필터에서 SessionAuthenticationStrategy 호출 | 인증 로직 내부에서 직접 호출 |
| 주의 사항 | DSL 일부 무효화 가능성 존재 | sessionManagement() 일부 기능 동작 안 함 |
참고 자료
https://docs.spring.io/spring-security/reference/servlet/authentication/session-management.html
UsernamePasswordAuthenticationFilter : 로그인 처리 필터ExceptionTranslationFilter : 보안 예외 처리 필터**[코드로 보는 필터 체인 구성 예시]
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/home")
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
)
.csrf(csrf -> csrf
.disable()
);
return http.build();
}
}
SecurityContextPersistenceFilter 을 통해 기존 로그인 정보가 있는지 확인하고UsernamePasswordAuthenticationFilter 을 통해 로그인 했는지 확인ExceptionTranslationFilter 을 통해 인증 실패면 로그인 페이지로 리다이렉트FilterSecurityInterceptor 을 통해 권한(ADMIN) 체크 한다.HttpSecurity 설정을 통해 customizing 가능하다.**실제 사례로 알아보기
// 실제 폼
<form method="post"
action="/transfer">
<input type="text"
name="amount"/>
<input type="text"
name="routingNumber"/>
<input type="text"
name="account"/>
<input type="submit"
value="Transfer"/>
</form>
// 문제가 있는 코드
<form method="post"
action="https://bank.example.com/transfer">
<input type="hidden"
name="amount"
value="100.00"/>
<input type="hidden"
name="routingNumber"
value="evilsRoutingNumber"/>
<input type="hidden"
name="account"
value="evilsAccountNumber"/>
<input type="submit"
value="Win Money!"/>
</form>
사용자가 송금액, 은행 번호, 계좌 번호를 직접 입력하고 송금하는 정상적인 요청
아래는 공격자가 만든 폼:
그렇기에 Spring이 2가지 CSRF 공격에 대응하는 mechanism을 제공합니다
추가적으로 GET, HEAD, OPTIONS, TRACE같은 메서드는 application을 바꾸면 안되기에 read-only로 관리하는 것을 권장합니다.
**[그 외에 구체적인 주의사항]
참고 자료
https://docs.spring.io/spring-security/reference/features/exploits/csrf.html
hyttsu02.comhttp, https 등[CORS 오류 예시]
Access to XMLHttpRequest at 'http://localhost:8080/api/data'
from origin 'http://localhost:3000' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
[참고 코드를 넣어 놓을게요! : Sesssion 사용 안하는 JWT 서버 ]
// JWT 서버를 만들 예정, Session 사용 안함.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
log.debug("디버그 : filtterChain 등록함");
// iframe 허용 안 함
http.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable));
// enable 이면 postMan 작동 안 함
http.csrf(AbstractHttpConfigurer::disable);
http.cors(cors -> cors.configurationSource(configurationSource()));
//httpBasic은 브라우저가 팝업창을 이용해서 사용자 인증을 진행
http.httpBasic(AbstractHttpConfigurer::disable);
// 필터 적용
http.with(new CustomSecurityFilterManager(), c -> c.getClass());
// 인증 실패
http.exceptionHandling((exceptionConfig) ->
exceptionConfig.authenticationEntryPoint((request, response, authException) -> {
CustomResponseUtil.fail(response, "로그인을 진행해 주세요", HttpStatus.UNAUTHORIZED);
}));
// 권한 실패
http.exceptionHandling((exceptionConfig) -> exceptionConfig.accessDeniedHandler((request, response, e) -> {
CustomResponseUtil.fail(response, "권한이 없습니다.", HttpStatus.FORBIDDEN);
}));
http.authorizeHttpRequests(auth -> auth
.requestMatchers(new AntPathRequestMatcher("/api/s/**")).authenticated()
.requestMatchers(new AntPathRequestMatcher("/api/admin/**")).hasRole("" + UserEnum.ADMIN)
.anyRequest().permitAll());
// react, 앱으로 요청할 예정
http.formLogin(AbstractHttpConfigurer::disable);
// jSessionId를 서버쪽에서 관리 안 하겠다는 뜻!
http.sessionManagement(sessionManagement ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
public CorsConfigurationSource configurationSource() {
log.debug("디버그 : ConfigurationSource cors 설정이 SecurityFilterChain에 등록함");
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedHeader("*");
configuration.addAllowedMethod("*"); //GET, POST, PUT, DELETE (Javascript 요청 허용)
configuration.addAllowedOriginPattern("*"); // 모든 IP 주소 허용 (프론트 엔드 IP만 허용 react)
configuration.setAllowCredentials(true); // 클라이언트에서 쿠키 요청 허용
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
CorsConfigurationSource 보이시나요?[Global으로 CORS 허용 하는 방법 + 예시 코드]
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 모든 경로
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowCredentials(true);
}
}