Spring Security에 대해서

구본식·2022년 12월 26일
2
post-thumbnail

프로젝트에서 Spring Security를 사용하여 JWT 토큰 기반 인증/인가 검증을 하려고 한다.
JWT토큰을 기반 Spring Security Filter를 커스텀하기전 공부한 개념을 정리하려고 한다.

참고로 섬세한 구분이 필요한 api(로그인,비로그인 구분)에 대해서는 Spring security를 사용하지 않고 뒤에 설명할 커스텀 어노테이션Spring Intercepter를 이용하여 인증,권한을 처리했다.
😂여러가지를 공부하면서 프로젝트에 적용해보고 구현중이라 어떤것이 더 현명한 방법인지는 잘모르겠다.ㅠ

1. 들어가기에 앞서

스프링 시큐리티의 구조와 동작원리를 알아보기전에 어플리케이션을 구성하는
두 가지 영역에는 인증(Authentication)과 인가(Authorization)이 있다.

인증은 해당 사용자가 본인임이 맞는지 확인하는 절차이다.
인가는 인증을 마친 사용자가 요청한 자원을 사용할 수 있는 권한을 가지고 있는지를 확인하는 과정이다.

스프링 시큐리티는 인증 절차를 진행한 후, 인가 과정을 통해 요청 리소스에 접근 권한이 있는지를 확인해주는 역할을 수행하게 된다.


2. Spring Security 란

스프링 시큐리티 란 Spring 기반의 어플리케이션의 보안(인증,인가)을 담당하는 스프링 하위 프레임워크이다.

스프링 시큐리티인증과 인가처리를 여러개의 Filter 흐름에 따라 처리한다.

스프링 시큐리티 필터의 위치를 보게 되면,
Dispatcher Servlet에 도달하기 전에 서블릿 Filter를 거쳐가게 되는데 이 서블릿 Filter 이전에 Spring Security Filter들이 위치하게 된다.

스프링 시큐리티는 이러한 인증,인가 검증을 위해서 Pricipal를 아이디로, Credential를 패스워드
사용하는 Credental 기반의 인증 방식을 사용한다.

  • Pricipal : 보호 받는 리소스를 접근하는 대상
  • Credential: 보호 받는 리소스에 접근하는 비밀번호

3. Spring Scurity 구조

스프링 시큐리티의 처리 과정의 예시를 통해 구조를 설명해보겠다.

1. 사용자가 보호받는 리소스로 로그인 정보와 함께 인증 요청을한다.(HTTP Request)
2. AuthenticationFilter가 요청을 가로채고, 가로챈 정보를 가지고 UsernamePasswordAuthenticationToken의 인증용 객체를 생성한다.
3. AuthenticationManager의 구현체인 ProviderManager에게 앞서 생성한 UsernamePasswordToken 객체를 전달한다.
4. AuthenticationManager는 등록된 AuthenticationProvider 리스트 중에서 처리할수 있는 provider에게 인증처리를 위임하게 된다.
5. 앞서 선택된 AuthenticationProvider가 등록된 UserDetailsService(직접 구현가능)에게 사용자 정보를 넘겨주게 된다.
6. UserDetailsService는 넘겨받는 사용자 정보를 통해 DB에서 사용자 정보를 찾게되고, 찾은 정보로 UserDetails 객체를 만들어 넘겨준다.(UserDetails도 직접 구현가능)
7. AuthenticationProvider 들은 UserDetails를 넘겨 받고, UsernamePasswordAuthenticationToken의 사용자 정보와 비교한다.
8. 인증이 완료되면 AuthenticationManager(ProviderManager)가 사용자 정보와 권한등을 포함하고 있는 Authentication 객체를 생성하여 반환해준다.
9. 최초의 AuthenticationFilter에 Authentication 객체가 반환되어진다.
10. SecurityContext의 영역에 Authentication 객체를 저장한다.
11. 인가 처리 부분에서 SecurityContext에 저장된 Authentication객체를 가지고 권한이 있는지를 판별하게 된다.

즉, 인증(10번까지)처리가 완료되었다면 SecurityContextHolder는 세션 영역에 있는 SecurityContext에 Authentication 객체를 저장하게 된는 것이다.
(인가 처리는 11번과 같이 진행됨. 간단하게 설명하자면)

이를 통해 스프링 시큐리티는 쿠키-세션 기반의 인증방식을 사용한다는 것을 알 수 있다.


4. Spring Security 구조의 주요 모듈

💡Authentication

리소스에 접근한 주체의 정보와 권한을 담는 인터페이스이다. Authentication은 객체이고 SecurityContext에 저장되고, SecurityContext를 통해 Authentication 객체에 접근할 수 있다.

public interface Authentication extends Principal, Serializable {
	// 주체의 권한 목록을 가져온다.
	Collection<? extends GrantedAuthority> getAuthorities();

	//Credenticals를 가져온다. 주로 비밀번호가 사용되어진다.
	Object getCredentials();

	Object getDetails();

	//Pricipal 객체를 가져온다.
	Object getPrincipal();

	//인증 여부를 확인 한다.
	boolean isAuthenticated();

	//인증 여부를 세팅한다.
	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; }

💡UsernamePasswordAuthenticationToken

