spring security는 spring 진영에서 가장 유명한 인증, 인가 라이브러리 입니다. 그러나 자주 사용하는 도구임에도 정확한 동작 방식을 공부하고 정리해 본적은 없는 것 같아 시작하게 되었습니다.
위 그림을 토대로 로그인 요청이 들어왔을때 플로우는 아래와 같습니다.
이제 각 부분에 대해 좀 더 자세히 설명해드리겠습니다.
사용자의 인증 요청을 처리하는 핵심 컴포넌트로 사용자가 로그인 또는 인증을 위해 애플리케이션에 요청을 보낼 때, 해당 요청을 가로채고 인증 과정을 시작합니다.
요청 가로채기: 인증 관련 요청(예: 로그인)을 가로채고 인증 정보(예: 아이디, 비밀번호)를 처리합니다. 보통 특정 URL 패턴(예: /login)에 매핑됩니다.
Authentication 객체 생성: 사용자의 입력 데이터를 기반으로 Authentication 객체를 생성합니다.예: UsernamePasswordAuthenticationToken.
AuthenticationManager로 인증 위임:생성된 Authentication 객체를 AuthenticationManager에 전달하여 실제 인증 작업을 수행합니다.
성공 또는 실패 처리:인증 성공 시, AuthenticationSuccessHandler를 호출. 인증 실패 시, AuthenticationFailureHandler를 호출. 요청은 AuthenticationFilter에서 시작됩니다.
이 필터는 인증을 처리하는 역할을 합니다.
Spring Security에서 AuthenticationFilter는 일반적으로 AbstractAuthenticationProcessingFilter를 기반으로 구현됩니다.
public abstract class AbstractAuthenticationProcessingFilter
extends org.springframework.web.filter.GenericFilterBean
implements org.springframework.context.ApplicationEventPublisherAware, org.springframework.context.MessageSourceAware
AbstractAuthenticationProcessingFilter는 브라우저 기반의 Http 기반 인증에 대한 추상
클래스입니다. GenericFilterBean의 상속을 받고 있고, subclass로는 OAuth2LoginAuthenticationFilter, Saml2WebSsoAuthenticationFilter, UsernamePasswordAuthenticationFilter가 있습니다.
이 중에서 가장 많이 사용하는 필터는 아이디/비밀번호를 처리하는 필터와 jwt 토큰 인증을 처리하는 필터입니다.
요청 가로채기
인증 정보 추출
Authentication 객체 생성
AuthenticationManager로 전달
인증 결과 처리
Authentication 객체?
현재 인증된 사용자나 인증 요청 정보를 표현하는 데 사용되는 객체.주로 SecurityContext에 저장되어 애플리케이션 전역에서 인증 정보를 공유하는데 사용
AuthenticationManager는 인증을 책임지는 인터페이스로 인증 요청을 받아 적절한 인증 제공자(AuthenticationProvider)에게 위임하고, 성공 또는 실패 여부를 반환합니다.
인증 요청 처리
AuthenticationProvider와의 협업
인증 성공/실패 결과 반환
AuthenticationManager는 인터페이스로, 구현체를 통해 동작합니다. Spring Security에서 제공하는 기본 구현체는 ProviderManager입니다.
인증 요청 생성 : 사용자 요청에서 인증 정보를 추출하여 Authentication 객체를 생성합니다. 예: UsernamePasswordAuthenticationToken.
ProviderManager로 전달 : ProviderManager는 등록된 여러 AuthenticationProvider를 순차적으로 호출합니다. 각 AuthenticationProvider는 인증 정보의 타입을 확인하고, 처리할 수 있으면 인증을 수행합니다.
인증 결과 반환 :
인증 성공 -> 인증된 Authentication 객체 반환합니다.
인증 실패-> AuthenticationException 예외를 던집니다.
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
authenticationManger 코드를 작성하고 security 설정이 필요합니다
@Component
public class CustomAuthenticationManager implements AuthenticationManager {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
// 사용자 인증 로직
if ("user".equals(username) && "password".equals(password)) {
return new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>());
} else {
throw new BadCredentialsException("Invalid credentials");
}
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomAuthenticationManager customAuthenticationManager;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(customAuthenticationManager);
}
}
authenticationManger 코드 작성은 필요없고 security 설정만 해주면 됩니다.
in-memory
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user")
.password("{noop}password") // 비밀번호 암호화 사용 안 함
.roles("USER");
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
jdbc
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema()
.withUser("user")
.password("{noop}password")
.roles("USER");
}
}
여러 인증 제공자(AuthenticationProvider)를 조합하여 다양한 인증 방식 처리할 수 있습니다.
확장성이 뛰어나고, 실무에서 자주 사용됩니다.
@Bean
public AuthenticationManager authenticationManager() {
List<AuthenticationProvider> providers = Arrays.asList(
new DaoAuthenticationProvider(), // 기본 사용자 인증
new CustomAuthenticationProvider() // 커스텀 인증 방식
);
return new ProviderManager(providers);
}
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
// 커스텀 인증 로직 (예: 외부 서비스 호출)
if ("customUser".equals(username) && "customPass".equals(password)) {
return new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>());
} else {
throw new BadCredentialsException("Invalid custom credentials");
}
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
}
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
boolean supports(Class<?> authentication);
}
AuthenticationProvider는 실제 인증 로직을 처리하는 인터페이스로 AuthenticationManager로부터 호출됩니다. 이때 AuthenticationProvider는 특정 인증 방식(예: 아이디/비밀번호, JWT, OAuth2 등)에 대해 인증을 수행합니다.
인증 처리: 사용자 요청에서 전달된 Authentication 객체를 기반으로 인증을 수행합니다.
성공하면 인증된 Authentication 객체를 반환하고, 실패하면 예외를 던집니다.
특정 인증 방식 지원:AuthenticationProvider는 자신이 지원하는 인증 방식만 처리합니다.
지원 여부는 supports(Class<?> authentication) 메서드로 판단합니다.
다양한 인증 로직 구현:데이터베이스 인증, LDAP 인증, OAuth2 인증, JWT 토큰 검증 등 여러 인증 방식을 구현할 수 있습니다.
AuthenticationProvider는 다음 두 가지 메서드를 제공합니다
authenticate(Authentication authentication)
supports(Class<?> authentication)
데이터베이스 기반 인증을 처리하는 가장 일반적인 구현체입니다.
특징
구성 요소
사용법
설정
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // 비밀번호 암호화 방식 설정
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider(UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder);
return provider;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider(userDetailsService(), passwordEncoder()));
}
}
UserDetailsService
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
Collections.singletonList(new SimpleGrantedAuthority(user.getRole()))
);
}
}
이미 인증된 사용자를 처리하기 위한 AuthenticationProvider입니다. 주로 SSO(Single Sign-On)와 같은 외부 인증 시스템과 통합할 때 사용됩니다.
특징
구성 요소
1. PreAuthenticatedUserDetailsService: 인증된 사용자에 대한 세부 정보를 로드합니다.
사용법
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PreAuthenticatedAuthenticationProvider preAuthenticatedAuthenticationProvider() {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(userDetailsServiceWrapper());
return provider;
}
@Bean
public AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> userDetailsServiceWrapper() {
return token -> {
// 외부 시스템에서 인증된 사용자 정보 로드
String username = token.getName();
return new User(username, "", List.of(new SimpleGrantedAuthority("ROLE_USER")));
};
}
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(preAuthenticatedAuthenticationProvider());
}
}
익명 사용자를 처리하기 위한 AuthenticationProvider입니다.
특징
구성 요소
사용법
AnonymousAuthenticationProvider는 Spring Security가 기본적으로 등록하므로, 추가 설정이 필요하지 않습니다. 따라서, 익명 사용자를 활성화하려면 Security 설정에서 .anonymous()를 명시하면 됩니다.
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public").permitAll()
.anyRequest().authenticated()
.and()
.anonymous() // 익명 사용자 활성화
.and()
.formLogin();
}
}
"Remember Me" 기능을 처리하는 AuthenticationProvider입니다.
특징
구성 요소
RememberMeServices: Remember Me 쿠키를 생성하고, 인증 정보를 유지합니다.
키: Remember Me 기능은 고유한 키를 사용하여 인증 상태를 검증합니다.
사용법
Security 설정에서 Remember Me 활성화
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.rememberMe()
.key("uniqueAndSecret") // Remember Me를 위한 고유 키
.tokenValiditySeconds(86400); // 쿠키 유효 시간
}
}
Custom AuthenticationProvider로 기본 AuthenticationProvider가 아니라 요구사항에 맞는 맞춤형 인증 로직을 구현할 수 있습니다.
특징
특수 인증 로직 구현
Spring Security와의 통합
유연성
구현
AuthenticationProvider 인터페이스를 구현하고, 인증 로직을 authenticate 메서드에 정의합니다.
이때 주의해야 할 점으로
1. authenticate 메서드에서 인증 로직 정의, 인증 성공/실패를 명확히 처리해야하고
2. supports 메서드에서 처리 가능한 인증 객체 타입을 지정해야 합니다.
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
boolean supports(Class<?> authentication);
}
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 인증 요청에서 사용자 이름과 비밀번호를 가져옴
String username = authentication.getName();
String password = (String) authentication.getCredentials();
// 사용자 인증 로직 (예: 외부 API 호출, DB 검증 등)
if ("customUser".equals(username) && "customPassword".equals(password)) {
// 인증 성공: 인증된 Authentication 객체 반환
return new UsernamePasswordAuthenticationToken(username, password, new ArrayList<>());
} else {
// 인증 실패: 예외 던짐
throw new BadCredentialsException("Invalid credentials");
}
}
@Override
public boolean supports(Class<?> authentication) {
// 처리 가능한 Authentication 객체 타입 정의
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
@Component
public class ApiAuthenticationProvider implements AuthenticationProvider {
@Autowired
private ExternalAuthService externalAuthService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = (String) authentication.getCredentials();
// 외부 API 호출을 통해 사용자 인증
if (externalAuthService.authenticate(username, password)) {
// 인증 성공: 권한 정보 추가
List<GrantedAuthority> authorities = List.of(new SimpleGrantedAuthority("ROLE_USER"));
return new UsernamePasswordAuthenticationToken(username, password, authorities);
} else {
// 인증 실패
throw new BadCredentialsException("Invalid credentials from external service");
}
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
@Autowired
private JwtTokenService jwtTokenService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String token = (String) authentication.getCredentials();
// JWT 토큰 검증
if (jwtTokenService.validateToken(token)) {
// 토큰에서 사용자 정보 추출
String username = jwtTokenService.getUsernameFromToken(token);
List<GrantedAuthority> authorities = jwtTokenService.getAuthoritiesFromToken(token);
// 인증 성공: 인증된 Authentication 객체 반환
return new UsernamePasswordAuthenticationToken(username, token, authorities);
} else {
// 인증 실패
throw new BadCredentialsException("Invalid JWT token");
}
}
@Override
public boolean supports(Class<?> authentication) {
return JwtAuthenticationToken.class.isAssignableFrom(authentication);
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
@Autowired
private ApiAuthenticationProvider apiAuthenticationProvider;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider)
.authenticationProvider(apiAuthenticationProvider);
}
}
@Bean
public AuthenticationManager authenticationManager() {
List<AuthenticationProvider> providers = List.of(
new CustomAuthenticationProvider(),
new ApiAuthenticationProvider()
);
return new ProviderManager(providers);
}
사용자의 인증 정보(아이디, 비밀번호, 권한 등)를 제공하는 핵심 인터페이스입니다. UserDetailsService는 주로 데이터베이스에서 사용자의 세부 정보를 가져오고, 이를 Spring Security의 인증 처리 과정에서 활용합니다.
사용자 정보 로드
인증 과정 지원
권한 제공
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
public interface UserDetails {
Collection<? extends GrantedAuthority> getAuthorities(); // 권한 정보
String getPassword(); // 비밀번호
String getUsername(); // 사용자 이름
boolean isAccountNonExpired(); // 계정 만료 여부
boolean isAccountNonLocked(); // 계정 잠금 여부
boolean isCredentialsNonExpired(); // 비밀번호 만료 여부
boolean isEnabled(); // 계정 활성화 여부
}
UserDetailsService는 직접 구현해야 하며, 데이터베이스 또는 외부 서비스에서 사용자 정보를 가져오는 로직을 정의합니다.
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository; // JPA 기반 사용자 저장소
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 데이터베이스에서 사용자 검색
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
// UserDetails 객체 반환
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
getAuthorities(user)
);
}
// 권한 설정 메서드
private Collection<? extends GrantedAuthority> getAuthorities(User user) {
return user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
}
}
Security 설정
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // 비밀번호 암호화
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService) // UserDetailsService 등록
.passwordEncoder(passwordEncoder()); // 비밀번호 암호화 사용
}
}
사용자 ID, 이메일, 전화번호, 활성화 상태와 같은 추가 필드가 필요하다면 custom user details를 정의해서 사용할 수 있습니다.
public class CustomUserDetails implements UserDetails {
private String username; // 사용자 이름
private String password; // 비밀번호
private String email; // 이메일 (추가 필드)
private boolean enabled; // 활성화 여부
private List<GrantedAuthority> authorities; // 권한
//생성자
//필수메서드
}
이 경우에 Custom UserDetailsService에서 CustomUserDetails 객체를 반환하도록 구현합니다.
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 데이터베이스에서 사용자 조회
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
// CustomUserDetails 객체로 변환
return new CustomUserDetails(
user.getUsername(),
user.getPassword(),
user.getEmail(),
user.isEnabled(),
user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList())
);
}
}
Spring Security의 SecurityContextHolder를 통해 CustomUserDetails 정보를 가져올 수 있습니다.
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/me")
public ResponseEntity<?> getCurrentUser() {
// 현재 인증된 사용자 정보 가져오기
CustomUserDetails userDetails = (CustomUserDetails) SecurityContextHolder
.getContext()
.getAuthentication()
.getPrincipal();
return ResponseEntity.ok(Map.of(
"username", userDetails.getUsername(),
"email", userDetails.getEmail(),
"roles", userDetails.getAuthorities()
));
}
}
현재 실행 중인 스레드의 보안 컨텍스트(Security Context)를 저장하고 관리하는 핵심 클래스로 보안 컨텍스트에는 사용자의 인증 정보와 권한이 저장되며, Spring Security의 인증 및 권한 관리를 위해 사용됩니다.
인증이 성공하면, 인증된 Authentication 객체는 SecurityContextHolder에 저장되고 이 컨텍스트는 현재 요청에 대한 인증 정보를 유지하며, 이후 요청에서도 사용됩니다.
SecurityContext
public interface SecurityContext {
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}
Authentication
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
public class UserService {
public String getCurrentUsername() {
// SecurityContext에서 Authentication 객체 가져오기
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return null; // 인증되지 않은 경우
}
// 사용자 이름 반환
return authentication.getName();
}
public UserDetails getCurrentUserDetails() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof UserDetails) {
return (UserDetails) authentication.getPrincipal(); // 사용자 정보 반환
}
return null;
}
}
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
public class AuthenticationUtil {
public static void setAuthentication(String username, String role) {
// 인증 객체 생성
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
username,
null,
List.of(new SimpleGrantedAuthority(role))
);
// SecurityContext에 설정
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
SecurityContextHolder는 기본적으로 ThreadLocal을 사용하여 SecurityContext를 관리합니다.
MODE_THREADLOCAL (기본값)
각 스레드에서 독립적으로 SecurityContext를 저장합니다.
동시성 문제가 없도록 설계되어 있으며, 대부분의 애플리케이션에서 사용합니다.
MODE_INHERITABLETHREADLOCAL
부모 스레드의 SecurityContext를 자식 스레드로 전달합니다.
자식 스레드에서 부모 스레드의 인증 정보를 활용하는 경우에 사용합니다.
MODE_GLOBAL
애플리케이션 전체에서 하나의 SecurityContext를 공유합니다.
여러 사용자가 동시에 요청하는 애플리케이션에서는 사용하지 않는 것이 좋습니다.
SecurityContextHolder.clearContext();
HTTP 요청을 가로채고, 인증(Authentication) 및 인가(Authorization) 관련 작업을 수행하는 일련의 필터(필터 체인)입니다. 모든 클라이언트 요청은 Spring Security의 Filter Chain을 통과하며, 보안 관련 로직은 이 체인을 통해 처리됩니다.
인증이 완료되면 요청은 다음 필터로 전달됩니다.
이후 필터에서 추가적인 작업(예: 권한 검사)을 수행하거나 최종적으로 Spring Controller로 요청이 전달됩니다.
요청 가로채기
HTTP 요청이 컨트롤러에 도달하기 전에 Filter Chain에서 가로채어 보안 작업을 수행합니다.
순차적 처리
요청은 등록된 필터들을 순서대로 통과하며, 각 필터는 특정 보안 작업(예: 인증, 권한 검사)을 담당합니다.
유연한 확장성
기본 제공 필터를 커스터마이징하거나, 필요에 따라 사용자 정의 필터를 추가할 수 있습니다.
📢ConcurrentSessionFilter와 SessionManagementFilter의 차이점
ConcurrentSessionFilter : 이미 로그인한 상태에서 동작하며, 사용자가 동시에 여러 세션(동시 로그인)을 가질 수 없도록 제한, 만약 사용자가 다른 곳에서 동일한 계정으로 로그인하면, 현재 세션은 만료 처리
SessionManagementFilter: 로그인 시점에 동작하며, 세션과 관련된 보안 작업을 수행, 로그인 후 새 세션을 만들거나 기존 세션을 재사용정리
ConcurrentSessionFilter: "동시 로그인 제한을 통해, 한 계정으로 여러 곳에서 로그인하지 못하게 막아!"
[사용자 A - 컴퓨터 로그인] → OK [사용자 A - 스마트폰 로그인] → 컴퓨터 세션 만료
SessionManagementFilter: "로그인할 때 새로운 세션을 만들어 안전하게 관리하자!"
[로그인 전: 기존 세션] → 로그인 → [새로운 세션 생성]
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll() // 공개 리소스
.anyRequest().authenticated() // 나머지 요청은 인증 필요
.and()
.formLogin() // 기본 로그인 폼 활성화
.and()
.logout(); // 로그아웃 활성화
}
}
@Component
public class CustomAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 요청 헤더에서 사용자 정의 인증 로직 처리
String customHeader = request.getHeader("X-Custom-Auth");
if (customHeader != null && customHeader.equals("valid-token")) {
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("customUser", null, List.of(new SimpleGrantedAuthority("ROLE_USER")))
);
}
filterChain.doFilter(request, response); // 다음 필터로 요청 전달
}
}
Custom Filter 등록
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomAuthenticationFilter customAuthenticationFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(customAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // 커스텀 필터 추가
.authorizeRequests()
.anyRequest().authenticated();
}
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new CustomFilter()); // 특정 URL에만 적용 가능
}
https://www.elancer.co.kr/blog/detail/235
https://velog.io/@on5949/SpringSecurity-Authentication-%EA%B3%BC%EC%A0%95-%EC%A0%95%EB%A6%AC
https://ohtaeg.tistory.com/8
https://gngsn.tistory.com/160
https://juran-devblog.tistory.com/240