Spring security web config 설정
시작
스프링부트로 웹 프로젝트를 개발할 때 스프링 시큐리티는 필수적인 프레임워크 기능입니다. 실무에서는 이미 설정이 완료된 상태의 프로젝트 환경에서 개발을 했지만 직접 프로젝트를 생성하고 환경을 구성해보고자 하였습니다. 이 포스팅에서는 제가 설정한 파일의 코드와 왜 그렇게 설정했는가에 대한 이유를 풀이하였습니다.
전문
@Configuration
@EnableWebSecurity
public class WebConfig implements WebMvcConfigurer {
//Spring security 관련 설정
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // CSRF 보호 비활성화
.authorizeRequests()
.anyRequest().permitAll();
return http.build();
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public Globals globals() {
return new Globals();
}
@Bean
public AccessJwtToken accessJwtToken(Globals globals) {
return new AccessJwtToken(globals);
}
@Bean
public TokenInterceptor tokenInterceptor(AccessJwtToken accessJwtToken) {
return new TokenInterceptor(accessJwtToken);
}
// API 호출 시 도메인 허용
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*") // 모든 도메인을 허용
.allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 토큰 interceptor
List<String> excludeList = new ArrayList<String>();
excludeList.add("/v1/login/login");
excludeList.add("/v1/login/signup");
excludeList.add("/v1/login/generateAccessToken");
excludeList.add("/v1/login/logout");
excludeList.add("/error");
registry.addInterceptor(tokenInterceptor(accessJwtToken(globals())))
.addPathPatterns("/**")
.excludePathPatterns(excludeList);
}
}
상세
@Configuration 어노테이션은 스프링의 ioc 컨테이너가 메소드를 호출하여 빈으로 등록할 수 있도록 합니다. 그리고@EnableWebSecurity을 통해 웹 보안 설정을 활성화하여 사용자 정의 보안 설정을 할 수 있습니다.
//Spring security 관련 설정
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // CSRF 보호 비활성화
.authorizeRequests()
.anyRequest().permitAll();
return http.build();
}
먼저 csrf 보호를 비활성화합니다. 현재 제 프로젝트는 리액트를 프론트로 구성하고 있습니다. 로그인과 관련하여 필요한 정보는 로컬 스토리지에 저장하고 있으며 사용자는 API 요청을 보낼 때마다 토큰 정보를 포함하여 보내야만 합니다. 따라서 서버는 클라이언트의 요청을 신뢰할 수 있어 CSRF의 공격에 안전하므로 비활성화 처리하였습니다.
CSRF(Cross-Site Request Forgery)?
공격자가 사용자 인증 정보를 도용하여 사용자가 의도하지 않은 행동을 하게 만드는 공격. 사용자가 악의적인 웹사이트를 방문했을 때 사용자가 다른 사이트에 로그인한 상태임을 이용하여 의도치 않은 요청을 날릴 수 있다.
또한 인터셉터에서 별도로 페이지 권한을 설정하기 때문에 시큐리티에서는 모든 권한을 허가합니다(permitAll).
@Bean
public AccessJwtToken accessJwtToken(Globals globals) {
return new AccessJwtToken(globals);
}
@Bean
public TokenInterceptor tokenInterceptor(AccessJwtToken accessJwtToken) {
return new TokenInterceptor(accessJwtToken);
}
엑세스 토큰 관련 설정을 위해 빈을 등록해줍니다. 해당 클래스에서 엑세스 토큰 생성, 소멸, 인증 단계를 관리합니다.
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*") // 모든 도메인을 허용
.allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS");
}
모든 도메인의 api 요청을 열어둡니다. 이는 사용자 정의 요구사항에 맞게 특정 도메인만을 열어둔다거나 api 방식을 관리하도록 할 수 있습니다.
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 토큰 interceptor
List<String> excludeList = new ArrayList<String>();
excludeList.add("/v1/login/login");
excludeList.add("/v1/login/signup");
excludeList.add("/v1/login/generateAccessToken");
excludeList.add("/v1/login/logout");
excludeList.add("/error");
registry.addInterceptor(tokenInterceptor(accessJwtToken(globals())))
.addPathPatterns("/**")
.excludePathPatterns(excludeList);
}
인터셉터와 관련한 설정 부분입니다. 여기서는 TokenInterceptor를 통해 유효한 엑세스 토큰인지를 검증한 후에 해당 api를 요청하도록 정의하였습니다. 그리고 excludeList를 통해 TokenInterceptor의 영향을 받지 않는 api를 별도로 지정하여 정의하였습니다. 위의 코드처럼 로그인이나 회원가입 등의 api는 로그인을 통해 토큰을 발급받기 이전에 사용이 가능해야 하므로 TokenInterceptor의 범위에서 제외하였습니다.