Spring 기반의 어플리케이션의 보안(인증, 인가, 권한)을 담당하는 프레임워크
Http Request
)AuthenticationFilter
가 요청을 가로챈다. 사용자가 입력한 정보를 바탕으로 UsernamePasswordAuthenticationToken
객체(미검증 상태)를 생성한다.AuthenticationManager
의 구현체인 ProviderManager
에게 UsernamePasswordAuthenticationToken
를 전달한다.AuthenticationProvider
에게 UsernamePasswordAuthenticationToken
를 전달한다.UserDetailsService
에게 사용자 정보를 넘겨준다.UserDetails
로 받아온다.AuthenticationProvider
는 사용자 정보와 UserDetails
를 비교한다.Authentication
을 반환한다.AuthenticationFilter
에 Authentication
이 반환된다.SecurityContextHolder
에 Authentication
을 저장한다.실질적인 인증 과정은 사용자가 입력한 데이터와 UserDetailService
의 메서드가 반환하는 UserDetails
객체를 비교함으로써 동작한다. 두 클래스의 구현에 따라 인증의 세부 과정이 달라진다.
SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
과 SecurityContextHolder.MODE_THREADLOCAL
방법을 제공public interface Authentication extends Principal, Serializable {
// 현재 사용자의 권한 목록을 가져온다.
Collection<? extends GrantedAuthority> getAuthorities();
// credentials(주로 비밀번호)을 가져온다.
Object getCredentials();
Object getDetails();
// Principal 객체를 가져온다.
Object getPrincipal();
// 인증 여부를 가져온다.
boolean isAuthenticated();
// 인증 여부를 설정한다.
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
// 주로 사용자의 ID에 해당함
private final Object principal;
// 주로 사용자의 PW에 해당함
private Object credentials;
// 인증 완료 전의 객체 생성
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
setAuthenticated(false);
}
// 인증 완료 후의 객체 생성
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(true); // must use super, as we override
}
}
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
}
public interface AuthenticationProvider {
// 인증 전의 Authenticaion 객체를 받아서 인증된 Authentication 객체를 반환
Authentication authenticate(Authentication var1) throws AuthenticationException;
boolean supports(Class<?> var1);
}
WebSecurityConfigurerAdapter를 상속해 만든 SecurityConfig에서 직접 구현한 provider를 등록 가능하다.
- WebSecurityConfigurerAdapter의 상위 클래스는 AuthenticationManager(6)을 가지고 있기 때문
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public AuthenticationManager getAuthenticationManager() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public CustomAuthenticationProvider customAuthenticationProvider() throws Exception {
return new CustomAuthenticationProvider();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider());
}
}
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
ProviderManager
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
public List<AuthenticationProvider> getProviders() {
return providers;
}
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
//for문으로 모든 provider를 순회하여 처리하고 result가 나올 때까지 반복한다.
for (AuthenticationProvider provider : getProviders()) {
....
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
} catch (AccountStatusException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
}
....
}
throw lastException;
}
}
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// TODO Auto-generated method stub
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
ROLE_ADMIN
, ROLE_USER
같이 ROLE_*
의 형태로 사용한다.아래는 auto-config attribute를 설정하였을 경우 자동으로 구성되는 필터들이다.
또한 사용자가 입력한 데이터와 UserDetailService가 반환한 UserDetails 개체를 비교하여 실제 유효성 검사가 작동하는지 확인했습니다. 그 점에서 세부적인 검증 과정은 2계층 구현에 따라 다르다.