본문 바로가기

spring/security

[Spring security] SecurityContext, 접근 제한, 권한 제한에 대해서

들어가며

스프링 시큐리티에서 인증을 구현하기 위한 클래스의 역할을 중점으로 알아보자.

인증을 담당하는 요소들

interface description
AuthenticationManager HTTP 필터 단계에서 요청을 수신하고 이 책임을 AuthenticationProvider에서 위임하는 책임을 지닌다.
AuthenticationProvider 인증 방식(논리)를 책임하는 인터페이스로 로그인 방식에 따른 인증 방식이 존재한다.

인증 정보를 관리하는 요소

class description
SecurityContext 인증된 사용자의 정보(Authentication)를 관리하는 문맥
SecurityContextHolder 요청(Thread)에 따른 사용자의 문맥을 관리하는 요소

하나의 스레드는 독립적인 문맥(SecurityContext)를 가진다. 동일한 스레드에서 문맥을 조회하면 동일한 결과를 보장한다.
하지만 스레드가 분기되어서 자식 스레드로 로직을 처리하는 경우에는 문맥이 복사가 되지 않기 때문에 의도하지 않게 동작할 수 있다.

비동기 호출 전략들

1. 스프링이 관리하는 쓰레드 분기

프레임워크의 기능을 통해서 쓰레드를 생성하는 경우에도 문맥은 복사가 되지 않지만 설정을 통해서 극복할 수 있다.

name description
MODE_THREADLOCAL 기본 전략으로 스레드 마다 문맥을 다르게 사용
MODE_INERITABLETHREADLOCAL 상위 스레드의 문맥을 하위 스레드에서 복사하여 공유하는 전략
MODE_GLOBAL 모든 스레드에서 문맥을 공유하는 전략
  • 사용 예시
@Bean
public InitializingBean initializingBean(){
        return()->SecurityContextHolder.setStrategyName(
        SecurityContextHolder.MODE_XXXX);
        }

2. 자체 관리 스레드 분기

스프링이 모르는 방법으로 스레드를 분기하는 경우에는 위의 설정으로 문맥을 복사할 수 없다.
그래서 위임을 이용해서 스레드를 생성하는 방식을 사용해야한다.

Executor, ExecutorService에 데코레이터를 적용하여 이를 해결한 방법을 사용하면 된다.
다음은 적용한 데코레이터 클래스들에 대한 설명이다.

class description
DelegatingSecurityContextExecutor Executor 인터페이스를 구현하며 Executor 객체를 장식하면 보안 컨텍스트를 해당 풀에 의해 생성된 스레드로 전달하는 기능을 제공하도록 디자인한 클래스
DelegatingSecurityContextExecutorService ExecutorService 인터테이스를 구현하며 ExecutorService 객체를 장식하면 보안 컨텍스트를 해당 풀에 의해 생성된 스레드로 전달하는 기능을 제공하도록 디자인한 클래스
DelegatingSecurityContextScheduledExecutorService Schedule 기능을 추가 제공
DelegatingSecurityContextRunnable Runnable 인터페이스를 구현하고 다른 스레드에서 실행되며 응답을 반환하지 않는 작업을 나타낸다.
DelegatingSecurityContextCallable Callable 인터페이스를 구현하고 다른 스레드에서 실행되며 최종적으로 응답을 반화하는 것이 Runnable과의 차이!

인증 성공시 실패시 처리

인증이 실패하면 예외 처리를 해야하는데 이러한 처리를 담당하는 요소들 살펴보자.

Basic 인증에서 실패 처리

AuthenticationEntryPoint 객체를 통해서 인증이 실패시 후 처리를 구현할 수 있다!!
서블릿 기술을 사용해서 Http 요청과 응답을 제어할 수 있어서 실패시 메시지를 수정하는 로직을 작성한다.

Form 인증에서 후처리

AuthenticationFailureHandler : 실패 후 처리 로직 담당
AuthenticationSuccessHandler : 성공 후 처리 로직 담당
서블릿 기술을 사용해서 Http 요청과 응답을 제어할 수 있어서 메시지를 수정하는 로직을 작성한다.

AuthenticationEntryPoint vs AuthenticationFailureHandler

두 가지 인터페이스가 제공하는 기능은 완벽하게 같다. 다만 특정 인증 방식에 따라서 entrypoint가 적합한 경우가 있는 것으로 추정된다.
근거는 EntryPoint를 사용하는 AuthenticationEntryPointHandler가 AuthenticationFailureHandler를 상속 받아서 실패 처리시에 entrypoint 객체를
사용하는 것을 확인했기 때문이다.

Servlet 독립적인 후처리

서블릿과 독립적으로 후처리 로직을 구성한다면 좀 더 유연한 AuthenticationEventPublisher를 사용하는 것이 좋아보인다.

