🍀Spring/Security
스프링 시큐리티
말동말동현
2025. 4. 26. 15:16
스프링 실습을 하다 시큐리티부분이 궁금해져서 적어두게 되었다.
1. SecurityConfig 클래스
이 클래스는 Spring Security의 주요 설정을 담당합니다.
package com.shop.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf
.csrfTokenRepository(new CookieCsrfTokenRepository()) // csrf 토큰 저장소를 http only cookie로 설정
)
.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class)
.authorizeHttpRequests(authorizeHttpRequestsCustomizer -> authorizeHttpRequestsCustomizer
.requestMatchers("/css/**", "/js/**", "/img/**").permitAll()
.requestMatchers("/", "/members/**", "/item/**", "/images/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest()
.authenticated()
).formLogin(formLoginCustomizer -> formLoginCustomizer
.loginPage("/members/login")
.defaultSuccessUrl("/", true)
.usernameParameter("email")
.failureHandler(new FormLoginAuthenticationFailureHandler())
).logout( logoutCustomizer -> logoutCustomizer
.logoutRequestMatcher(new AntPathRequestMatcher("/members/logout"))
.logoutSuccessUrl("/")
).exceptionHandling(e -> e
.authenticationEntryPoint(new CustomAuthenticationEntryPoint())
)
.build()
;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
- @Configuration: Spring의 설정 클래스임을 나타냅니다.
- @EnableWebSecurity: Spring Security 기능을 활성화합니다.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
이 메서드는 Spring Security의 필터 체인을 구성합니다. 모든 보안 설정이 여기서 이루어집니다.
CSRF 설정
.csrf(csrf -> csrf
.csrfTokenRepository(new CookieCsrfTokenRepository()) // csrf 토큰 저장소를 http only cookie로 설정
)
.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class)
- CSRF(Cross-Site Request Forgery) 공격을 방지하기 위한 설정입니다.
- CookieCsrfTokenRepository를 사용하여 CSRF 토큰을 HTTP Only 쿠키에 저장합니다.
- CsrfCookieFilter를 BasicAuthenticationFilter 뒤에 추가하여 CSRF 토큰 처리를 합니다.
URL 접근 권한 설정
.authorizeHttpRequests(authorizeHttpRequestsCustomizer -> authorizeHttpRequestsCustomizer
.requestMatchers("/css/**", "/js/**", "/img/**").permitAll()
.requestMatchers("/", "/members/**", "/item/**", "/images/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest()
.authenticated()
)
- 정적 리소스와 특정 경로는 모든 사용자에게 공개됩니다 (permitAll()).
- /admin/** 경로는 ADMIN 역할을 가진 사용자만 접근 가능합니다.
- 그 외 모든 요청은 인증된 사용자만 접근 가능합니다.
로그인 설정
.formLogin(formLoginCustomizer -> formLoginCustomizer
.loginPage("/members/login")
.defaultSuccessUrl("/", true)
.usernameParameter("email")
.failureHandler(new FormLoginAuthenticationFailureHandler())
)
- 커스텀 로그인 페이지를 /members/login으로 지정합니다.
- 로그인 성공 시 메인 페이지(/)로 리다이렉트합니다.
- 사용자 이름 파라미터를 email로 설정합니다 (기본값은 username).
- 로그인 실패 처리를 위한 핸들러를 설정합니다.
로그아웃 설정
.logout( logoutCustomizer -> logoutCustomizer
.logoutRequestMatcher(new AntPathRequestMatcher("/members/logout"))
.logoutSuccessUrl("/")
)
- 로그아웃 URL을 /members/logout으로 설정합니다.
- 로그아웃 성공 시 메인 페이지로 리다이렉트합니다.
예외 처리 설정
.exceptionHandling(e -> e
.authenticationEntryPoint(new CustomAuthenticationEntryPoint())
)
- 인증되지 않은 사용자가 인증이 필요한 리소스에 접근할 때 처리 방식을 정의합니다.
패스워드 인코더 빈
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
- 비밀번호 암호화를 위한 BCrypt 알고리즘을 사용하는 인코더를 제공합니다.
2. CsrfCookieFilter 클래스
public class CsrfCookieFilter extends OncePerRequestFilter {
- OncePerRequestFilter를 상속하여 요청당 한 번만 실행되는 필터를 구현합니다.
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// HttpServletRequest에서 CsrfToken 속성 조회
CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrfToken != null) {
// csrf 토큰 강제 생성
csrfToken.getToken();
}
filterChain.doFilter(request, response);
}
- 요청에서 CSRF 토큰을 검색하고, 존재하면 getToken() 메서드를 호출하여 토큰을 강제로 생성합니다.
- 이렇게 함으로써 쿠키에 CSRF 토큰이 저장되도록 합니다.
- 필터 체인을 계속 진행하기 위해 filterChain.doFilter()를 호출합니다.
3. CustomAuthenticationEntryPoint 클래스
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
- AuthenticationEntryPoint 인터페이스를 구현하여 인증되지 않은 사용자가 보호된 리소스에 접근할 때의 동작을 정의합니다.
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
- 인증되지 않은 사용자가 보호된 리소스에 접근하면 401 Unauthorized 오류를 반환합니다.
- "Unauthorized" 메시지와 함께 HTTP 상태 코드 401을 응답으로 보냅니다.
4. FormLoginAuthenticationFailureHandler 클래스
public class FormLoginAuthenticationFailureHandler implements AuthenticationFailureHandler {
- AuthenticationFailureHandler 인터페이스를 구현하여 로그인 실패 시의 동작을 정의합니다.
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.sendRedirect("/members/login/error");
}
- 로그인 실패 시 /members/login/error 경로로 리다이렉트합니다.
- 실패 원인은 전달하지 않고 단순히 오류 페이지로 이동시킵니다.
이 설정들은 함께 작동하여 웹 애플리케이션의 보안을 관리하고, 사용자 인증과 권한 부여, CSRF 방지, 오류 처리 등의 기능을 제공합니다.