본문 바로가기

개인적으로 공부한 것을 정리해 놓은 블로그입니다 틀린 것이 있으면 댓글 부탁 드립니다!


Spring Security

스프링시큐리티 공부 15 - 권한이란?

반응형

스프링 시큐리티에서 중요한 2가지 개념 중 하나이다 

 

사용자에 SecurityFilter를 거쳐 인증을 얻었다면

필터마지막에서 FilterSecurityInterceptor 혹은

서블릿단에서 특정 메서드에 어노테이션(MethodSecurityInterceptor) 를  통해 권한을 체크하게 된다. 

 

스프링 시큐리티에서 권한에 대한 처리들은 스프링 AOP를 기반으로 동작한다 .  

 

DI(의존성주입) 과 함께 스프링의 주요 철학인  AOP(Aspenct Oriented Programming 관점지향 프로그래밍)은

스파게티 코드(여러 관심사 가 스파게티처럼 엮여있는 코드)에서 벗어나  관심사를 분리하여 , 같은 관심사를 갖는 코드끼리 따로 관리하는 프로그래밍 철학이다 .  

 

스프링에서는 AOP를 구현하기 위해 아래와 같은 개념들을 제공한다. 

 

Aspect: 공통 관심사

-권한 처리 

- 로그 

- 트렌젝션 관리

- 세션 관리

- 기타 . .. 

 

Weaving

- 빈과 빈을 Proxy로 감사서 연결해주는 작업

- 빈과 빈의 호출사이에 Pointcut을 적용해서 JoinPoint를 판별한 다음 PointCut을 요청한 Advice를 JoinPoint를 적용시킴

 

PointCut: JoinPoint를 만들어냄  

- JoinPoint를 지적해주는 Expression

- anntation 마킹

 

JoinPoint: 빈과 빈 사이에서 호출이 일어나는 곳

 

-메소드 호출

- setter를 통한 멤버 주입

- 생성자 호출

 

Advice: 호출시 추가로 처리해줄 로직 

-인터셉트 해서 주입해줄 코드 

 

PointCut을 통해 JoinPoint를 만들어내고 만들어진 JoinPoint 사이에 Advice를 삽입하는 메커니즘

 

 

권한(Authroization)

 

이제까지 살펴봤던 인증(Authentication)의 경우 필터를 통해 동작하여 요청이 여러개의 필터가 이어진 필터체인을 거쳐 DispatcherSevlet에게 전달되어 비지니스  로직을 수행했다면  

 

권한은  하나의 AccessDecisionManager가 해당 요청에 대해 권환위원회를 열어 권한을 체크하고 특정 메서드에 접근을 시킬지를 결정한다. 권한체크는 위에 설명한 AOP를 통해 동작하는데 스프링이 빈을 등록하고 프락시 객체를 가지고 엮어주는 과정에서 각 PointCut에 의해 구분된 JoinPoint에 Interceptor가 요청을 가로체  Advice하는 메커니즘으로 작동한다.  

 

권한 처리에 고려해야할 것들

 

 

 접근하려고 하는 사람이 어떤 접근 권한을 가지고 있는지 

 

- 유저에게 GrantedAuthority를 통해 접근 권한을 준다.  

 -role based , scope based , user defined 세가지 형식으로 구현할 수 있다.

 

 접근하려고 하는 상황에서는 체크해야 할 내용이 무엇인지 

 

 -SecurityMetadataSource , ConfigAttribute (체크해야할 내용들이 들어간다)

 - 정적인 경우와 동적인 경우 

 - AccessDecisionVoter가 ConfigAttribute을 체크하여 옳은 권한인지  투표 한다. 

 

여러가지  판단 결과가 나왔을 때 취합은 어떤 방식으로 할 것인지

 

-AccessDecisionManager : 권한 위원회 (Voter가 투표한 것을 취합하여 결정한다)

  -AffirmativeBased : 긍정 위원회 (한명이라도 투표했을 경우 통과)

  -ConsensusBased : 다수결 위원회 ( 다수결을 통해 다쪽에 속하는 의견을 따른다)

  -UnanimouseBased: 만장일치 위원회 

 

 

인증과 권한의 구조 

 

 

 

디버거로 권한이 동작하는  흐름을 한번 살펴보자 

 

먼저 간단하게 컨트롤러를 만들었다 .  /greeting에 요청에 대하여 컨트롤러 안으로 들어가기 이전에

