SpringBoot 3.x 스프링 시큐리티 (Spring security)
1. 스프링 시큐리티 (Spring security)
(1) 인증 / 인가 차이
인증(Authentication) 은 사용자가 본인의 신원을 입증하는 과정이다. 예를 들면 어떤 사이트에 아이디와 비밀번호를 입력하고 로그인 하는 과정이 있다.
인가(Authorization)은 사용자의 권한을 확인하는 작업이다. 쉽게 말해 파일 공유 시스템에서 권한 별로 접근할 수 있는 폴더가 다르다. 상위 권한을 가진 사람은 상위 폴더에 접근이 가능하고, 하위 권한을 가진 사람은 하위 폴더에만 접근이 가능하다. 이럴 경우 사용자의 권한을 확인해야 하는데 이 과정을 인가라고 한다.
(2) 스프링 시큐리티
스프링 기반 애플리케이션의 보안을 담당하는 스프링 하위 프레임워크이다. 보안 관련 옵션을 제공해주며 복잡한 로직 없이 어노테이션으로 설정 가능하다. 기본적으로 스프링 시큐리티는 세션 기반 인증을 제공한다.
스프링 시큐리티는 필터 기반으로 동작한다.
각 필터에서 인증, 인가와 관련된 작업을 처리하며, SecurityContextPersistenceFilter 부터 아래로 내려가며 FilterSecurityInterceptor까지 순서대로 필터를 거친다.
어피치 스티커가 붙어있는 UsernamePasswordAuthenticationFilter 는 아이디와 패스워드가 넘어오면 인증 요청을 위임하는 인증 관리자 역할을 한다.
프로도 스티커가 붙어있는 FilterSecurityInterceptor는 권한 부여 처리를 위임해 접근 제어 결정을 쉽게 하는 접근 결정 관리자 역할을 한다.
<필터 설명>
필터명 | 설명 |
SecurityContextPersistenceFilter | SecurityContextRepository에서 SecurityContext(접근 주체와 인증에 대한 정보를 담고 있는 객체) 를 가져오거나 저장하는 역할 |
LogoutFilter | 설정된 로그아웃 URL로 오는 요청을 확인해 해당 사용자를 로그아웃 처리함 |
UsernamePasswordAuthenticationFilter | 인증관리자 폼 기반 로그인 시 사용되는 필터로 아이디 패스워드 데이터를 파싱하여 인증 요청을 위임 인증이 성공하면 AuthenticationSuccessHandler, 실패하면 AuthenticationFailureHandler를 실행 |
DefaultLoginPageGeneratingFilter | 사용자가 로그인 페이지를 지정하지 않았을 때 기본으로 설정하는 로그인 페이지 관련 필터 |
BasicAuthenticationFilter | 요청 헤더에 있는 아이디와 패스워드를 파싱해 인증 요청 위임 인증이 성공하면 AuthenticationSuccessHandler, 실패하면 AuthenticationFailureHandler를 실행 |
RequestCacheAwareFilter | 로그인 성공 후, 관련 있는 캐시 요청이 있는지 확인하고 캐시 요청 처리 |
SecurityContextHolderAwareRequestFilter | HttpServletRequest 정보를 감싼다. 필터 체인 상의 다음 필터들에게 부가 정보 제공 |
AnonymousAuthenticationFilter | 필터가 호출되는 시점까지 인증되지 않았다면 익명 사용자 전용 객체인 AnonymousAuthentication을 만들어 SecurityContext에 넣어준다 |
SessionManagementFilter | 인증된 사용자와 관련된 세션 작업 진행, 세션 변조 방지 전략 설정, 유효하지 않은 세션에 대한 처리, 세션 생성 전략을 세우는 등의 작업을 처리한다 |
ExceptionTranslationFilter | 요청을 처리하는 중에 발생할 수 있는 예외를 위임하거나 전달 |
FilterSecurityInterceptor | 접근 결정 관리자 AccessDecisionManager로 권한 부여 처리를 위임함으로써 접근 제어 결정을 쉽게 해준다. 이 과정에서 이미 사용자 인증이 되어있으므로 유효한 사용자인지도 알 수 있음. 인가 관련 설정 가능 |
2. 스프링 시큐리티 인증 처리 과정
- 사용자가 form에 아이디, 패스워드를 입력하면 HTTPServletRequest에 아이디, 패스워드 정보가 전달된다. 이때 AuthenticationFilter가 넘어온 아이디와 패스워드의 유효성 검사를 실시한다.
- 유효성 검사 후 실제 구현체인 UsernamePasswordAuthenticationToken을 만들어 넘겨준다.
- 인증용 객체인 UsernamePasswordAuthenticationToken을 AuthenticationManager에게 전달한다.
- UsernamePasswordAuthenticationToken 을 AuthenticationProvider에게 전달한다.
- 사용자 아이디를 UserDetailService로 보낸다. UserDetailService는 사용자 아이디로 찾은 사용자 정보를 UserDetails 객체로 만들어 AuthenticationProvider에게 전달한다.
- 입력 정보와 UserDetails의 정보를 비교하여 실제 인증 처리를 진행한다.
- ~⑩ 까지 인증이 완료되면, SecurityContextHolder에 Authentication을 저장한다. 인증 성공 여부에 따라 성공 시 AuthenticationSuccessHandler, 실패 시 AuthenticationFailureHandler 핸들러를 실행한다.
3. 스프링부트 3.x 에 스프링 시큐리티 적용하기
(1) 의존성 추가하기
build.gradle dependencies 에 스프링 시큐리티 관련 의존성을 추가한다.
// 스프링 시큐리티를 사용하기 위한 스타터 추가
implement 'org.springframework.boot:spring-boot-starter-security'
// 타임리프에서 스프링 시큐리티를 사용하기 위한 의존성 추가
implement 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
// 스프링 시큐리티를 테스트하기 위한 의존성 추가
testImplementation 'org.springframework.security:spring-security-test'
(2) 회원 도메인(Entity) 생성
UserDetails 클래스를 상속하는 도메인 클래스 생성
UserDetails 클래스는 스프링 시큐리티에서 사용자의 인증 정보를 담아 두는 인터페이스이므로 필수 오버라이드 메서드가 많다.
메서드 | 설명 |
getAuthorities() | 사용자가 가지고 있는 권한 목록 반환 |
getUsername() | 사용자가 식별할 수 있는 사용자 이름 반환 사용되는 값은 반드시 고유해야 한다. |
getPassword() | 사용자의 비밀번호 반환 반드시 암호화해서 저장해야 한다. |
isAccountNonExpired() | 계정이 만료되었는지 확인. 만료되지 않았을 경우 true 반환 |
isAccountNonLocked() | 계정이 잠금되었는지 호가인. 잠금되지 않았을 경우 true 반환 |
isCredentialIsNonExpired() | 비밀번호가 만료되었는지 확인 만료되지 않았을 경우 true 반환 |
isEnabled() | 계정이 사용 가능한지 확인 사용 가능할 경우 true 반환 |
(3) Service 생성
UserDetailsService 인터페이스를 구현하고, loadUserByUsername() 메서드를 오버라이딩해서 사용자 정보를 가져오는 로직을 작성한다.
WebSecurityConfig
@RequiredArgsConstructor
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
@Configuration
public class WebSecurityConfig {
private final UserDetailService userService;
//스프링 시큐리티 기능 비활성화
@Bean
public WebSecurityCustomizer configure() {
return (web) -> web.ignoring()
.requestMatchers(toH2Console())
.requestMatchers("/static/**");
}
// 특정 HTTP 요청에 대한 웹 기반 보안 구성
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests()
.requestMatchers("/login", "/signup", "/user").permitAll()
.anyRequest().authenticated()
.and()
.formLogin() // 폼 기반 로그인 설정
.loginPage("/login")
.defaultSuccessUrl("/home")
.and()
.logout() // 로그아웃 설정
.logoutSuccessUrl("/login")
.invalidatedHttpSession(true)
.and()
.csrf().disable() // csrf 비활성화
.build();
}
// 인증 관리자 관련 설정
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() throws Exception {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userService);
daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
return daoAuthenticationProvider;
}
// 패스워드 인코더로 사용할 빈 등록
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
- configure() : 스프링 시큐리티의 모든 기능(인증, 인가)를 사용하지 않도록 설정
- requestMatchers() : 특정 요청과 일치하는 url에 대한 액세스 설정
- ignoring() : requestMathcers()에 적힌 url에 대한 인증, 인가 서비스를 적용하지 않음
- filterChain() : 특정 HTTP 요청에 대해 웹 기반 보안 구성, 인증/인가/로그인/로그아웃 설정
- permitAll() : 누구나 접근 가능. requestMatchers()에 기재된 url은 인증, 인가 없이도 접근 가능
- anyRequest() : 해당 코드 윗 줄에서 설정한 url 이외의 요청에 대해 설정
- authenticated() : 인가는 필요하지 않지만 인증 필요
- loginPage() : 로그인 페이지 설정
- defaultSuccessUrl() : 로그인 성공 시 이동할 경로
- logoutSuccessUrl() : 로그아웃 성공 시 이동할 경로
- invalidatedHttpSession() : 로그아웃 이후에 세션 전체 삭제 여부
- csrf().disable() : CSRF 설정 비활성화, 원래는 CSRF 공격을 방지하기 위해 활성화하는게 좋음
- daoAuthenticationProvider() : 인증 관리자 설정. 사용자 정보를 가져올 서비스를 재정의하거나, 인증 방법 등을 설정
- setUserDetailsService() : 사용자 정보를 가져올 서비스 설정, 이때 설정하는 클래스는 반드시 UserDetailsService를 상속받은 클래스여야 한다.
- setPasswordEncoder() : 비밀번호 암호화를 위한 인코더 설정
- bCryptPasswordEncoder() : 비밀번호 암호화를 위한 빈 등록