사용자가 애플리케이션에 로그인 요청을 보냅니다. 이 요청은 AuthenticationFilter
로 전달됩니다. 일반적으로 UsernamePasswordAuthenticationFilter
가 사용됩니다. 이 필터는 로그인 폼에서 입력된 사용자명과 비밀번호를 처리합니다.
AuthenticationFilter
는 사용자의 로그인 요청을 받아 UsernamePasswordAuthenticationToken
객체를 생성합니다. 이 객체는 사용자가 입력한 사용자명과 비밀번호를 포함합니다.
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
AuthenticationFilter
는 생성된 UsernamePasswordAuthenticationToken
객체를 AuthenticationManager
에게 전달합니다. AuthenticationManager
는 인증 요청을 처리하고, 인증 성공 여부를 결정합니다.
return this.getAuthenticationManager().authenticate(authRequest);
AuthenticationManager
는 여러 개의 AuthenticationProvider
를 관리합니다. 각각의 AuthenticationProvider
는 특정 인증 방식을 처리합니다. 여기서 UsernamePasswordAuthenticationToken
객체를 처리할 수 있는 AuthenticationProvider
로 전달합니다.
Authentication authResult = provider.authenticate(authentication);
AuthenticationProvider
는 사용자가 입력한 비밀번호를 인코딩(암호화)합니다. 이 과정에서 PasswordEncoder
가 사용됩니다. 스프링 시큐리티에서는 주로 BCryptPasswordEncoder
를 사용하여 비밀번호를 암호화합니다.
if (passwordEncoder.matches(rawPassword, userDetails.getPassword())) {
// 비밀번호가 일치하면
}
AuthenticationProvider
는 UserDetailsService
를 호출하여 데이터베이스에서 사용자 정보를 조회합니다. UserDetailsService
는 주로 사용자명(아이디)으로 사용자를 검색하여 사용자 정보를 반환합니다.
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UserDetailsService
는 데이터베이스에서 사용자를 조회한 후, 사용자 정보를 담은 UserDetails
객체를 반환합니다. 이 객체는 스프링 시큐리티가 요구하는 사용자 정보(아이디, 비밀번호, 권한 등)를 포함합니다.
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return new PrincipalDetails(user);
}
AuthenticationProvider
는 UserDetails
객체에서 반환된 사용자 정보와 사용자가 입력한 비밀번호를 비교합니다. 이 과정에서 비밀번호는 인코딩된 상태로 비교됩니다. BCryptPasswordEncoder
와 같은 인코더는 matches
메서드를 통해 원문 비밀번호와 인코딩된 비밀번호를 비교할 수 있습니다.
if (passwordEncoder.matches(rawPassword, userDetails.getPassword())) {
// 비밀번호가 일치하면 인증 성공
}
비밀번호 검증이 성공하면, AuthenticationProvider
는 인증이 성공한 Authentication
객체를 생성하여 AuthenticationManager
로 반환합니다. 이 객체는 사용자의 인증 정보를 담고 있습니다.
Authentication auth = new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
return auth;
AuthenticationManager
는 인증이 성공한 Authentication
객체를 AuthenticationFilter
로 반환합니다.
Authentication authResult = this.getAuthenticationManager().authenticate(authRequest);
AuthenticationFilter
는 인증이 성공한 Authentication
객체를 SecurityContext
에 저장합니다. SecurityContext
는 현재 인증된 사용자 정보를 담고 있는 컨텍스트입니다. 이를 통해 애플리케이션의 다른 부분에서 현재 사용자의 인증 정보를 참조할 수 있습니다.
SecurityContextHolder.getContext().setAuthentication(authResult);
attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
: 로그인 시도 메서드로, 사용자명과 비밀번호를 기반으로 Authentication
객체를 생성합니다.request.getParameter("username")
와 request.getParameter("password")
를 호출하여 사용자 입력을 읽어옵니다. 그 후, UsernamePasswordAuthenticationToken
객체를 생성하여 AuthenticationManager
에게 전달합니다.@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
// 요청에서 사용자명과 비밀번호를 가져옵니다.
String username = obtainUsername(request);
String password = obtainPassword(request);
// 사용자명과 비밀번호를 사용하여 Authentication 객체를 생성합니다.
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// AuthenticationManager에게 인증 요청을 전달합니다.
return this.getAuthenticationManager().authenticate(authRequest);
}
successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult)
: 인증 성공 시 호출됩니다. 인증 정보를 SecurityContext
에 저장하고, 성공 후 처리 작업을 수행합니다.SecurityContextHolder.getContext().setAuthentication(authResult)
를 호출하여 인증 정보를 저장합니다.@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) {
// 인증 성공 정보를 SecurityContext에 저장합니다.
SecurityContextHolder.getContext().setAuthentication(authResult);
// 다음 필터로 요청을 전달합니다.
chain.doFilter(request, response);
}
unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)
: 인증 실패 시 호출됩니다. 실패 원인을 처리하고, 실패 후 작업을 수행합니다.@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
// 인증 실패 시 SecurityContext를 지웁니다.
SecurityContextHolder.clearContext();
// 실패 상태 코드를 설정합니다.
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
// 실패 원인을 응답에 작성합니다.
response.getWriter().write("Authentication failed: " + failed.getMessage());
}
역할: 사용자가 입력한 사용자명과 비밀번호를 포함하는 객체입니다. 인증 요청 시 사용되며, 인증 성공 시 사용자 정보와 권한을 포함합니다.
생성자:
UsernamePasswordAuthenticationToken(Object principal, Object credentials)
: 인증 요청 시 사용됩니다. principal
은 사용자명, credentials
는 비밀번호를 의미합니다.UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities)
: 인증 성공 후 사용됩니다. principal
은 사용자 정보, credentials
는 비밀번호, authorities
는 사용자 권한을 의미합니다.예제:
// 인증 요청 시 사용되는 생성자 예제
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// 인증 성공 시 사용되는 생성자 예제
UsernamePasswordAuthenticationToken authSuccess = new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
AuthenticationProvider
를 관리하며, 각각의 AuthenticationProvider
는 특정 인증 방식을 처리합니다. 인증 요청을 적절한 AuthenticationProvider
에게 위임합니다.authenticate(Authentication authentication)
: 전달된 Authentication
객체를 인증합니다. 적절한 AuthenticationProvider
를 찾아 인증을 위임하고, 인증 결과를 반환합니다.AuthenticationProvider
를 순회하면서, supports
메서드를 사용하여 해당 Authentication
타입을 지원하는지 확인한 후, authenticate
메서드를 호출합니다.@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 등록된 모든 AuthenticationProvider를 순회합니다.
for (AuthenticationProvider provider : providers) {
// 해당 AuthenticationProvider가 전달된 Authentication 타입을 지원하는지 확인합니다.
if (provider.supports(authentication.getClass())) {
// 지원하는 경우, 해당 AuthenticationProvider에게 인증을 위임하고 결과를 반환합니다.
return provider.authenticate(authentication);
}
}
// 지원하는 AuthenticationProvider를 찾지 못한 경우 예외를 발생시킵니다.
throw new ProviderNotFoundException("No suitable AuthenticationProvider found");
}
AuthenticationManager
가 인증 요청을 위임하는 대상입니다.authenticate(Authentication authentication)
: 전달된 Authentication
객체를 검증합니다. 비밀번호를 검증하고, 사용자 정보를 조회하여 인증 결과를 반환합니다.UserDetailsService
를 사용하여 사용자 정보를 조회하고, PasswordEncoder
를 사용하여 비밀번호를 검증합니다. 성공하면 UsernamePasswordAuthenticationToken
을 반환합니다.@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 인증 요청에서 사용자명과 비밀번호를 가져옵니다.
String username = authentication.getName();
String password = (String) authentication.getCredentials();
// UserDetailsService를 사용하여 사용자 정보를 조회합니다.
UserDetails user = userDetailsService.loadUserByUsername(username);
// 비밀번호를 검증합니다.
if (passwordEncoder.matches(password, user.getPassword())) {
// 비밀번호가 일치하면 인증 성공 객체를 생성하여 반환합니다.
return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
} else {
// 비밀번호가 일치하지 않으면 예외를 발생시킵니다.
throw new BadCredentialsException("Invalid credentials");
}
}
supports(Class<?> authentication)
: 특정 Authentication
객체를 이 AuthenticationProvider
가 지원하는지 여부를 결정합니다.UsernamePasswordAuthenticationToken
인지 확인합니다.@Override
public boolean supports(Class<?> authentication) {
// 전달된 클래스 타입이 UsernamePasswordAuthenticationToken인지 확인합니다.
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
encode(CharSequence rawPassword)
: 원문 비밀번호를 암호화합니다.// 원문 비밀번호를 암호화합니다.
String encodedPassword = passwordEncoder.encode(rawPassword);
matches(CharSequence rawPassword, String encodedPassword)
: 원문 비밀번호와 암호화된 비밀번호가 일치하는지 확인합니다.// 원문 비밀번호와 암호화된 비밀번호가 일치하는지 확인합니다.
boolean isMatch = passwordEncoder.matches(rawPassword, encodedPassword);
UserDetails
객체를 반환합니다.loadUserByUsername(String username)
: 사용자명을 기반으로 사용자 정보를 조회하고, UserDetails
객체를 반환합니다. 사용자가 존재하지 않으면 UsernameNotFoundException
을 던집니다.UserRepository
등을 사용하여 데이터베이스에서 사용자 정보를 조회합니다.@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 사용자명을 사용하여 데이터베이스에서 사용자를 조회합니다.
User user = userRepository.findByUsername(username);
// 사용자가 존재하지 않으면 예외를 던집니다.
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
// 조회된 사용자 정보를 기반으로 UserDetails 객체를 생성하여 반환합니다.
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), user.getAuthorities());
}
getAuthorities()
: 사용자의 권한 목록을 반환합니다.@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 사용자의 권한 목록을 반환합니다.
return authorities;
}
getPassword()
: 사용자의 비밀번호를 반환합니다.@Override
public String getPassword() {
// 사용자의 비밀번호를 반환합니다.
return password;
}
getUsername()
: 사용자의 사용자명을 반환합니다.@Override
public String getUsername() {
// 사용자의 사용자명을 반환합니다.
return username;
}
isAccountNonExpired()
: 사용자의 계정이 만료되지 않았는지 여부를 반환합니다.@Override
public boolean isAccountNonExpired() {
// 사용자의 계정이 만료되지 않았는지 여부를 반환합니다.
return true;
}
isAccountNonLocked()
: 사용자의 계정이 잠기지 않았는지 여부를 반환합니다.@Override
public boolean isAccountNonLocked() {
// 사용자의 계정이 잠기지 않았는지 여부를 반환합니다.
return true;
}
isCredentialsNonExpired()
: 사용자의 자격 증명이 만료되지 않았는지 여부를 반환합니다.@Override
public boolean isCredentialsNonExpired() {
// 사용자의 자격 증명이 만료되지 않았는지 여부를 반환합니다.
return true;
}
isEnabled()
: 사용자의 계정이 활성화되었는지 여부를 반환합니다.@Override
public boolean isEnabled() {
// 사용자의 계정이 활성화되었는지 여부를 반환합니다.
return true;
}
SecurityContextHolder
: SecurityContext
객체를 저장하고 조회할 수 있는 정적 메서드를 제공합니다. 현재 스레드의 SecurityContext
를 관리합니다.SecurityContext
객체를 ThreadLocal
에 저장하여 스레드별로 인증 정보를 분리합니다.// 새로운 SecurityContext 객체를 생성합니다.
SecurityContext context = SecurityContextHolder.createEmptyContext();
// 인증 객체를 SecurityContext에 설정합니다.
context.setAuthentication(authentication);
// SecurityContextHolder에 SecurityContext를 설정합니다.
SecurityContextHolder.setContext(context);
## Authentication
Principal
과 Authorities
를 포함합니다.getAuthorities()
: 인증된 사용자의 권한 목록을 반환합니다.@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 인증된 사용자의 권한 목록을 반환합니다.
return authorities;
}
getCredentials()
: 인증에 사용된 자격 증명을 반환합니다.@Override
public Object getCredentials() {
// 인증에 사용된 자격 증명을 반환합니다.
return credentials;
}
getDetails()
: 추가적인 인증 세부 정보를 반환합니다.@Override
public Object getDetails() {
// 추가적인 인증 세부 정보를 반환합니다.
return details;
}
getPrincipal()
: 인증된 사용자 정보를 반환합니다.@Override
public Object getPrincipal() {
// 인증된 사용자 정보를 반환합니다.
return principal;
}
isAuthenticated()
: 사용자가 인증되었는지 여부를 반환합니다.@Override
public boolean isAuthenticated() {
// 사용자가 인증되었는지 여부를 반환합니다.
return authenticated;
}
setAuthenticated(boolean isAuthenticated)
: 인증 상태를 설정합니다.@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
// 인증 상태를 설정합니다.
this.authenticated = isAuthenticated;
}
이 과정을 통해 Spring Security는 사용자의 인증을 안전하게 처리하며, 인증된 사용자만 보호된 리소스에 접근할 수 있도록 보장합니다. 이를 통해 애플리케이션의 보안 수준을 크게 향상시킬 수 있습니다.
AuthenticationFilter
가 요청을 가로채고, UsernamePasswordAuthenticationToken
객체를 생성합니다.AuthenticationManager
에게 UsernamePasswordAuthenticationToken
객체를 전달합니다.AuthenticationManager
는 적절한 AuthenticationProvider
를 찾아 인증을 위임합니다.AuthenticationProvider
는 비밀번호를 인코딩하고, UserDetailsService
를 통해 사용자 정보를 조회합니다.UsernamePasswordAuthenticationToken
객체를 생성하여 반환합니다.AuthenticationManager
는 인증된 Authentication
객체를 AuthenticationFilter
로 반환합니다.AuthenticationFilter
는 인증 정보를 SecurityContext
에 저장합니다.