Authentication을 구현한 하위 클래스인 구현체이다. 사용자의 ID가 Pricipal의 역할을 하게되고, Password가 Credential의 역할을 하게된다.
두가지 생성자가 존재하는데 첫번째는 인증 전의 객체를 생성하고, 두번째는 인증 완료 객체를 위해 사용되어진다.

public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {

	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
    
    //주체에 ID 해당된다. 아이디나 엔티티 등을 넣으면 된다.
	private final Object principal;
    
    //주체에 비밀번호에 해당된다.
	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
	}

	@Override
	public Object getCredentials() {
		return this.credentials;
	}
	...

💡AuthenticationManager

스프링 시큐리티 필터들이 수행하는 방식을 정의한 API라고 한다. 시큐리티 필터내에서 넘어온 인증정보를 가지고 Autentication타입의 객체를 만드는 역할을 한다.

💡ProviderManager

가장 많이 사용되는 AuthenticationManager의 구현체이다. 인증을 처리하는 부분이고 실제로는 ProviderManager 내부에도 AuthenticationProvider 리스트가 존재하고 인증처리를 위임하게 된다. 이 리스트들은 ProviderManager 생성시 주입된다.
AuthenticationProvider들은 다른 인증 방식이 수행된다. 예를 들어 하나의 Provider가 이름, 비밀번호를 검증할수 있으면 다른 Provider는 다른 인증방식을 수행하게된다. 다운스트림 방식으로 요청한 인증 방식을 처리할수 있는 Provider를 찾게 되는 것이다.

💡UserDetailsService

UserDetails 객체를 반환하는 역할을 하는 인터페이스이다.
하나의 메서드를 가지고 있고 보통 이를 구현한 구현체에서 Repository를 사용하여 DB에 있는 사용자의 정보를 가지고 UserDetails를 생성하여 반환해준다.(보통 커스텀 하여 구현체를 만들어서 사용)

public interface UserDetailsService {
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

만약 사용자를 찾지 못하게 되면 UsernameNotFoundException 예외를 발생시키면 된다.

💡UserDetails

인증에 성공해서 Authentication 객체를 생성하기 위해 사용되어진다.
직접 커스텀 하여 원하는 객체를 넣어 처리할 수 있다.

참고로 인증/인가를 마친 후 Authentication객체를 SecurityContext에 저장하게 되는데, Controller에서 인증을 마친 사용자의 정보를 사용하기 위해서 UserDetails를 implements 하여 사용자 엔티티 객체를 넣어 커스텀 하였다.
자세한 코드는 뒤에서 살펴보겠다.

💡SecurityContext

인증을 마친 Authentication을 보관하는 역할을 하며 저장하거나 꺼내올수 있따.

💡GrantedAuthority

현재 주체(Pricipal)이 가지고 있는 권한을 의미한다.
주로 인증이 완료되어 Authentication를 생성할 때 인자로 넘기며 권한 체크에서 해당 객체의 권한을 판단할때 사용되어진다.


5. Spring Security Filter의 역할

스프링 시큐리티의 필터 구성은 아래와 같다.

temp

스프링 시큐리티를 통해 이번 프로젝트에서 크게 로그인 인증, JWT 토큰 인증, 권한검사를 하기 위해 커스텀할 필터를 알아보겠다.

💡UsernamePasswordAuthenticationFilter

사용자가 로그인 인증 하기 위해서 Id,Password를 요청하게 되고, 인증이 완료되면 Authentication 객체가 생성되게 되고 실패한다면 생성되지 않는다. 그림을 보게 되면 위에서 설명했던 순서대로 모듈을 호출하는 것을 볼 수 있다.

인증이 완료되면 정상적으로 Authentication이 생성되고 AuthenticationSuccessHadler를 호출하게 된다.
프로젝트에서 JWT토큰을 사용할 예정이기 때문에 이 핸들러에서 토큰을 생성하여 응답할 예정이다.

인증이 실패할 경우에는 AuthenticationFailureHandler를 호출하게 된다.

참고로 UsernamePasswordAuthenticationFilter에 로그인 인증 성공 여부에 따라 호출되는 메서드가 존재한다.
이 메서드를 사용해도 되지만 나는 위에서 말한 두개의 핸들러를 호출하도록 할 것이다.

💡BasicAuthenticationFilter

헤더에 토큰으로 "basic"으로 된 토큰을 사용하는 HTTPBasic 기반의 유저 인증을 담당하는 필터이다.

프로젝트에서는 JWT 토큰 인증 방식을 사용할 것이기 때문에, 이 부분을 커스텀 하여 인증이 필요한 리소스 접근시 JWT 토큰 검증 절차 로직을 구현할 것이다.

인증을 마치게 되면 SecurityContext에 Authentication객체를 담게 되는데, 만약 인증이 실패하여 Authentication 객체를 SecurityContext에 담지 못하게 되면 AuthenticationEntryPoint 핸들러를 호출하게 된다.

💡인가 체크 필터

BasicAuthenticationFilter를 통해 인증이 완료되게 되면 SecurityContext에 Authentication 객체가 담아지게 되고 요청한 리소스의 권한을 가지고 있는지 검증하게 된다.

만약 올바른 권한을 가지고 있지 않는다면, AccessDeniedHandler 핸들러를 호출하게 된다.

profile
백엔드 개발자를 꿈꾸며 기록중💻

0개의 댓글