사용자가 ADMIN권한을 갖고 있는지  체크할 것이다 . 

 

멤버변수인 interceptor는 클래스에 들어가보려고 만든거니 신경쓰지않아도 된다. 

 

@RestController
public class HomeController {
    
    //확인용 없어도 상관 없음
    MethodSecurityInterceptor interceptor;
    
    
    @PreAuthorize("hasRole('USER')")
    @GetMapping("/greeting")
    public String greeting(){
        return "hello there!";
    }
}

 

MethodSecurityInterceptor 기반으로 권한을 체크하기 위해선 설정을 해줘야한다 . 

 

@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {

}

 

 

권한체크에 대한 설정들을 정의할 MethodSecurityConfig를 만들고 GlobalMethodSecurityConfiguration을 상속받았다 . 

 

GlobalMethodSecurityConfiguration에 기본적으로 권한에 대한 기능들이 구현되어 있다 . 원한다면 오버라이딩해서 AccessDecisionManager 같은 것을 커스터마이징해서 사용할 수 있다 . 지금은 흐름을 보려 하는거닌 따로 오버라이딩 하지는 않았다

 

@EnableGlobalMethodSecurity(prePostEnabled = true) 는 MethodSecurityInterceptor가 컨트롤러에 들어가기 전에  작동할 수 있도록 해준다

 

간단한 테스트 코드를 작성해 보자 

 

 

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class AuthorityTestApplicationTest{

    TestRestTemplate template = new TestRestTemplate("user1","1111");

    @LocalServerPort
    int port;

    public URI uri(String path) {
        try {
            return new URI(format("http://localhost:%d%s", port, path));
        }catch (Exception e){
            throw new IllegalArgumentException();
        }
    }

    @DisplayName("컨트롤러 테스트")
    @Test
    void basicTest(){

        String testMessage = template.getForObject(uri("/greeting"), String.class);
        System.out.println("testMessage = " + testMessage);
        assertEquals("hello there!",testMessage);
    }

}

 

TestRestTemplate 를 사용하여 요청을 보내고 응답값을 출력하고 테스트 하도록 했다 . 

 

securityConfig에서 inmemoryUser로  User(username:"user1" , password:"1111" , roles: "USER")인 유저를 하나 등록해 놨다.  

 

현재 /greeting에 @PreAuthorize에는 hasRole이 USER이기 떄문에 "hello there!"가 출력되고 테스트가 성공할 것이다. 

 

디버그 모드로 해당 과정을 따라가 보자 

 

MethodSecurityInterceptor의 invoke 메서드에 브레이크 포인트를 찍고 확인해봤다 . 

 

 

 

invoke에서는   beforeInvocation()을 호출하고

 

 

권한 판단에 사용될 ConfigAttribute들을  SecurityMetadataSource를 통해 가져온다 .

 

 

 

 

 

다음으론 인증에 대해서 확인하고 인증이 필요한 경우 authenticateIfRequired()로 들어와 인증을 하게된다. 

 

 

 

 

 

 

attempAuthorization()에서는 accessDecisionManager를 통해 위원회를 열어 권한을 체크한다 . decide 가 권한을 체크하는

실질적인 메서드이다 .

 

configAttributes를 Voter에게 넘겨 Voter들이 권한을 체크하고 자신이 체크할 결정할 수 있는 것이라면 투표한다. 

 

Voter.vote()의 리턴값을 담는 result 는 투표의 결과를 나타낸다

 

-1 , 0 , 1 을 리턴하는데 -1은 반대 , 0은 판단할 수 없음 , 1은 찬성을 의미한다. 

 

위의 그림을 보면 현재 voter는  PreInvocationAuthorizationAdviceVoter 이고 찬성 표를 던졌다 .  

 

PreInvocationAuthorizationAdviceVoter은 메서드의 @PreAuthorize 같은 어노테이션의 권한체크를 담당한다.  

 

현재 AffirmativeBased (긍정 권한 위원회 ) 이기 때문에 한명만 찬성해도 pass 된다.  

위에 만든 MethodSecurityConfig에서 accessDecisionManager를 구현해서 넣어준다면

 

다른 방식의 위원회( AffirmativeBased,ConsensusBased, UnanimouseBased)가 구성되도록 할 수도 있다 . 

 

 

 

권한이 통과되면 InterceptorStatusToken을 발급해주고 해당 토큰을 통해 메서드 안으로 들어가 로직을 수행한다. 

 

개념들이 재밌는 것 같다 . 

반응형