본문 바로가기
개발 이론/Spring Security

[Spring Security] 권한 부여 : 제한 적용

by dal_been 2023. 11. 12.
728x90

권한 부여 구성을 적용할 요청을 선택하는데는 선택기 메서드를 사용한다.

스프링 시큐리티에는 세 유형의 선택기 메서드가 있다

  • mvc 선택기 : 경로에 mvc식을 이용해 엔드포인트를 선택
  • 앤트 선택기 :  경로에 앤트 식을 이용해 엔드포인트를 선택
  • 정규식 선택기 : 경로 정규식을 이용해 엔드포인트를 선택

 

일단 선택기 메서드에 대해서 알아보자

간단하게 예제를 들자면

 

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello!";
    }

    @GetMapping("/ciao")
    public String ciao() {
        return "Ciao!";
    }


    @GetMapping("/hola")
    public String hola() {
        return "Hola!";
    }

}

 

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {


    @Override
    @Bean
    public UserDetailsService userDetailsService() {
        var manager = new InMemoryUserDetailsManager();

        var user1 = User.withUsername("john")
                .password("12345")
                .roles("ADMIN")
                .build();

        var user2 = User.withUsername("jane")
                .password("12345")
                .roles("MANAGER")
                .build();

        manager.createUser(user1);
        manager.createUser(user2);

        return manager;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic();

        http.authorizeRequests()
                .mvcMatchers("/hello").hasRole("ADMIN")
                .mvcMatchers("/ciao").hasRole("MANAGER")
                .anyRequest().permitAll();
                //.anyRequest().denyAll();
                //.anyRequest().authenticated();
    }

}

 

현재 클래스에서는 InMemoryUserDetailsManager를 UserDetailsService 인스턴스로서 선언하고 다른 역할을 가진 두 사용자를 추가하였다

 

그래서 .mvcMatchers()를 통해 엔드포인트에 어느 역할을 허락할 것인지 정하였다. 그래서 만약 역할에 해당되지 않으면 403 Forbidden 에러가 나온다

 

그러나 anyRequest().permitAll()같이 엔드포인트에 대한 다른 설정이 없는 것들은 다 허용하게 하였다

 

여기서 주의할점 permitAll에 해당하는 엔드포인트인데 사용자 이름과 암호를 제공했다면 스프링 시큐리티는 인증 프로세스를 수행하기 때문에 아무리 permitAll이라도 검증하지 못하면 인증에 실패하고  401 권한 없음 으로 반환한다.

-> 즉 permitAll로 요청에 대해 허용하지지만 인증관련 데이터가 오면 인증 논리를 실행하므로 인증 필터에서 걸려지게 되어 퀀할 필터로 전달하지 않아 HTTP 401권합 없음으로 응답한다

 

 

 


MVC 선택기로 권한을 부여할 요청 선택

mvcMatchers(HttpMethod method, String... patterns) : 제한을 적용한 HTTP 방식과 경로를 모두 지정할 수 있다

mvcMatchers(String .. patterns) : 경로만을 기준으로 권한 부여 제한을 적용할때

 

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {

    @Override
    @Bean
    public UserDetailsService userDetailsService() {
        var manager = new InMemoryUserDetailsManager();

        var user1 = User.withUsername("john")
                .password("12345")
                .roles("ADMIN")
                .build();

        var user2 = User.withUsername("jane")
                .password("12345")
                .roles("MANAGER")
                .build();

        manager.createUser(user1);
        manager.createUser(user2);

        return manager;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic();

        http.authorizeRequests()
        		.mvcMatchers(HttpMethod.GET,"/b").authenticated()
                .mvcMatchers( "/a/b/**").authenticated()
                .anyRequest().permitAll();

        http.csrf().disable();
    }
}

 

위의 코드를 살펴보면 "/b"인데 get 방식일때 인증이 요청되고 "/a/b/**"는 뒤에가 뭐든 "/a/b"로 시작하면 인증이 요청된다.

나머지 요청들은 permitAll() 한다

 

 

연산자

 