@Bean
public AuthenticationEventPublisher authenticationEventPublisher
        (ApplicationEventPublisher applicationEventPublisher) {
    return new DefaultAuthenticationEventPublisher(applicationEventPublisher);
}
@Component
public class AuthenticationEvents {
    @EventListener
    public void onSuccess(AuthenticationSuccessEvent success) {
        // ...
    }

    @EventListener
    public void onFailure(AbstractAuthenticationFailureEvent failures) {
        // ...
    }
}

more...

권한 제한

사용자의 권한에 따라서 제공하는 서비스의 범위를 다르게 설정 해야하는 필요가 있다. 스프링 시큐리티에서 이런 기능을 제공하는데 조금 알아보자.

사용자의 권한에 따른 제한

사용자가 어떤 권한이 있는지에 따라서 허용되거나 거부되는 경우가 있다. 이를 위해서 권한(Authority) 개념을 이용해서 구현할 수 있다.

Authority

UserDetetail이 사용자의 정보를 나타내는 클래스이다. 여기서 권한에 대한 정보를 초기화할 수 있다.

var user = User.withUsername("john")
                 .password("1234")
                 .authorithies("read")
                 .build();

로그인한 상태에서 사용자의 정보는 SecurityContext에 저장되고 인가 필터에서 이를 활용하여 사용자의 권한 여부를 확인한다.
권한에 따른 접근 제한을 하기 위해서 사용되는 함수는 다음과 같다.

function description
access 복잡한 권한 설정을 위해서 사용
hasAuthority 권한이 있으면 허용
hasRole 역할이 있으면 허용

role vs authority
역할과 권한의 차이가 무엇인가?
쉽게 예시를 보자!
역할 : 관리자
권한 : 채용, 해고, 승진, 징계
case1 : 채용 권한이 있는 관리자
case2 : 징계 권한과 채용 권한이 있는 관리자

역할을 나타내고 싶을 때는 ROLE_ 접두사를 가진 권한을 역할로 취급한다. 앞에서 UserDetail 객체를 만든 예시에 역할을 부여한 예시는 이렇다.

var user = User.withUsername("john")
                 .password("1234")
                 .authorithies("ROLE_ADMIN")
                 .build();

GrantedAuthorityDefaults이 객체가 접두사를 초기화하는 객체인데 요놈을 빈으로 등록해서 나만의 접두사를 설정하면 커스텀이 가능!!

@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
    return new GrantedAuthorityDefaults("MYPREFIX_");
}

제한할 API 설정하기

사용자의 권한에 따라서 요청을 허용/거부할 수 있지만 어떤 요청에 정책을 적용하는지는 URL로 통제할 수 있다.
그 방법으로는 3가지 정도를 제공하는데 알아보자.

1. mvcMatchers()

오버 로딩을 통해서 두가지 종류의 메서드를 제공한다.
하나는 http method와 url을 조합하여 지정하는 방법 다른 하나는 오직 url만 사용하여 지정하는 방법
상황에 맞게 원하는 메소드를 사용하자.

return httpSecurity
                .authorizeRequests()
                .mvcMatchers("/test/**")
//                .mvcMatchers(HttpMethod.GET, "/test/**")
                .permitAll();

접근 제한 메서드의 입력 String에 식을 통해서 접근을 제한할 url을 지정할 수 있다.

expression description
/a /a 경로만
/a/* * 연산자는 하나의 아무 단어를 의미한다.
/a/** ** 연산자는 공백을 포함한 모든 문자들을 의미한다. 다 적용하라!!
/a/{param} 주어진 경로 매개 변수를 포함한 /a 경로에 적용
/a/{param:regex} 매개 변수 값과 주어진 정규식이 일치할 때만 주어진 경로 매개 변수를 포함한 /a 경로에 적용
2. antMatchers()

이녀석은 세가지 종류의 메서드가 있는데, mvc와 같이 2가지 종류와 오직 http method를 사용해서 매칭해주는 방식이 있다.

return httpSecurity
                .authorizeRequests()
                .antMatchers("/test/**")
//                .antMatchers(HttpMethod.GET, "/test/**")
//                .antMatchers(HttpMethod.GET)
                .permitAll();

mvc vs ant
mvc를 쓰는 것을 권한다. 왜냐하면 만약 /test에 접근을 제한한다고 지정하면 mvc는 /test, /test/ 도 막아주는데 ant는 /test만 막아준다.
예상치 못한 빈틈이 생긴다는 것! 그래서 mvc를 쓰고 http method 접근을 막는 경우에는 ant를 사용하자.

3. 정규식을 통한 접근 제한

정규식을 통해서 url을 필터링하고 접근을 막을 수 있다.
제공하는 함수의 이름은 regexMatchers()이다.

Reference

도서 : 스프링 시큐리티 인 액션