개발/Java,Spring

Spring Boot 2.x, 3.x Security에서 hasPermission 사용

호돌맨 2019. 8. 11. 15:27

Spring Boot2.x에서 Spring Security를 이용해 권한을 관리 할 수 있다.
기본적으로 Override된 configure(HttpSecurity http)에서 AntMatcher를 이용해 Role확인을 할 수 있다.
하지만 관리 대상과 요구사항이 많아지면 Role만으로는 문제 해결이 용이하지 않다.
그래서 MethodSecurity를 이용한 권한 관리 방법을 간략히 정리한다.

Gradle 설정

compile("org.springframework.boot:spring-boot-starter-security")
compile("org.springframework.security:spring-security-oauth2-client")
compile("org.springframework.session:spring-session-jdbc")

Spring Security 설정 (SpringBoot 2.x 기준)

Spring Security와 관련된 코드는 기본적으로 아래처럼 할 수 있다.
설정 되어있다고 가정하고 넘어간다.

@EnableWebSecurity
public class ClientSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 생략..
    }
}

GlobalMethodSecurity 설정

@EnableGlobaMethodSecurity를 위 ClientSecurityConfig에 추가해도 작동하지만
Class를 분리하여 ClientMethodSecurity에 설정하기로 한다.

SpringBoot 2.x 기준

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class ClientMethodSecurity extends GlobalMethodSecurityConfiguration {

}

SpringBoot 3.x 기준

Spring 6.0부터 @Bean 등록 방식으로 변경되었다.

@Configuration
@EnableMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {

    @Bean
    protected MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
        // 자세한 내용은 아래에
    }

}

isAuthorized 사용

isAuthorized 등 annotation을 사용할 수 있다.

@GetMapping("/api/users/me")
@PreAuthorize("isAuthenticated()")
public UserResponse getMe(@AuthenticationPrincipal ExternalUser extUser) {
    return userService.getMe(extUser.getId());
}

hasPermission 사용

hasPermission을 사용하려면 몇 가지 설정이 더 필요하다. permission을 evaluation할 수 있는 클래스를 만들고 GlobalMethodSecurity 설정에 연결 해야한다.

우선 PermissionEvalutor를 구현하는 클래스를 만든다.

public class ClientPermissionExpression implements PermissionEvaluator {

    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        // 적절한 권한 관리 코드
        return false;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        // 적절한 권한 관리 코드
        return false;
    }
}

ClientMethodSecurity에 연결

SpringBoot 2.x 기준

그리고 나서 위의 클래스를 GlobalMethodSecurity에 연결한다. 그러므로 ClientMethodSecurity.java 내용은 이렇게 된다.

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class ClientMethodSecurity extends GlobalMethodSecurityConfiguration {

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        DefaultMethodSecurityExpressionHandler expressionHandler =
                new DefaultMethodSecurityExpressionHandler();
        expressionHandler.setPermissionEvaluator(new ClientPermissionExpression());
        return expressionHandler;
    }
}

SpringBoot 3.x기준

@Configuration
@EnableMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {

    @Bean
    protected MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
        DefaultMethodSecurityExpressionHandler expressionHandler =
                new DefaultMethodSecurityExpressionHandler();
        expressionHandler.setPermissionEvaluator(new ClientPermissionExpression());
        return expressionHandler;
    }
}

컨트롤러 예제

이제 @PreAuthorizehasPermission을 사용할 수 있다. 두 가지 형태(method)가 존재한다.

  1. 아래는 게시물 Object에 권한이 있는지 확인하도록 하는 소스
@PreAuthorize("hasPermission('BBS', 'read')")
@GetMapping("/api/bbs/{name}")
public BbsResponse get(@PathVariable String name) {
    // 생략
}
  1. 게시물 인자 name을 같이 넘겨 확인하는 소스
@PreAuthorize("hasPermission(#name, 'BBs', 'read')")
@GetMapping("/api/bbs/{name}")
public BbsResponse get(@PathVariable String name) {
    // 생략
}

두 메소드가 ClientPermissionExpression.java에서 만든 두 개의 메소드로 각각 호출 되기 때문에
마음에 드는 형태로 구현하면 된다.
DB에서 회원 id를 조회해서 권한이 있는지 확인 한다던지..

참고 링크

https://www.baeldung.com/spring-security-create-new-custom-security-expression
https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2client