본문 바로가기
개발 일기/spring

Spring Security 설정 (Dynamic)

by URMOO 2022. 9. 2.
반응형

권한 설정을 Hierarchy를 이용해 적용하는 방법도 있지만, 동적으로 설정해주는 것도 가능하다.

예를들어, /user URL에 GETPOST 두 가지가 있는데, POSTROLE_ADMIN만 접근 가능하다면? Hierarchy 설정은 이런부분에서 유연하게 대처할 수 없다.

이런 상황에서 동적으로 접근권한을 설정한다면 더 유연하게 사용할 수 있으며 컨트롤러에 불필요한 어노테이션(@PreAuthorize)을 붙이지 않아도 된다.

단점은 DB에 권한 관련 설계를 할 때, 어떻게 설계하느냐에 따라 구조가 복잡해질 수 있다는 점이다.

 

본 포스팅의 코드는 여기서 확인 가능합니다😉

 

 

이 포스팅은 DB 설계까지는 하지 않고, 하드코딩으로.. 아래와 같이 접근권한을 제한하여 개발을 진행하려한다.

  GET POST
/admin ROLE_ADMIN  ROLE_ADMIN
/company ROLE_ADMIN, ROLE_COMPANY  ROLE_ADMIN
/user ROLE_ADMIN, ROLE_COMPANY, ROLE_USER  ROLE_ADMIN, ROLE_COMPANY

코드

FilterSecurityInterceptor를 만들어서 내가 원하는 형식의 권한 설정을 해주어야한다.

SecurityConfig.java

//..
public class SecurityConfig {
//..
  @Bean
  public SecurityFilterChain filterChain(HttpSecurity security) throws Exception {

//..
    security
        .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
        //본인이 만든 커스텀 필터를 필터로 등록
        .addFilterBefore(customFilterSecurityInterceptor(), FilterSecurityInterceptor.class);
//..
  }

//..

  @Bean
  public FilterSecurityInterceptor customFilterSecurityInterceptor() {

    FilterSecurityInterceptor filterSecurityInterceptor = new FilterSecurityInterceptor();
    filterSecurityInterceptor
        .setSecurityMetadataSource(urlFilterInvocationSecurityMetadataSource());
    filterSecurityInterceptor.setAccessDecisionManager(affirmativeBased());

    return filterSecurityInterceptor;
  }

  //이 Bean을 생성할 때, 권한과 관련된 설정을 해준다. 
  @Bean
  public UrlFilterInvocationSecurityMetadataSource urlFilterInvocationSecurityMetadataSource() {
    return new UrlFilterInvocationSecurityMetadataSource();
  }

  private AccessDecisionManager affirmativeBased() {
    return new AffirmativeBased(Collections.singletonList(new RoleVoter()));
  }

}

UrlFilterInvocationSecurityMetadataSource.java

@Getter
public class UrlFilterInvocationSecurityMetadataSource implements
    FilterInvocationSecurityMetadataSource {

  private Map<RequestMatcher, List<ConfigAttribute>> requestMap;

  public UrlFilterInvocationSecurityMetadataSource() {
    this.requestMap = new HashMap<>();

    //GET, POST를 포함한 모든 HTTPMethod를 ROLE_ADMIN 권한 접근 허용
    requestMap.put(new AntPathRequestMatcher("/admin"),
        Collections.singletonList(new SecurityConfig("ROLE_ADMIN")));

    //ROLE_ADMIN, ROLE_COMPANY -> `/company` GET 허용
    requestMap.put(new AntPathRequestMatcher("/company", "GET"),
        Arrays.asList(new SecurityConfig("ROLE_ADMIN"), new SecurityConfig("ROLE_COMPANY")));
    //ROLE_ADMIN -> '/company' POST 허용
    requestMap.put(new AntPathRequestMatcher("/company", "POST"),
        Collections.singletonList(new SecurityConfig("ROLE_ADMIN")));

    //ROLE_ADMIN, ROLE_COMPANY, ROLE_USER -> `/user` GET 허용
    requestMap.put(new AntPathRequestMatcher("/user", "GET"),
        Arrays.asList(new SecurityConfig("ROLE_ADMIN"), new SecurityConfig("ROLE_COMPANY"),
            new SecurityConfig("ROLE_USER")));
    //ROLE_ADMIN, ROLE_COMPANY -> '/user' POST 허용
    requestMap.put(new AntPathRequestMatcher("/user", "POST"),
        Arrays.asList(new SecurityConfig("ROLE_ADMIN"), new SecurityConfig("ROLE_COMPANY")));


  }

  @Override
  public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
    final HttpServletRequest request = ((FilterInvocation) object).getRequest();

    if (requestMap != null) {
      Set<Map.Entry<RequestMatcher, List<ConfigAttribute>>> entries = requestMap.entrySet();
      for (Map.Entry<RequestMatcher, List<ConfigAttribute>> entry : entries) {
        if (entry.getKey().matches(request)) {
          return entry.getValue();
        }
      }
    }

    return Collections.emptyList();
  }

  @Override
  public Collection<ConfigAttribute> getAllConfigAttributes() {
    Set<ConfigAttribute> allAttributes = new HashSet<>();

    for (Map.Entry<RequestMatcher, List<ConfigAttribute>> entry : requestMap
        .entrySet()) {
      allAttributes.addAll(entry.getValue());
    }

    return allAttributes;
  }

  @Override
  public boolean supports(Class<?> clazz) {
    return FilterInvocation.class.isAssignableFrom(clazz);
  }
}

UrlFilterInvocationSecurityMetadataSource를 생성할 때, 본인이 원하는 접근권한을 설정해준다.

new AntPathRequestMatcher("/company", "GET")처럼 url와 httpMethod를 각각 지정해줄 수 도있고,

new AntPathRequestMatcher("/admin") 이렇게 사용한다면, 모든 httpMethod를 포함하게 된다.

그리고 SecurityConfig 에 권한 이름을 담은 객체들을 만들어 리스트에 담아준다.

 

앞서 말했던것 처럼 나는 하드코딩으로 리스트를 생성해 줬지만, DB에 있는 값을 가져와 원하는 형식으로 만들어서 사용도 가능하다. GET과 POST를 제외한 DELETE, PUT, PATCH도 별도 지정 가능하다.

 

컨트롤러는 아래와 같이 만들어주었다. 

@RestController
@RequiredArgsConstructor
public class Controller {
	//...
  @GetMapping("/company")
  public String company() {
    return "company";
  }

  @GetMapping("/admin")
  public String admin() {
    return "admin";
  }

  @GetMapping("/user")
  public String user() {
    return "user";
  }

  @PostMapping("/company")
  public String companyPost() {
    return "company";
  }

  @PostMapping("/admin")
  public String adminPost() {
    return "admin";
  }

  @PostMapping("/user")
  public String userPost() {
    return "user";
  }

}

 

이렇게 설정을 끝내고 테스트 코드를 돌려보면, 아래와같은 결과를 얻을 수 있다.

 

반응형

'개발 일기 > spring' 카테고리의 다른 글

Spring security (접근권한 변경)  (0) 2022.09.06
Spring Security 설정 (Hierarchy)  (0) 2022.09.02
Spring Security 설정 (JWT 2)  (0) 2022.08.31
Spring Security 설정 (JWT 1)  (2) 2022.08.31
Spring Security 설정 (기본)  (2) 2022.08.31

댓글