스프링 시큐리티회원 관리 인터페이스UserDetailsUserDetailsServiceUserDetailsManagerApplication Context에 userDetailsService() 구현체 등록AuthenticationAuthenticationProviderSecurityContext회원 로그인 인증 전체 프로세스 - 구현FilterAuthenticationEntryPointauthorizeHttpRequests().requestMatchers()
스프링 시큐리티
- Spring 기반 애플리케이션의 보안을 담당하는 프레임워크
- 인증과 인가를 담당
- 웹 클라이언트의 요청은 필터 → 인터셉터 → 디스패처 서블릿 → 컨트롤러 순으로 들어오는데, 스프링 시큐리티는 서블릿 필터 앞쪽 끝단에서 동작한다.
- 다양한 보안 관련 요구사항을 각각의 필터 형태로 애플리케이션에 삽입하고, 사용자의 요청을 가로채 필요한 보안적 역할을 수행하는 형식으로 동작한다.

- 사용자의 요청이 스프링 시큐리티 인증필터인 AuthenticationFilter에 가로채진다.
- UsernamePasswordAuthenticationToken : 추후 인증이 끝나고 SecurityContextHolder에 등록될 Authentication 객체
- AuthenticationFilter는 내부적으로 AuthenticationManager를 호출하여 사용자의 요청을 검증한다.
- AuthenticationManager는 다수의 AuthenticationProvider를 주입받아 해당 인증의 작업을 수행한다.
- 다양한 AuthenticationProvider 실행 , ex) UserDetailsService : 회원을 스프링 시큐리티가 사용할 수 있는 형태로 가져올 수 있음
- ex) UserDetails : UserDetailsService에 사용할 유저 정보를 UserDetails 인터페이스에 저장.
7, 8 ex) UserDetails를 AuthenticationProvider에 반환, AuthenticationManager에 주입
9, 10 인증에 성공하면, 해당 정보를 SecurityContextHolder 내부에 저장한다. 실패할 경우 Exception
회원 관리 인터페이스
UserDetails
- 스프링 시큐리티가 사용 가능한 회원의 형태를 기술한 인터페이스
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
- 해당 메서드들을 오버라이딩하여 사용한다.
- GrantedAuthority
- 특정 회원에게 인가할 권한 목록을 담는다. 다음과 같이 GrantedAuthority 인터페이스를 구현한다.
public class CustomGrantedAuthority implements GrantedAuthority {
private final String authority;
public CustomGrantedAuthority(String authority) {
this.authority = authority;
}
@Override
public String getAuthority() {
return authority;
}
}
UserDetailsService
- AuthenticationProvider가 회원 정보를 인증할 수 있도록 시스템상으로 회원 정보를 불러온다.
- 하나의(loadUserByUsername) 메서드를 갖는 인터페이스
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
- loadUserByUsername
- username을 받아 일치하는 회원이 있으면 해당 유저의 UserDetails를 반환
- 일치하는 회원이 없으면 UsernameNotFoundException을 AuthenticationProvider에게 전달
UserDetailsManager
- loadUserByUsername밖에 없는 UserDetailsService에 부족한 역할을 보안하기 위한 인터페이스
public interface UserDetailsManager extends UserDetailsService {
void createUser(UserDetails user);
void updateUser(UserDetails user);
void deleteUser(String username);
void changePassword(String oldPassword, String newPassword);
boolean userExists(String username);
}
Application Context에 userDetailsService() 구현체 등록
@Configuration
public class SecurityConfiguration {
@Bean
public UserDetailsServiceImpl userDetailsService() {
CustomUser tom = new CustomUser("tom","tom","1234","SUPER");
CustomUser richard = new CustomUser("richard","richard","1234","NORMAL");
return new UserDetailsServiceImpl(List.of(tom, richard));
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
- @Configuration 어노테이션으로 Application Context에 해당 클래스가 설정 파일임을 알려준다.
- → 스프링 컨테이너가 생성될 때 해당 클래스의 @Bean들이 컨테이너 내부에 등록된다.
- CustomUser은 UserDetails의 구현체이다.
- 스프링 시큐리티는 회원 비밀번호 데이터를 암호화하여 저장하기 때문에, PasswordEncoder를 지정해 주어야 한다.
- PasswordEncoder는 단방향 암호화로 복호화가 불가능하다.
Authentication
- 인증된 회원의 정보를 저장하는 인터페이스, Principal, Credential, Authority에 정보가 저장되어 있다.
- Principal : 정보의 주체로, 시스템에 접근하고 인증 허가된 회원/시스템 등을 일컫는다.
- Credential: 주체가 올바르다는 것을 증명할 수 있는 자격증명
- Authority: 주체에게 부여된 권한
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
AuthenticationProvider
- 회원 정보와 사용자의 요청을 비교해 사용자의 요청을 검증한다.
- AuthenticationManager는 다수의 AuthenticationProvider을 가질 수 있다.
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
boolean supports(Class<?> authentication);
}
- authenticat 메서드
- 회원의 정보와 사용자의 요청을 비교해 각각의 정보가 일치하는지 검증
- 일치 시 Authentication 객체에 회원 정보를 담아 return, 일치하지 않으면 AuthenticationException
- authenticate 메서드 내부에는 @Bean으로 등록된 UserDetailsService, PasswordEncoder가 사용됨
- supports 메서드
- 사용자의 요청이 자신이 수행하는 인증 검증과 일치하는지 조회
SecurityContext
- SecurityContext로 Authentication을 setter/getter 할 수 있다.
public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}
- SecurityContext는 데이터를 담는 일종의 상자로, AuthenticationProvider와 AuthenticaionManager에 의해 인증된 회원 정보를 저장하는 보관소이다.
- SecurityContextHloder
- SecurityContext를 담는 또 하나의 상자이다.
- Authentication → SecurityContext → SecurityContextHolder를 담는다.
- SecurityContextHloder 내부 다음과 같은 메서드들로 SecurityContext에 접근하거나 제어할 수 있다.
public static void clearContext() {
strategy.clearContext();
}
public static SecurityContext getContext() {
return strategy.getContext();
}
public static int getInitializeCount() {
return initializeCount;
}
private static void initialize() {
...
}
public static void setContext(SecurityContext context) {
strategy.setContext(context);
}
회원 로그인 인증 전체 프로세스 - 구현
- User와 User에 담을 Authority의 Entity를 생성한다.
- UserReposiotry, AuthorityRepository를 생성한다.(JpaRepository 상속)
- UserDetailsService를 상속받은 서비스를 생성한다.
- loadUserByUsername: UserDetails의 구현체인 CustomUserDetails를 반환
public class JpaUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findUserByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("조회 실패"));
return new CustomUserDetails(user);
}
}
- AuthenticationProvider를 생성한다.
- AuthenticationProvider는 UserDetailsService로 조회된 UserDetails 구현체를 이용해 실제 인증 작업을 수행한다.
- 사용자가 filterChain에
.formLogin
에 정의된 로그인 페이지에 접근 - 사용자는 아이디/패스워드를 입력하여 로그인 정보를 서버에 제출
.addFilterBefore({jwt 필터}, UsernamePasswordAuthenticationFilter.class)
사용 시 custom jwt 필터로 jwt 검증과정 진행, jwt 만료기한 check, refresh 등UsernamePasswordAuthenticationFilter
: 사용자의 로그인 인증 정보를 가로채서AuthenticationManager
에게 인증을 요청한다.AuthenticationManager
는AuthenticationProvider
에게 사용자 인증을 위임한다.AuthenticationProvider
는 실제 제공된 아이디/비밀번호를 기반으로 실제 인증을 수행한다. 데이터베이스의 회원 정보와 비교하는 과정이 진행된다.- 인증에 성공하면,
AuthenticationProvider
는UsernamePasswordAuthenticationToken
로Authentication
를 생성하여AuthenticationManager
로 반환한다. Authentication
가 성공적으로 반환되면,AuthenticationSuccessHandler
가 호출되어 로그인 성공 로직, 성공 페이지로 리다이렉트 등이 일어난다.Authentication
가 성공적으로 반환되지 않으면,AuthenticationFailureHandler
가 호출되어 로그인 실패 페이지로 리다이렉션 or 실패 메세지를 표시한다.- 로그인 성공 시, 사용자의 인증 정보는
SecurityContextHolder
에 저장되어 현제 세션동안 유지된다. - 사용자 인증이 완료되면, 권한 기반의 접근 제어가 적용되어 사용자가 특정 리소스에 접근할 수 있는지 여부를 결정한다.
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final JpaUserDetailsService jpaUserDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
CustomUserDetails customUserDetails = jpaUserDetailsService.loadUserByUsername(username);
return new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication);
}
}
회원 로그인 인증 전체 프로세스 - 내부 프로세스
Filter
- 서블릿 필터
- 클라이언트 요청이 스프링 컨테이너로 들어가기 전 단계에서 동작하는 객체 혹은 계층
- 일반적으로 인증, 인가와 같은 작업 수행
- javax.servlet의 filter 인터페이스를 구현함으로써 사용 가능
- filter 인터페이스는 다음 세 메서드가 존재한다.
- init : 필터 객체가 초기화 될 때 실행
- doFilter : 해당 필터가 스프링 컨테이너에 의해 호출될 때 실행
- destroy : 필터 객체가 종료될 때 실행
- Filter Chain
- 각각의 필터들은 필터 체인 내부의 순서에 따라 차곡차곡 보관되고, 클라이언트의 요청이 있을 때마다 필터 체인에 의해 순서대로 호출됨.
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response); // 호출된 필터는 반드시 필터 체인을 호출해 다음 필터를 실행해야 합니다.
}
AuthenticationEntryPoint

- AuthenticationEntryPoint 를 통해 필터에서 예외 처리를 할 수 있다.
- commence를 오버라이딩 한다.
public class CustomJwtEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
}
}
// SecurityConfig ..
private final CustomJwtEntryPoint customJwtEntryPoint;
http.addFilterBefore(new JwtAuthenticationFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class).
exceptionHandling(exceptionConfig -> exceptionConfig.authenticationEntryPoint(customJwtEntryPoint))
authorizeHttpRequests().requestMatchers()
- authorizeHttpRequests()
- HTTP 요청에 대한 인가 설정을 구성하는데 사용
- 인가 규칙 정의, 경로별로 다른 권한 설정 가능
- requestMatchers()
- 특정한 HTTP 요청 메서드를 적용할 수 있게 해줌.
Share article