"**" 와같이 여러 경로에 대한 요청을 나타내는데에 사용한다

만약 "/a/**/c"라면 "/a/d/f/c"라도 인증을 거치게된다

 

경로변수에는 정규식을 이용할 수 도 있다

 

http.authorizeRequests()
                .mvcMatchers("/a/{code:^[0-9*$]}").authenticated()

 

이렇게 하면 숫자를 포함한 요청만을 허용한다. 만약 다른 값이 포함되었다면 401에러가 나온다

 

 

 


앤트 선택기로 권한을 부여할 요청 선택

 

antMatchers(HttpMethod method, String patterns) : 제한을 적용한 HTTP 방식과 경로를 참조할 앤트 패턴을 모두 적용

antMatchers(String patterns) : 경로만을 기준으로 권한 부여 제한을 적용할때

antMatchers(HttpMethod method) : 경로와 관계없이 특정 HTTP 방식을 지정가능

 

근데 앞서 이야기했던 mvc 선택기와는 어떤점이 다를까?

스프링은 동일한 작업에 대한 모든 경로에 대해 경로 뒤에 다른 / 를 추가해도 해석할 수 있다. 예를 들어 "/hello" "/hello/"는 같은 메서드를 호출한다. mvc선택기는 두 경로에 대해 자동으로 같은 규칙을 적용한다

그러나 앤트 선택기는 다르다. 두 요청에 대해서 다르게 적용한다

 

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic();

        http.authorizeRequests()
                //.mvcMatchers( "/hello").authenticated();
                .antMatchers( "/hello").authenticated();
    }

 

앤트선택기의 경우 "/hello" 에 대해서 인증을 처리하지만 "/hello/"에 대해서는 인증처리를 하지 않는다

 

 

따라서 사실 mvc 선택기를 이용하는 것이 좋다. 그 이유는 앞서 얘기했듯이 권한 부여 규칙을 위해 경로를 해석하는 방법과 스프링이 경로를 엔드포인트에 매핑하기 위해 해석하는 방법이 같기 때문이다.

 

 

 


정규식 선택기로 권한을 부여할 요청 선택

 

앞서 앤트와 mvc식으로는 해결할 수 없는 특정한 요구사항이 있을 수 있다.

예를 들어 "경로에 특정한 기호나 문자가 있으면 모든 요청을 거부한다" 같은 경우다.

이런 시나리오에는 정규식 같은 강력한 식이 필요하다. 

 

다만 간단한 시나리오에 적용하더라도 읽기 어렵기 때문에 우선적으로 mvc,앤트 선택기를 적용하고 대안이 없을때 정규식을 이용하자

 

regexMatchers(HttpMethod method, String regex) : 제한을 적용한 HTTP방식과 경로를 참조할 정규식을 모두 지정

regexMatchers(String regex) :  경로만 기준으로 권한 부여제한을 적용할때

 

 

@RestController
public class VideoController {

    @GetMapping("/video/{country}/{language}")
    public String video(@PathVariable String country,
                        @PathVariable String language) {
        return "Video allowed for " + country + " " + language;
    }

}

 

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {

    @Override
    @Bean
    public UserDetailsService userDetailsService() {
        var uds = new InMemoryUserDetailsManager();

        var u1 = User.withUsername("john")
                     .password("12345")
                     .authorities("read")
                .build();

        var u2 = User.withUsername("jane")
                    .password("12345")
                    .authorities("read", "premium")
                .build();

        uds.createUser(u1);
        uds.createUser(u2);

        return uds;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic();

        http.authorizeRequests()
                .regexMatchers(".*/(us|uk|ca)+/(en|fr).*")
                    .authenticated()
            .anyRequest().hasAuthority("premium");
    }
}

 

 

 

 


정리

  • 실제 시나리오에서는 요청마다 다른 권한 부여 규칙을 적용하는 경우가 많다
  • 경로와 HTTP 방식에 따라 권한 부여 규칙을 구성할 요청을 지정한다. 이를 위해 MVC,앤트,정규식의 세가지 선택기 메서드를 이용하는 방법이 있다