Spring/스프링 시큐리티 인 액션

[8장] 권한 부여 구성: 제한 적용

꾸준함. 2025. 5. 30. 23:51

주의

이 책은 Spring Security 5 버전을 기준으로 작성되었으므로, Spring Boot 3.X 버전에서는 일부 클래스가 더 이상 사용되지(deprecated) 않을 수 있습니다.

 

1. Matcher 메서드로 엔드포인트 선택

  • 요청에 권한을 부여할 때 관리자 역할이 있는 사용자만 엔드포인트를 호출할 수 있도록 mvcMatchers() 메서드를 이용할 수 있음
  • Matcher 메서드로 요청을 참조할 때는 특정한 규칙부터 일반적인 규칙의 순서로 지정해야 하므로 anyRequest() 메서드를 mvcMatchers() 같은 더 특정적인 Matcher 메서드보다 먼저 호출할 수 없음

 

@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") // 관리자 역할인 사용자만 /hello 엔드포인트 호출 가능
.mvcMatchers("/ciao").hasRole("MANAGER") // 운영자 역할인 사용자만 /ciao 엔드포인트 호출 가능
.anyRequest().permitAll(); // 나머지 모든 엔드포인트에 대해 모든 요청 허용
//.anyRequest().denyAll();
//.anyRequest().authenticated();
}
}
view raw .java hosted with ❤ by GitHub

 

2. MVC Matcher 메서드로 권한을 부여할 요청 선택

  • 권한 부여 구성을 적용할 요청을 지정하는 일반적인 방법은 MVC 식을 이용하는 것
  • MVC Matcher 메서드는 표준 MVC 구문으로 경로를 지정하며 해당 구문은 @RequestMapping, @GetMapping, @PostMapping 등의 어노테이션으로 엔드포인트 매핑을 작성할 때의 구문과 동일함
  • 다음 두 메서드로 MVC Matcher 메서드를 선언할 수 있음
    • mvcMatchers(HttpMethod method, String... patterns): 제한을 적용할 HTTP 방식과 경로를 모두 지정할 수 있으며 같은 경로에 대해 HTTP 방식별로 다른 제한을 적용할 때 유용함
    • mvcMatchers(String... patterns): 경로만을 기준으로 권한 부여 제한을 적용할 때 간단하게 이용 가능, 해당 메서드를 이용하면 자동으로 해당 경로의 모든 HTTP 방식에 제한이 적용됨

 

@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("bill")
.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()
// HTTP GET 방식으로 /a 경로를 요청하면 앱이 사용자를 인증해야 함
.mvcMatchers(HttpMethod.GET, "/a").authenticated()
// HTTP POST 방식으로 /a 경로를 요청하면 모두 허용
.mvcMatchers(HttpMethod.POST, "/a").permitAll()
// 다른 경로에 대한 모든 요청 거부
.anyRequest().denyAll();
// HTTP Post 방식으로 /a 경로를 호출할 수 있게 CSRF 비활성화
http.csrf().disable();
}
}
view raw .java hosted with ❤ by GitHub

 

  • 여러 경로에 같은 권한 부여 규칙을 적용하는 방법은 크게 두 가지가 있음
    • 권한 부여 규칙을 적용할 경로를 모두 열거하는 방법: 경로가 많아질 경우 코드가 장황해짐
    • 경로 식을 통해 같은 접두사로 시작하는 경로 그룹에는 항상 같은 권한 부여 규칙을 적용: 같은 그룹에 새 경로를 추가할 때는 권한 부여 구성을 변경할 필요가 없다는 장점이 있음

 

@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()
// /a/b/** 식은 접두사 /a/b가 붙은 모든 경로를 나타냄
.mvcMatchers( "/a/b/**").authenticated()
.anyRequest().permitAll();
http.csrf().disable();
}
}
view raw .java hosted with ❤ by GitHub

 

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
// 길이와 관계없이 숫자를 포함하는 문자열을 나타내는 정규식
.mvcMatchers( "/product/{code:^[0-9]*$}").permitAll()
.anyRequest().denyAll();
}
}
view raw .java hosted with ❤ by GitHub

 

MVC Matcher 메서드에 사용하는 식 설명
/a /a 경로만
/a/* * 연산자는 한 경로 이름만 대체
이 경우 /a/b 또는 /a/c와는 일치하지만, /a/b/c와는 일치하지 않음
/a/** ** 연산자는 여러 경로 일므을 대체
이 경우 /a, /a/b, /a/b/c가 모두 이 식과 일치함
/a/{param} 이 식은 주어진 경로 매개변수를 포함한 /a 경로에 적용
/a/{param:regex} 이 식은 매개변수 값과 주어진 정규식이 일치할 때만 주어진 경로 매개변수를 포함한 /a 경로에 적용됨

 

3. Ant Matcher로 권한을 부여할 요청 선택

  • Ant Matcher는 다음의 세 메서드를 이용함
    • antMatchers(HttpMethod method, String patterns): 제한을 적용할 HTTP 방식과 경로를 참조할 앤트 패턴을 모두 지정 가능, 같은 경로 그룹에 대해 HTTP 방식별로 다른 제한을 적용할 때 유용함
    • antMatchers(String patterns): 경로만을 기준으로 권한 부여 제한을 적용할 때 더 쉽고 간단하게 이용할 수 있음, 모든 HTTP 방식에 자동으로 제한이 적용됨
    • antMatchers(HttpMethod method): antMatchers(httpMethod, "/**")와 같은 의미이며 경로와 관계없이 특정 HTTP 방식을 지정할 수 있음

 

  • Ant Matcher는 패턴에 대해 주어진 앤트 식을 그대로 적용하며 스프링의 미묘한 MVC 기능은 고려하지 않음
    • 이 경우 /hello는 /hello/ 경로에 앤트 식으로 적용되지 않음
    • /hello/ 경로도 보호하려면 이를 개별적으로 추가하거나 일치하는 앤트 식을 작성해야 함

 

  • Ant Matcher보다는 MVC Matcher를 이용하는 것을 권장


@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic();
http.authorizeRequests()
.antMatchers( "/hello").authenticated();
}
view raw .java hosted with ❤ by GitHub

 

4. Regex Matcher로 권한을 부여할 요청 선택

  • 정규식은 어떤 문자열 형식이든지 나타낼 수 있으므로 무한의 가능성을 제공하지만 간단한 시나리오에 적용하더라도 읽기 어렵다는 단점이 있음
  • 따라서 MVC나 Ant Matcher를 우선적으로 이용하고 다른 대안이 없을 때만 정규식을 이용하는 것을 권장
  • 다음 두 메서드로 Regex Matcher를 구현할 수 있음
    • regexMatchers(HttpMethod method, String regex): 제한을 적용할 HTTP 방식과 경로를 참조할 정규식을 모두 지정함, 같은 경로 그룹에 대해 HTTP 방식별로 다른 제한을 적용할 때 유용함
    • regexMatchers(String regex): 경로만을 기준으로 권한 부여 제한을 적용할 때 더 쉽고 간단하게 이용할 수 있음, 모든 HTTP 방식에 자동으로 제한이 적용됨


@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");
}
}
view raw .java hosted with ❤ by GitHub

 

참고

스프링 시큐리티 인 액션

 

반응형