(스프링) 14장 시큐리티 메소드

웹 시큐리티는, 사용자가 인증받지 못한 콘텐츠 접근을 막아준다.

스프링 시큐리티를 사용하여 시큐어 빈(sercure bean)메소드를 어떻게 사용하는지 볼 수 있다. 이를 통해서 실행시킬 수 있는 사용자가 실제로 그것을 실행할 수 있는 권한을 가지지 않는다면 ,메소드가 실행되는 것을 방지하는 시큐리티를 나타낼 수 있다.


1. 애너테이션을 사용한 시큐어 메소드


스프링 시큐리티는 세 가지 다른 종류의 시큐리티 에너테이션을 제공한다.

  • 스프링 시큐리티의 @Secured
  • JSR-250의 @RolesAllowed
  • 표현식 주도의 애너테이션(@PreAuthorize, @PostAuthorize, @PreFilter, @PostFilter을 사용)
@Secured와 @RolesAllowed 애너테이션은 간단한 옵션이며, 권한이 사용자에게 부여된 것에 기반을 두고 액세스를 제한한다. 메소드에 대해 시큐리티 규칙 정의 시에 더 유연한 방법이 필요할 때, 스프링 시큐리티는 @PreAuthorize와 @PostAuthorize를 제공한다.
컬렉션의 @PostFilter/ @PreFilter 필터 요소는 메소드로 전달되거나 메소드에서 반환된다.


1.2 @Secure를 이용한 메소드 보안


스프링의 애너테이션 기반 메소드 시큐리티의키는 @EnableGlobalMethodSecurity를 사용하여 설정 클래스를 애너테이션 한다.

@Configuration
@EnableGlobalMethodSecurity(securedEnabled=true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration{
}

-->애너테이션과 더불어 GlobalMethodSecurtyConfiguration을 확장한다.


또한, 웹 레이어 시큐리티 설정에서 이미 인증 설정되지 않았다면, GlobalMethodSecurityConfiguration의 configure() 메소드를 오버라이딩해서 사용한다.

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
   auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}


또한, secureEnabled가 true일 때, 포인트 커트가 생성되며, 스프링 시큐리티 애스펙트는 @Secured로 애터네이션되는 빈 메소드를 감싼다. 예를들면, @Secured로 애너테이션 되는 addSpittle()메소드를 보자.

@Secured("ROLE_SPITTER")
public void addSpittle(Spittle spittle){
//...
}

-->@Secured 애너테이션은 인자로 String 배열을 사용한다. 각 String은 메소드를 실행하기 위해 필요한  인증 역할을 한다. ROLE_SPITTER를 전달하여 인증된 사용자가 ROLE_SPITTER에 대한 권한이 없으면 스프링 시큐리티는 addSpittle() 메소드의 호출을 허용하지 않는다.
'
만일 @Secured애너테이션에 인자로 하나 이상의 값이 전달된다면 인증된 사용자는 메소드를 호출하기 위해 인자로 전달된 권한 중 최소 한 가지 권한은 갖고 있어야 한다.
예를들어, @Secured예제에서 사용자는 메소드를 호출하려면 ROLE_SPITTER나 ROLE_ADMIN권한을 가진다.

@Secure({"ROLE_SPITTER", "ROLE_ADMIN"})
public void addSpittle(Spittle spittle){
//..
}

인증되지 않은 사용자나 필요한 권한이 없는 사용자가 메소드를 호출하려면, 메소드를 래핑하는 애스펙트는 스프링 시큐리티의 예외를 발생시킨다.(아마도 AuthenticationException이나 AccessDeniedException의 서브클래스다.) 예외는 반드시 처리해야 한다. 만일 보안이 적용된 메소드가 웹 요청 중간에 호출되면, 예외는 자동적으로 스프링 시큐리티의 필터가 처리한다. 그렇지 않은 경우는 예외를 처리하는 코드를 작성해야 한다.

@Secured의 애너테이션의 단점 중 하나는 @Secured가 스프링에 특화된 애너테이션이라는 점이다. 만일 표준 애너테이션을 사용하는 것이 보다 편한 경우는 @RoleAllowed를 대신 사용하는 것도 고려한다.


1.2 JSR-250의 @RolesAllowed를 사용


@RolesAllowed는 거의 모든 면에서 @Secured와 같다. 유일한 차이점이라면 @RolesAllowed는 JSR-250에 정의된 자바 표준 애너테이션 중 하나라는 것이다.

