Spring Security의 인증 처리 흐름
사용자는 자신의 신원(Credentials)을 포함한 요청으로 로그인을 요청한다.
Spring Security의 여러 Filter Chain 중 가장 먼저 UsernamePasswordAuthencticationFilter
가 실행되고 아직 인증되지 않은 Authentication
정보를 생성한다.
AuthenicationManager
는 생성된 Authentication
정보를 토대로 인증 절차를 진행한다.
AuthenticationProvider
는 UserDetailsService
를 통해 UserDetails
를 조회한다.
조회한 UserDetails
를 전달 받고 PasswordEncoder
를 통해 UserDetails
의 암호화된 Password와 인증을 위한 Authentication
안에 포함된 Password가 일치하는지 검증한다.
5.1 검증에 실패하면 예외를 발생하고 인증 처리가 중단된다.
검증에 성공하면 인증이 된 UserDetails
를 Authentication
으로 변환하여 생성한다.
ProviderManager
와 UsernamePasswordAuthenticationFilter
를 거쳐 SecurityContext
에 인증된 인증을 저장한다.
최종적으로 SecurityContext
에는 Principal과 Credentials, 권한과 관련된 부분들이 저장된다.
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
//...
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
username = (username != null) ? username.trim() : "";
String password = obtainPassword(request);
password = (password != null) ? password : "";
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
password);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
//...
}
클라이언트로부터 전달 받은 Credential을 Spring Security가 인증 프로세스에서 이용할 수 있도록 UsernamePasswordAuthenticationToken
의 unauthenticated(username, password)
메서드로 토큰을 생성하는 역할을 한다.
클래스 이름은 Filter이지만, 실질적인 Filter의 역할은 AbstractAuthenticationProcessingFilter
에서 진행된다.
if (this.postOnly && !request.getMethod().equals("POST"))
해당 조건문 때문에 HTTP Method가 POST인 경우에만 처리되며, 그렇지 않으면 예외를 던진다.
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
}
HTTP 기반의 인증 요청을 처리하지만 실질적인 인증 시도는 하위 클래스에게 맡기고, 인증에 성공하면 인증된 사용자의 정보를 SecurityContext
에 저장하는 역할을 한다.
doFilter()
메서드를 가지고 있어 사용자의 로그인 요청을 가장 먼저 전달받는다.
public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
스프링 시큐리티에서 인증을 표현하는 인터페이스이다.
Principal
사용자를 식별하는 고유 정보
일반적으로는 Username
이, 다른 인증 방식에서는 UserDetails
가 고유정보가 된다.
Credentials
사용자 인증에 필요한 정보
인증 이후, ProviderManager
에 의해 삭제된다.
Authorities
AuthenticationProvider
에 의해 부여된 사용자의 접근 권한 목록
일반적으로 GrantedAuthority
인터페이스의 구현 클래스로 SimpleGrantedAuthority
를 사용한다.
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
인증 처리를 총괄하는 매니저 역할을 하는 인터페이스이다.
Filter는 AuthenticationManager
를 통해 느슨한 결합을 유지하며, 인증을 위한 실질적인 관리는 구현 클래스를 통해 이뤄진다.
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
//...
private List<AuthenticationProvider> providers = Collections.emptyList();
private AuthenticationManager parent;
//...
}
AuthenticationManager
를 구현하는 클래스이다.
스프링 시큐리티가 생성하고 등록하는 스프링 빈이므로 직접 구현할 필요는 없다.
AuthenticationProvider
를 관리하고, AuthenticationProvider
에게 인증 처리를 위임하는 역할을 한다.
인증에 성공하는 경우 AuthenticationManager
인터페이스의 메서드인 authenticate()
메서드의 반환 값으로 Authentication
객체 안에 인증 값을 넣게 된다.
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
boolean supports(Class<?> authentication);
}
AuthenticationManager
로부터 인증 처리를 위임받아 실질적인 인증 수행을 담당하는 컴포넌트이다.
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
데이터베이스 등의 저장소에 저장된 사용자의 Username과 사용자의 자격을 증명하는 Credential, 사용자의 권한 정보를 포함하는 컴포넌트이다.
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
loadUserByUsername()
으로 UserDetails를 로드하는 핵심 인터페이스이다.
public class SecurityContextHolder {
//...
// SecurityContext에 연결하는 객체
private static SecurityContextHolderStrategy strategy;
//...
}
Authentication 객체를 저장하는 컴포넌트이다.
SecurityContext를 관리하는 역할을 담당
Spring Security는 SecurityContextHolder
에 의해 SecurityContext
의 값이 채워져 있다면 인증된 사용자로 간주한다.
Spring Security의 권한 부여 처리 흐름
인증된 사용자임을 확인하는 경우, 인가된 사용자인지 사용자의 권한을 확인하는 처리가 필요하다.
Filter를 통해 인증 처리가 성공한 경우 AuthorizationFilter를 실행한다.
인증된 사용자 정보를 통해 권한을 얻는다.
AuthorizationFilter에 있는 AuthorizationManager에서 권한에 대한 처리를 담당한다.
URL을 통해 사용자의 액세스를 제한하는 권한 부여 Filter이며, Spring Security 5.5 버전부터 FilterSecurityInterceptor
를 대체
권한 부여 처리를 총괄하는 매니저 역할을 하는 인터페이스
AuthorizationManager
의 구현 클래스 중 하나이며, 직접 권한 부여 처리를 수행하지 않고 RequestMatcher
를 통해 매치되는 AuthorizationManager
구현 클래스에게 권한 부여 처리를 위임
RequestMatcher
는 SecurityConfiguration
에서 .antMatchers("/orders/**").hasRole("ADMIN")
와 같은 메서드 체인 정보를 기반으로 생성
접근 제어 표현식
httpSecurity
객체에서
httpSecurity
.authorizeHttpRequests(authorize -> authorize
.antMatchers("/**").permitAll());
antMatchers()
처럼 authorize.
으로 사용할 수 있다.
표현식 | 설명 |
---|---|
hasRole(Stirng role) | - 현재 보안 주체(principal)가 지정된 역할을 가지는지 확인하고 가지고 있으면 true를 반환 - hasRole(’admin’) 처럼 파라미터로 넘긴 role 이ROLE_ 로 시작하지 않으면 기본적으로 추가 - DefaultWebSecurityExpressionHandler의 defaultRolePrefix를 수정하면 커스텀 가능 |
hasAnyRole(String… roles) | - 현재 보안 주체가 지정한 역할을 하나라도 갖고 있다면 true 반환 - 문자열 리스트를 콤마로 구분해서 전달한다. ex) hasAnyRole(’admin’, ‘user’) |
hasAuthority(String authority) | - 현재 보안 주체가 지정한 권한을 가지는지 확인하고 가지고 있으면 true 반환 ex) hasAuthority(’read’) |
hasAnyAuthority(String… authorities) | - 현재 보안 주체가 지정한 권한 중 하나라도 있으면 true 반환 ex) hasAnyAuthority(’read’, ‘write’) |
principal | - 현재 사용자를 나타내는 principal 객체에 직접 접근 가능 |
authentication | - SecurityContext 로 조회할 수 있는 현재 Authentication 객체에 직접 접근 가능 |
permitAll | - 항상 true로 평가 |
denyAll | - 항상 false로 평가 |
isAnonymous() | - 현재 보안 주체가 익명 사용자면 true 반환 |
isRememberMe() | - 현재 보안 주체가 remember-me 사용자면 true 반환 |
isAuthenticated() | - 사용자가 익명이 아닌 경우 true 반환 |
isFullyAuthenticated() | - 사용자가 익명 사용자, remember-me 사용자가 아니면 true 반환 |
hasPermission(Object target, Object permission) | 사용자가 target에 해당 permission 권한이 있으면 true 반환 ex) hasPermission(domainObject, ‘read’) |