7장 권한 부여 구성: 액세스 제한
이 블로그 내용은 아래 도서를 학습한 내용입니다.
학습 도서 정보
스프링 시큐리티 인 액션(Spring Security in Action)
로렌티우 스필카 지음
최민석 옮김
위키북스
- 권한 부여(Authorization)는 식별된 클라이언트가 요청된 리소스에 액세스할 권한이 있는지 시스템이 결정하는 프로세스다.
- 스프링 시큐리티에서 애플리케이션은 인증 흐름을 완료한 후 요청을 권한 부여 필터에 위임한다.
- 필터는 구성된 권한 부여 규칙에 따라 요청을 허용하거나 거부한다.
- 권한이 무엇인지 이해하고 사용자 권한에 따라 모든 엔드포인트에 액세스 규칙을 적용한다.
- 권한을 역할로 그룹화하는 방법과 사용자 역할에 따라 권한 부여 규칙을 적용하는 방법을 알아본다.
- 클라이언트가 요청하면 인증 필터가 사용자를 인증한다.
- 인증이 완료되면 인증 필터가 사용자 세부 정보를 보안 컨텍스트에 저장하고 요청을 권한 부여 필터에 위임한다.
- 권한 부여 필터는 호출을 허용할지 결정한다.
- 요청에 권한을 부여할지 결정하기 위해 권한 부여 필터는 보안 컨텍스트의 세부 정보를 이용한다.
7.1 권한과 역할에 따라 접근 제한
권한 부여와 역할은 애플리케이션의 모든 엔드포인트를 보호하는데 이용된다.
사용자는 가진 권한에 따라 특정 작업만 실행할 수 있다.
애플리케이션은 이용 권리를 권한과 역할로서 제공한다.
- 한 사용자는 하나 이상의 권한(사용자가 수행할 수 있는 작업)을 가진다.
- 인증 프로세스 도중 UserDetailsService는 사용자의 권한을 포함한 모든 세부 정보를 얻는다.
- 애플리케이션은 사용자를 성공적으로 인증한 후 GrantedAuthority 인터페이스로 나타내는 권한으로 권한 부여를 수행한다.
public interface GrantedAuthority extends Serializable {
String getAuthority();
}
- 권한은 사용자가 시스템 리소스로 수행할 수 있는 작업이며 권한의 이름은 객체의 getAuthority()가 String 형식으로 반환하는 값에서 얻을 수 있다.
- 권한의 이름은 맞춤형 권한 부여 규칙을 정의할 때 필요하다.
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
}
- 스프링 시큐리티의 사용자를 나타내는 계약인 UserDetails는 GrantedAuthority 인스턴스의 컬렉션을 가진다.
- 한 사용자는 하나 이상의 이용 권리를 가질 수 있으며 getAutorities() 메서드는 GrantedAuthority 인스턴스의 컬렉션을 반환한다.
- 인증이 완료되면 로그인한 사용자의 세부 정보에 권한이 포함되며 애플리케이션은 이를 바탕으로 이용 권한을 부여할 수 있다.
7.1.1 사용자 권한을 기준으로 모든 엔드포인트에 접근 제한
운영 단계의 앱에서는 인증하지 않아도 호출할 수 있는 엔드포인트도 있고 특별한 이용 권리가 있어야 접근 가능한 엔드포인트도 있다.
권한은 사용자가 수행할 수 있는 작업이다.
권한을 기반으로 권한 부여 규칙이 구현된다.
특정 권한이 있는 사용자만 특정 엔드포인트에 요청 할 수 있다.
권한부여 세 메서드
- hasAuthority(): 애플리케이션이 제한을 구성하는 하나의 권한만 매개 변수로 받는다. 해당 권한이 있는 사용자만 엔드포인트를 호출할 수 있다.
- hasAnyAuthority(): 애플리케이션이 제한을 구성하는 권한을 하나 이상 받을 수 있다. "주어진 권한 중 하나라도 있을 때" 라고 기억한다. 사용자는 요청하려면 지정된 권한 중 하나라도 있어야 한다.
- access(): SpEL(Spring Expression Languate)을 기반으로 권한 부여 규칙을 구축하므로 액세스를 구성하는 데 무한한 가능성이 있지만 코드를 읽고 디버그하기 어려운 단점이 있다. 따라서 이 메서드는 추천하지 않으며, hasAnyAuthority()또는 hasAuthority() 메서드를 적용할 수 없을 때만 이용하는 것이 좋다.
- 그러나 access() 메서드는 매개 변수로 지정하는 식으로 규칙을 맞춤 구성할 수 있다는 이점이 있다. 이는 아주 강력한 기능이며 SpEL 식을 이용ㅇ하면 사실상 어떤 조건이라도정의할 수 있다.
- authorizeRequests() : 엔드포인트에 권한 부여 규칙을 지정
- anyRequest() : 이용된 URL이나 HTTP 방식과 관계없이 모든 요청에 대해 규칙을 적용한다.
- permitAll() : 인증 여부와 관계없이 모든 요청에 대해 접근을 허용한다.
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Override
@Bean
public UserDetailsService userDetailsService() {
var manager = new InMemoryUserDetailsManager();
var user1 = User.withUsername("john")
.password("12345")
.authorities("READ")
.build();
var user2 = User.withUsername("jane")
.password("12345")
.authorities("WRITE")
.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().anyRequest().hasAnyAuthority("WRITE", "READ");
//http.authorizeRequests().anyRequest().hasAuthority("WRITE");
http.authorizeRequests().anyRequest().access("hasAuthority('WRITE')");
}
}
- hasAuthority('WRITE') : 엔드포인트를 호출하려면 사용자에게 WRITE 권한이 필요하다고 지정한다.
- hasAnyAuthoriy('READ', 'WRITE') : 사용자에게 READ 또는 WRITE 권한이 필요하다고 지정한다. 이 식을 이용하면 액세스를 허용할 모든 권한을 나열할 수 있다.
읽기 권한이 있는 사용자는 접근할 수 있지만, 삭제 권한이 있는 사용자는 접근할 수 없다.
=> 이런 복잡한 접근 제어는 access() 메서드를 사용하여 구현한다.
- "hasAuthority('read') and !hasAuthority('delete')" : 읽기권한이 있어야 하지만 삭제 권한은 없는 사용자에게 허용
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Override
@Bean
public UserDetailsService userDetailsService() {
var manager = new InMemoryUserDetailsManager();
var user1 = User.withUsername("john")
.password("12345")
.authorities("read")
.build();
var user2 = User.withUsername("jane")
.password("12345")
.authorities("read", "write", "delete")
.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();
String expression = "hasAuthority('read') and !hasAuthority('delete')";
http.authorizeRequests()
.anyRequest().access(expression);
}
}
7.1.2 사용자 역할을 기준으로 모든 엔드포인트에 대한 접근을 제한
역할은 사용자가 수행할 수 있는 작업을 나타내는 다른 방법이다. 실제 애플리케이션에서는 권한과 함께 역할도 이용되므로 역할의 개념, 그리고 역할과 권한의 차이점을 이해하는 것이 중요하다.
애플리케이션에서 역할을 이용하면 더는 권한을 정의할 필요가 없다. 이때 권한은 개념상으로 존재하고 구현 요구 사항에도 나올 수 있지만 애플리케이션에서는 사용자가 이용 권리를 가진 하나 이싱의 작업을 포함하는 역할만 정의하면 된다.
스프링시큐리티에서 역할도 권한과 같은 GrantedAuthority 계약으로 나타낸다. 역할을 정의할 때 역할 이름은 ROLE_ 접두사로 시작해야 한다. 구현 수준에서 이 접두사는 역할과 권한 간의 차이를 나타낸다.
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
@Override
@Bean
public UserDetailsService userDetailsService() {
var manager = new InMemoryUserDetailsManager();
var user1 = User.withUsername("john")
.password("12345")
// authorities로 지정할때는 ROLE_ 접두사를 적는다.
.authorities("ROLE_ADMIN")
// role로 저정할 때는 ROLE_ 접두사를 적지 않는다.
.build();
var user2 = User.withUsername("jane")
.password("12345")
// authorities로 지정할때는 ROLE 접두사를 적는다.
.authorities("ROLE_MANAGER")
// role로 저정할 때는 ROLE_ 접두사를 적지 않는다.
// .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();
// 여기에는 ROLE_ 접두사를 적지 않는다.
http.authorizeRequests().anyRequest().hasRole("ADMIN");
}
}
- hasRole() : 애플리케이션이 요청을 승인할 하나의 역할 이름을 매개 변수로 받는다.
- hasAnyRole() : 애플리케이션이 요청을 승인할 여러 역할 이름을 매개 변수로 받는다.
- access() : 애플리케이션이 요청을 승인할 역할을 스프링 식으로 지정한다. 역할을 지정하는 데는 hasRole() 또는 hasAnyRole()을 SpEL 식으로 이용할 수 있다.
- 가능하면 hasRole() 또는 hasAnyRole() 메서드를 우선적으로 이용하고, 이러한 메서드로 해결할 수 없는 상황에서만 access()를 이용한다.
authorities() 메서드를 호출할 때는 ROLE_ 접두사를 지정하지만, roles() 메서드에는 ROLE_ 접두사를 포함하지 않는다.
access() 메서드를 이용하면 사실상 어떤 규칙이라도 구현할 수있고 가능성이 무한하다.
쉬운 예로 정오 이후에만 엔드포인트로 접근을 허용하는 경우를 들 수 있다. 이때는 다음과 같은 SpEL 식을 이용할 수 있다.
"T(java.time.LocalTime).now().isAfter(T(java.time.LocalTime).of(12, 0))"
스프링 EL: https://docs.spring.io/spring-framework/reference/core/expressions.html
Spring Expression Language (SpEL) :: Spring Framework
The Spring Expression Language (“SpEL” for short) is a powerful expression language that supports querying and manipulating an object graph at runtime. The language syntax is similar to Unified EL but offers additional features, most notably method inv
docs.spring.io
7.1.3 모든 엔드포인트에 대한 접근 제한
denyAll() 메서드는 모든 요청을 거부한다
경로 변수로 이메일 주소를 받는 엔드포인트가 있을때, .com으로 끝나는 매개 변수 값으로 엔드포인트를 호출하면 요청이 수락된다. .net으로 끝나는 이메일 주소를 지정하고 엔드포인트를 호출하면 요청이 거부된다. 이러한 동작을 구현하려면 매개 변수 값이 .com으로 끝나지 않는 모든 호출을 denyAll() 메서드로 거부하면 된다.
요약
- 권한 부여는 애플리케이션이 인증된 요청을 허가할지 결정하는 프로세스다. 권한 부여는 항상 인증 이후에 수행한다.
- 애플리케이션이 인증된 사용자의 권한과 역할에 따라 권한을 부여하는 방법을 구성할 수 있다.
- 애플리케이션에서 인증되지 않은 사용자가 특정 요청을 수행할 수 있게 지정할 수도 있다.
- denyAll() 메소드로 앱이 모든 요청을 거부하고 permitAll() 메서드로 모든 요청을 수락하게 할 수 있다.
https://shirohoo.github.io/spring/spring-security/2022-03-31-mvcMatchers/
antMatchers vs. mvcMatchers
CVE-2016-5007
shirohoo.github.io
스프링 시큐리티는 모든 요청을 막고 특정 요청만 허용하는 화이트리스트 방식을 선호한다.
요구사항에 따라 모든 요청을 허용하는 블랙리스트 방식을 사용해야한다면 /**를 붙혀주거나 mvcMatchers를 사용한다.