Spring Security 공식 문서
Spring Security Resource 공식 문서
Spring Boot 3.x.x부터 Spring Security 6.x.x를 사용해야 한다.
1. spring security의 의존성을 설정 후<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
- AuthenticationConfig 클래스 파일에
@EnableWebSecurity
어노테이션을 달아놓으면 모든 Spring Security가 API에 인증이 필요하다고 디폴트로 설정해 놓는다.
그래서 별다른 조치를 하지 않는다면, API를 보낼 때 401(UnAuthorized)에러가 발생할 것이다.
Filter Chain?
어떤 요청이 End-Point에 도달하기 전에 요청을 가로채서 특정 작업을 수행하는 컴포넌트를 Sevlet Filter라고 한다.
이 Filter에서는 주로 Security에 대한 전처리 과정을 진행한다.
Filter Chain은 이런한 Filter가 사슬처럼 엮여있는 것을 말한다.
그러나 모든 API에 인증이 필요한 것이 아니기 때문(회원가입 등)에 몇몇 API는 권한을 풀어야 한다.
config파일에 WebSecurityConfigurerAdapter를 사용하는 것은 Spring Security 5.4이후부터 Deprecated가 되었다.
그래서 security filter chain을 Bean등록해서 사용해야한다.
Spring blog 공식문서에서 확인
Spring Security lambda DSL과 HttpSecurity메서드를 사용하여 권한 부여 규칙을 정의한다.
"날; 숨" 프로젝트에서 Spring Security를 사용했는데, 이때 나는 Login Filter와 JWT Filter, Exception Handler를 Filter 사이에 추가했다.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// cors
http
.cors((cors) -> cors.configurationSource(new CorsConfigurationSource() {
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOriginPatterns(Collections.singletonList("http://localhost:5173"));
configuration.setAllowedMethods(Collections.singletonList("*"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Collections.singletonList("*"));
configuration.setMaxAge(3600L); // 1시간 동안 캐시하도록 설정
configuration.setExposedHeaders(Collections.singletonList("Authorization"));
return configuration;
}
}))
.csrf(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
.authorizeHttpRequests((auth) -> auth
.requestMatchers(HttpMethod.GET, "/comments/**").permitAll()
.requestMatchers("/", "/general/**", "/email/**", "/rehabilitation","/rehabilitation/{course_id}", "/rehabilitation/problem/{category_id}",
"/boards", "/boards/{board_id}", "/boards/search", "/articles/{article_id}", "/comments/list/{article_id}","/auth/kakao/**").permitAll()
.requestMatchers("/swagger-ui/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/users/**").hasRole("USER")
.anyRequest().authenticated())
.addFilterBefore(new FilterExceptionHandler(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JWTFilter(jwtUtil), LoginFilter.class)
.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration)), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
계층을 등록할 때는 ROLE_
을 사용하지 않아도 Spring Security가 자동으로 접두사를 붙인다.
http.requestMatchers("/admin").hasRole("ADMIN")
이와 같이 role을 설정할 때 계층만 정의하면 된다.
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public JoinService(BCryptPasswordEncoder bCryptPasswordEncoder) {
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
bCryptPasswordEncoder.encode(password);
원래 Login Filter에서 사용자 인증을 진행하고, JWT 토큰과 Refresh token을 발급하려고 했지만, 로그인 response 데이터에 사용자 정보가 들어있어, 고민하다가 로그인 인증만 filter에서 처리하고 controller에서 token발급을 진행했다.
다 구현하고 보니... filter에서 모두 처리하는게 좋았을 듯하다.
프로젝트에서 로그인 url이 'general/login'이지만 filter의 default url은 '/login'이였다.
url을 커스텀하기 위해 생성자에 아래 코드를 추가했다.
super.setFilterProcessesUrl("/general/login");
아래 코드는 CustomUserDetailsSevice의 일부인데, 이렇게 데이터베이스에 접근하여 id, password의 유효성을 검사한다.
@Override
public UserDetails loadUserByUsername(String login_id) throws UsernameNotFoundException {
Member member = memberRepository.findByLoginIdAndWithdrawFalse(login_id);
if(member!=null){
return new CustomUserDetails(member);
}
return null;
}