Rootable의 개발일기
Spring Security 구조 및 흐름 본문
스프링 시큐리티는 Authentication(인증)과 Authorization(인가)라는 두 가지 큰 흐름으로 처리된다.
때때로 Authorization을 'Access Control'이라고 말하기도 한다.
📌 인증 방식
- 인증(Authentication) : Who are you?
- 인가(Authorization) : What are you allowed to do?
스프링 시큐리티는 기본적으로 인증 절차를 거친 후 인가 절차를 진행하며, 인가 과정에서 해당 리소스에 접근 권한이 있는지 확인한다.
스프링 시큐리티에서는 인증과 인가를 위해 Principal을 아이디로, Credential을 패스워드로 로 사용하는 Crendential 기반의 인증 방식을 사용한다.
Principal(접근 주체) : 보호 받는 Resource에 접근하는 대상
Credential(비밀번호) : Resource에 접근하는 대상의 비밀번호
📌 Spring Security란
Spring 기반의 application 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크
🔍 장점
- 보안과 관련해서 많은 옵션을 제공해 주기 때문에 개발자 입장에서 일일이 보안 관련 로직을 작성하지 않아도 된다.
- 자체적으로 세션을 체크하지 않아도 된다.
- 로그인 완료 시 일일이 redirect하는 설정을 하지 않아도 된다.
🔍 특징
- 스프링 시큐리티는 Filter 기반으로 동작하므로 Spring MVC와 분리되어 관리하고 동작할 수 있다.
- Bean으로 설정할 수 있다.
- Spring Security 3.2부터는 XML 설정을 하지 않아도 된다.
📌 Spring Security Architecture
1. 사용자가 로그인 정보와 함께 요청한 HTTP request를 수신하면 해당 요청은 인증 mechanism과 model을 기반으로 한 필터들을 통과한다.
- HTTP 기본 인증 요청 : BasicAuthenticationFilter
- HTTP Digest 인증 요청 : DigestAuthenticationFilter
- Login Form에 의한 인증 요청 : UserPasswordAuthenticationFilter
- 509 인증 요청 : X509AuthenticationFilter
2. UsernamePasswordAuthenticationToken의 AuthenticationToken(인증용 토큰) 생성
- AuthenticationFilter는 요청 흐름을 가로채어 인증용 토큰을 생성한다.
- 인증용 토큰은 해당 요청으로 받은 username과 password를 기반으로 생성되며 대부분의 인증 mechanism과 동일하다.
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object principal; //User's ID
private Object credentials; //Password
//인증 전의 객체
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
}
@Override
public Object getCredentials() {
return this.credentials;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
Assert.isTrue(!isAuthenticated,
"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
this.credentials = null;
}
3. Filter를 통해 AuthenticationManager에 AuthenticationToken을 위임
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
- 가장 일반적인 구현은 AuthenticationManager가 자신을 구현한 ProviderManager에게 AuthenticationProvider Instances에게 인증 작업을 요구하도록 하는 것이다.
- AuthenticationManager는 ProviderManager에게 AuthenticationToken을 전달
- 인증에 성공하면 두 번째 생성자를 이용해 객체를 생성하여 SecurityContext에 저장한다.
4. ProviderManager는 AuthenticationProvider Instances를 조회하여 인증을 요구한다.
- 실제 인증에 대한 부분을 처리한다.
- 인증 전의 Authentication 객체를 받아서 인증이 완료된 객체를 반환
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
boolean supports(Class<?> authentication);
}
5. 실제 DB에서 사용자 인증 정보를 가져오는 UserDetailsService에 사용자 정보를 넘겨준다.
- UserDetailsService는 username을 기반으로 검색
- 일반적으로 UserDetailsService를 implements 한 클래스에서 UserRepository를 주입받아 DB와 연결하여 처리한다.
public interface UserDetailsService {
UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException;
}
6. UserDetailsService는 넘겨받은 사용자 정보를 통해 DB에서 찾은 사용자 정보인 UserDetails 객체를 생성하여 리턴한다.
- UserDetails는 UsernamePasswordAuthenticationToken을 생성하기 위해 사용됨
- UserDetails를 implements 하여 처리할 수 있다.
public interface UserDetails extends Serializable {
//권한 목록
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
//계정 만료 여부
boolean isAccountNonExpired();
//계정 잠김 여부
boolean isAccountNonLocked();
//비밀번호 만료 여부
boolean isCredentialsNonExpired();
//사용자 활성여부
boolean isEnabled();
}
7. AuthenticationProvider Instances는 UserDetails를 넘겨받고 사용자 정보를 비교
8. 인증이 완료되면 권한 등의 사용자 정보를 담은 Authentication 객체를 리턴
- Authenticaion은 현재 접근하는 주체의 정보와 권한을 담는 인터페이스다.
- AuthenticationManager.authenticate(Authentication) method에 의해 인증 요청이 처리된 후 인증 요청 또는 인증된 주체에 대한 토큰을 나타낸다.
- 일단 인증되면, Authentication은 보통 사용 중인 인증 mechanism에 의해 SecurityContextHolder에 의해 관리되는 SecurityContext에 저장된다.
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
현재 사용자가 갖고 있는 권한을 의미하며, ROLE_*의 형태로 사용한다.
GrantedAuthority 객체는 UserDetailsService에 의해 불러올 수 있고, 특정 자원에 대한 권한이 있는지를 검사하여 접근 허용 여부를 결정한다.
9. AuthenticationFilter에 Authentication 객체를 리턴
10. Authentication 객체를 SecurityContext에 저장
- SecurityContextHolder는 세션 영역에 있는 SecurityContext에 Authentication 객체를 저장한다.
- SecurityContext를 통해 Authentication을 저장하거나 꺼내올 수 있다.
- 사용자 정보를 저장한다는 것은 스프링 시큐리티가 세션-쿠키 기반의 인증 방식을 사용한다는 것을 의미한다.
✔ SecurityContextHolder
보안 주체의 세부 정보를 포함하여 응용프로그램의 현재 보안 콘텍스트에 대한 세부 정보가 저장된다.
References:
https://dev-coco.tistory.com/174
https://spring.io/guides/topicals/spring-security-architecture/
'Spring' 카테고리의 다른 글
GenericFilterBean vs OncePerRequestFilter (0) | 2024.04.24 |
---|---|
@Transactional (0) | 2023.08.08 |
스프링 파일 업로드/다운로드 (0) | 2023.08.03 |
API 예외 처리 (0) | 2023.08.01 |
예외 처리와 오류 페이지 (0) | 2023.07.31 |