@RolesAllowed를 사용한다면 @EnableGlobalMethodSecurity의 jsr250Enabled애트리뷰트를 true로 설정한다.


@Configuration
@EnableGlobalMethodSecurity(jsr250Enabled=true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration{
}


-->jsr250Enabled와 securedEnabled 두 가지 애너테이션 방식을 동시에 활성화 할 수있다.

jsr250Enabled가 true로 설정되면, 포인트 커트는 영향을 받고, @RolesAllowed로 애너테이션 된 메소드는 스프링 시큐리티의 애스펙트로 감쌀 수 있다.

@RolesAllowed("ROLE_SPITTER")
public void addSpittle(Spittle spittle){
//...
}

-->비록 @RolesAllowed가 메소드 시큐리티를 위한 표준 기반 애너테이션이라는 점에서 @Secured에 비해서 약간의 정치적인 이점을 가지더라도 두 애너테이션은 공통된 단점이 있다. 사용자가 특별한 권한을 가지는가의 여부에 따라서 메소드 실행이 제한된다. 



2. 메소드 레벨 시큐리티를 위한 표현식 사용하기


@Secured와 @RolesAllowed가 권한 없는 사용자의 접근을 제한하기는 하지만, 보안제약 사항은 사용자에게 권한 여부를 판단 하는 것보다 더 복잡하다.

스프링 시큐리티 3.0은 메소드에 보다 흥미로운 보안 제약 사항을 제공하기 위해 SpEL을 사용하는 새로운 애너테이션을 도입햇다.

<SpEL 표현식으로 메소드 보안을 적용하기 위해 스프링 시큐리티 3.0에서 제공하는 네 가지 새로운 애너테이션>
  • @PreAuthorize : 표현식 평가 결과에 따라 호출 전 메소드 접근을 제한한다.
  • @PostAuthorize : 메소드 호출을 허용하지만, 표현식 평가 결과가 false인 경우 보안 예외를 발생시킨다.
  • @PostFilter : 메소드 호출을 허용하지만, 표현식에 대한 메소드의 결과를 필터링한다.
  • @PreFitler : 메소드 호출을 허용하지만, 메소드 진입 전에 입력값을 필터링한다.

2.1 메소드 액세스 규칙 표현하기


사용자가 필요한 권한을 가지지 않으면 @Secured와 @RolesAllowed가 메소드 실행을 하지 못하게 하는 방법을 알아보았다. 여기서의 약점은 사용자에게 부여된 권한에 기반을 두고 결정을 한다는 점이다.

스프링 시큐리티는 표현식 검증에 기반을 둔 메소드 액세스를 제한하는 애너테이션인 @PreAuthorize와 @PostAuthorize를 추가한다. 
@PreAuthorize와 @PostAuthorize의 주요차이는 표현식 평가시 발견된다. 

@PreAuthorize는 메소드 수행전에 평가되며, 표현식이 true값을 가지지 않는다면 메소드가 실행되지 않는다.
@PostAuthorize는 시큐리티 예외 상황이 발생하거나 하지 않는 경우를 결정하기 전에 메소드 반환할 때까지 대기한다.


사전권한 부여 메소드 액세스

@PreAuthorize를 이용하여 인증된 사용자에게 주어진 권한을 기준으로 접근을 제한한다.

@PreAuthorize("hasRole('ROLE_SPITRER')")
public void addSpittle(Spittle spittle){
//...
}

--->이와 같은 방법으로 사용할 때, @PreAuthorize는 @Secured또는 @RolesAllowed를 통해 얻을 수 있는 이득이 없다. ROLE_SPITTER 권한을 갖는 경우 메소드 접근을 승인한다. 그렇지 않은 경우 시큐리티 예외상항이 발생하고 메소드는 실행되지 않는다.


그러나, 
액세스 결정을 가이드하는 SpEL표현식을 사용하여 훨씬 더 많은 고급 시큐리티 제한이 작성 가능하다.
예를들어, 일반 Spitter사용자는 14글자 미만의 spittle을 작성할 수 있지만, 프리미엄 사용자는 길이 제한 없이 spittle을 작성한다고 가정해보자.

@Secured와 @RolesAllowed는 이용할 수 없지만, @PreAuthorize는 다음과 같이 이용한다.

@PreAuthorize("(hasRole('ROLE_SPITTER') and #spitter.text.length()<=140)"+"or hasRole('ROLE_PREMIUM')")
public void addSpittle(Spittle spittle){
//....
}

-->>표현식의 #spittle 부분은 같은이름의 메소드 파라미터를 직접 참조한다는 의미다. 스프링 시큐리티는 메소드에 전달된 파라미터를 검사하고 이 파라미터를 이용하여 접근 여부를 결정한다.



사후권한 부여 메소드 액세스


사후권한 부여는 보안이 적용된 메소드에 반환된 객체를 기준으로 보안 적용 여부를 결정한다. 이런 과정은 메소드가 반드시 호출되며 반환 값을 생성할 기회가 주어진다는 사실을 의미한다.

예를들면, getSpittleById() 메소드를 확보하고, 반환된 Spittle객체가 인증된 사용자에 해당하는 경우의 액세스만 인가한다. Spittle이 현재 사용자에게 속하는지를 가져오기 전에 알수 있는 방법은 없다. 따라서 getSpittleById()를 먼저 실행해야 한다. Spittle를 가져온 이후에, 현재 사용자에 속한 것이 아닌 것으로 판명 나면 시큐리티 예외가 발생한다.

권한을 부여하는 시점만 제외하면 @PostAuthorize는 @PreAuthorize와 동일하게 동작한다. 스프링 시큐리티의 @PostAuthorize는 메소드가 실행된 이후까지 시큐리티 규칙을 적용하기 위해 대기하는 점을 제외하고 @PreAuthorize와 동일한 방식으로 동작한다. 이런점에서 결정시 반환 값을 검토할 기회를 가진다

@PostAuthorize("returnObject.spitter.username==principal.username")
public Spittle getSpittleById(long id){
//..
}

-->위 예제에서는 반환된 객체가 Spitter이므로 표현식은 spitter의 username프로퍼티를 이용한다.
principal은 현재 인증된 사용자의 신원을(특히 username)나타내는 스프링 시큐리티의 특별 내장 이름 중 하나다.

Spittler객체에서 Spitter객체의 username프로퍼티와 principal username프로퍼티가 같다면 Spittle객체를 호출자에게 반환한다. 그러나 프로퍼티 값이 서로다르다면 AccessDeniedException이 발생하고 호출자에게 Spittle객체를 반환하지 않는다.

@PostAuthorize 애너테이션이 적용된 메소드가 먼저 수행되고 나중에 권한을 부여한다는 점을 기억하자.

2.2 필터링 메소드의 입출력


가끔 메소드 액세스를 제한하는 것은 매우 무거운 작업이며, 가끔은 메소드 보안이 이루어지지 않을 수 있지만, 데이터가 전달되고 메소드로부터 데이터 반환이 이루어 질수 있다.


예를들면, Spittle의 리스트를 반환하는 getOffensiveSpittles()라는 메소드는 누구한테 속해있든 간에 Spittle리스트를 반환한다.

public List<Spittle> getOffensiveSpittles(){....}

-->메소드의 관리적인 사용적인 측면에서는 완벽하지만, 현재 사용자에게 속한 Spittle에 대한 리스트를 한정하기에는 부족한 면이 있다.

사용자는 주어진 사용자를 위한 오페시브 Spittle만을 가져오기 위해 사용하고, 사용자 ID를 사용하는 다른 버전의 getOffensiveSpittles()를 많이 사용한다.
필요한것은 getOffensiveSpittles() 로부터 반환되는 Spittles컬렉션을 필터링하는 방법이고, 현재 사용자에게 허용된 리스트를 기준으로 어떻게 범위를 좁혀서 볼 것인지를 결정하는 것이다. 이것이 스프링 시큐리티의 @PostFilter가 하는 역할이다.


*사후필터링 메소드 반환 값

메소드에 대한 액세스를 제한하기 위해 표현식을 사용하는 대신에, @PostFilter는 컬렉션으 각 멤버에 대한 표현식이 메소드로부터 반환되는 지를 검증하고, 표현식에 대한 멤버 검증 결과과 false이면 이를 제거한다.

@PreAuthorize("hasAnyRole({'ROLE_SPITTER', 'ROLE_ADMIN'})")
@PostFilter("hasRole('ROLE_ADMIN') || " +"filterObject.spitter.username==principla.name")
public List<Spittle> getOffensiveSpittles(){
...
}

-->여기서 @PreAuthorize애너테이션은 메소드 실행을 위해 사용자에게 ROLE_SPITTER또는 ROLE_ADMIN 권한을 인가한다. 만약에 사용자가 체크포인트를 통해서 수행 가능하면 메소드가 수행되고 Spittles리스트가 반환된다. 그러나 @PostFilter애너테이션이 그 리스트를 필터링하면 사용자는 볼 수 있도록 허용된 Spittle객체만 볼 수 있다. 특히 관리자는 모든 오펜시브 Spittle을 볼 수 있고, 비관리자는 단지 주어진 Spittle만을 볼 수 있다. 

표현식의 filterObject는 메소드에서 반환된 List에 있는 개별 요소(Spittle)를 참조한다. 만일 Spittle에서 Spittle의 username이 표현식의 principal.name에 해당하는 인증된 사용자와 동일하다면 필터링된 목록에 포함되며, 그렇지 않으면 필터링된 목록에서 제외된다.



사전필터링 메소트 파라미터


예를들면, 삭제할 Spittles리스트를 가지고 있닫고 가정하자. 삭제하기 위해서 다음과 같은 시그니처를 사용하는 메소드를 작성한다.

public void deleteSpittles(List<Spittle> spittles){....}

--->여기에 시큐리티 규칙을 적용하길 원하는 경우, Spittles는 소윤권을 가진 사용자 또는 관리자에 의해서 삭제된다. 

스프링 시큐리티의 @PreFilter는 이 문제를 해결하기 위한 완전한 솔루션일 수 있다.

@PreAuthorize("hasAnyRole({'ROLE_SPITTER' , 'ROLE_ADMIN'})")
@PreFilter("hasAnyRole('ROLE_ADMIN') || "
              +"targetObject.spitter.username==principal.username")
public void deleteSpittles(List<Spittle> spittles){....}


--->@PreAuthorize는 ROLE_SPITTER 또는 ROLE_ADMIN인가를 보유하지 않는 사용자를 대신하여 호출되는 것을 방지한다. 그러나 @PreFilter는 deleteSpittles()로 전달되는 리스트가 Spittles만을 포함되도록 하며, 현재 사용자는 삭제 권한을 가진다. 표현식은 컬렉션 내 각 아이템에 대해 검증하며, 표현식 검증된 아이템들이 true를 가지면 리스트에 존재한다. targetObject변수는 검증을 위한 현재 리스트 아이템을 나타내는 또 다른 스프링 시큐리티를 위해 제공되는 값이다.


권한 평가자 정의하기


@PreAuthorize("hasAnyRole({'ROLE_SPITTER', 'ROLE_ADMIN'})")
@PreFilter("hasPermission(targetObject, 'delete')")
public void deleteSpittles(List<Spittle> spittles){....}

--->@PreFilter에 주어진 표현식은 더 빡빡해졌으며, 타깃 객체를 삭제하기 위한 권한을 가지고 있는지 질문한다. 만약 그렇다면 표현식의 결과는 true이며, Spittle은 deleteSpittles()에 전달된 리스트에 남는다. 그렇지 않다면 토스아웃(toss-out)된다.

hasPermission()함수는 SpEL에 대한 스프링 시큐리티 제공확장자이며, 개발자가 검증 시 수행할 로직을 플러그인하기 위한 기회를 제공한다.

<다음 코드는 맞춤형 권한 평가자인 SpittePermissionEvaluator을 보여준다.>

public class SpittlePermissionEvaluator implements PermissionEvaluator{
 
  private static final GrantedAuthority ADMIN_AUTHORITY= new GrantedAutorityImpl("ROLE_ADMIN");

   public boolean hasPermission(Authentication authentication, Object target, Object permission){
 
   if(target instanceof Spittle){
      Spittle spittle=(Spittle)target;
      String username=spittle.getSpitter().getUsername();
      if("delete".equals(permission)){
          return isAdmin(authentication)||
                  username.equals(autentication.getName());
   }
}
throw new UnsupportedOperationException(
           "hasPermission not supported for object<"+target+"> and permission <"+
permission+">");
}

private boolean isAdmin(Authentication authentication){
  return authentication.getAuthroties().contains(ADMIN_AUTHORITY);
}

public boolean hasPermission(Authentication authentication, Serialzable targetId, String getType, Object permission){
  throw new UnsupportedOperationException();
}
}

댓글

  1. 블로그 관리자가 댓글을 삭제했습니다.

    답글삭제
  2. 블로그 관리자가 댓글을 삭제했습니다.

    답글삭제

댓글 쓰기

이 블로그의 인기 게시물

(18장) WebSocekt과 STOMP를 사용하여 메시징하기

(네트워크)폴링방식 vs 롱 폴링방식

(ElasticSearch) 결과에서 순서 정렬