💻 버전 관리
Spring Boot : 3.2.0
springframework.security : 6.2.0
JDK : 17
Build : Maven
Spring-Security 를 처음 공부 했을 땐, 헷갈리는 부분도 많았고 '보안'이라는 생소한 영역을 접하는 것 같아 어려움이 있었지만, 차근차근 용어 정리 후에 구조를 보는 것이 도움이 많이 되어 이에 대해 정리하고자 한다.
공식문서에서 발췌한 인증 과정인데 이에 대한 용어 정리를 목표로 정리하고자 한다.
보안 용어에는 인증, 인가가 있다.
다음은 Spring-Security 의 Authentication Interface 이다.
Authentication Interface
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;
}
💡 GrantedAuthority
ROLEADMIN, ROLE_USER 등 인증을 통과한 Principal 객체가 가지고 있는 권한을 나타내고, perfix(접두사)로 'ROLE' 이 붙는다.
SecurityContextHolder
는 보안 주체의 세부 정보를 포함하여 응용 프로그램의 현재 보안 컨텍스트에 대한 세부 정보가 저장된다.AbstractAuthenticationToken
의 하위 클래스AbstractAuthenticationToken.java
public abstract class AbstractAuthenticationToken implements Authentication, CredentialsContainer {
}
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
}
}
Authentication은 AuthenticationManager
에 등록된 AuthenticationProvider
에 의해 처리 된다.
인증이 정상 처리되면, boolean 값으로 위에서 봤던 AbstractAuthenticationToken
의 setAuthenticated(false) 을 true 로 변경 후 객체를 생성하여 SecurityContext
에 저장한다.
AuthenticationManager Interface
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
실제 인증 과정에 대한 로직을 가지고 있는 AuthenticationProvider
를 List로 가지고 있다.
ProviderManager
에서 AuthenticationProvider
를 for 문을 통해 조회 후 인증처리한다.
ProviderMangaer.java
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;
}
}
Authentication
에 대한 부분을 처리하는 인터페이스Authentication
객체를 받아서 인증 완료 된 객체를 반환하는 역할.public interface AuthenticationProvider {
// 인증 전의 Authenticaion 객체를 받아서 인증된 Authentication 객체를 반환
Authentication authenticate(Authentication var1) throws AuthenticationException;
boolean supports(Class<?> var1);
}
@Override
해야 하는 메서드는 다음과 같음Method-Name | Return | Discription | Default-Type |
---|---|---|---|
getAuthorities() | Collection<? extands GrantedAuthority> | 계정의 권한 목록 | |
getPassword() | String | 계정의 비밀번호 | |
getUserName() | String | 계정의 고유한 값 | |
isAccountNonExpired() | boolean | 계정의 만료 여부 | true |
isAccountNonLocked() | boolean | 계정의 잠김 여부 | true |
isCredentialsNonExpired() | boolean | 비밀번호 만료 여부 | true |
isEnable() | boolean | 계정의 활성화 여부 | true |
정보를 가져와서 UserDetails 타입으로 반환
Method-Name | Return | Discription | Default-Type |
---|---|---|---|
loadUserByUsername | UserDetails | UserDetails |
Spring-Security 는 Servlet-Filter 기반이다.
💡 Servlet Container(톰캣) 은 URI 패스에 따라 어떤 FilterChain 과 Servlet 에 연결할 지 결정
FilterChain.java
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// do something before the rest of the application
chain.doFilter(request, response); // invoke the rest of the application
// do something after the rest of the application
}
💡 doFilter 메서드는 필터를 실행하기 전후로 코드를 넣을 수 있다. 때문에 순서에 유의해야 한다.
💡역할?
Servlet Container에서 Filter 등록 해주지만, 우리가 사용하는 Spring에선 Bean으로 인식하지 않음.
DelegatingFilterProxy
를 사용하면, 필터 클래스를 Bean으로 등록 할 수 있음.
💡역할?
요청에 따라 필요한 필터를 실행시키는 역할
SecurityFilterChain
은 FilterChainProxy
가 어떤 필터 체인을 실행시켜야 하는지 파악하는데 사용한다.
SecurityFilterChain
안의 Security Filter
는 spring Bean 이다.
Security Filter
는 FilterChainProxy 에 등록되어있다.
FilterChainProxy
가 spring-security에서 동작하는 모든 서블릿의 시작점이기 때문에 proxy에 있음.FilterChainProxy
가 SecurityFilterChain
의 호출 시기 결정에 유연성을 가져다준다.FilterChainProxy
는 요청의 RequestMarcher
인터페이스를 활용해 실행 시점을 파악한다.ExceptionTranslationFilter
는 AccessDeniedException 과 AuthenticationException 을 HTTP 응답으로 변환시켜준다.SecurityContextHolder
비움
1. 클라이언트로 부터 요청을 받으면 서블릿 필터에서 SecurityFilterChain
으로 작업이 위임되고 그 중 UsernamePasswordAuthenticationFilter
(AuthenticationFilter)에서 인증을 처리
2. AuthenticationFilter
는 요청 객체(HttpServletRequest)에서 username과 password를 추출해서 토큰 생성
3. 이 후 AuthenticationManager
에 토큰 전달, Manager는 인터페이스이며, 일반적으로 사용되는 구현체는 ProviderManager
4. ProviderManager
는 인증을 위해 AuthenticationProvider
로 토큰 전달
5. AuthenticationProvider
는 토큰 정보를 UserDetailsService
에 전달
6. UserDetailsService
는 전달 받은 정보를 통해 DB에서 일치하는 사용자를 찾아 UserDetails
객체를 생성
7. 생성된 UserDetails
객체는 AuthenticationProvider
로 전달되며, 해당 Provider에서 인증을 수행하고 성공하게 되면 ProviderManager
로 권한을 담은 토큰 전달
8. ProviderManager
는 검증된 토큰을 AuthenticationFilter
로 전달
9. AuthenticationFilter
는 검증된 토큰을 SecurityContextHolder
에 있는 SecurityContext
에 저장