
💻 버전 관리
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.javapublic 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에 저장