이 장에서는 권한 부여(Authorization)에 대해 다루고 있으며, 이를 통해 인증된 사용자가 시스템의 리소스에 접근할 수 있는지 결정하는 방법을 설명합니다. 권한 부여는 인증 후에 발생하며, 인증된 사용자가 요청한 리소스에 접근할 권한이 있는지를 판단하는 과정입니다. 아래에서 중요한 개념과 단계들을 정리해 보겠습니다.
주요 개념:
- 권한 부여:
- 사용자가 시스템에 로그인하여 인증을 받은 후, 해당 사용자가 특정 리소스에 접근할 수 있는지 판단하는 과정입니다. 이 과정은 애플리케이션에서 중요한 보안 요소로 작용합니다.
- 권한 부여는 인증 이후에 이루어지며, 스프링 시큐리티에서는 인증을 통해 SecurityContext에 저장된 사용자 정보를 바탕으로 권한을 결정합니다.
- 권한 필터:
- 인증이 완료되면, 요청은 권한 부여 필터로 전달되어 사용자가 요청한 리소스에 접근할 권한이 있는지를 확인합니다. 이때 SecurityContext에 저장된 사용자 정보를 기반으로 권한을 검증합니다.
권한과 역할 정의:
- 권한(Authority): 특정 리소스나 기능에 접근할 수 있는 권한을 의미합니다. 예를 들어, "읽기", "쓰기" 권한을 들 수 있습니다.
- 역할(Role): 권한을 그룹화한 개념입니다. 예를 들어, "관리자", "사용자"와 같은 역할을 통해 여러 권한을 묶어 관리할 수 있습니다. 역할은 권한 부여 규칙을 적용하는 데 유용합니다.
이 장에서 다루는 주요 내용:
- 권한 규칙 적용:
- 사용자가 가진 권한에 따라 애플리케이션의 각 엔드포인트에 접근 규칙을 설정하는 방법을 배웁니다. 예를 들어, 관리자만 접근할 수 있는 페이지나, 읽기 권한만 있는 사용자에게 제한된 페이지 접근을 허용하는 방식입니다.
- 역할 기반 권한 부여:
- 권한을 역할로 그룹화하고, 사용자의 역할에 따라 권한을 부여하는 방법을 배웁니다. 예를 들어, "관리자" 역할은 모든 권한을 가질 수 있고, "사용자" 역할은 제한된 권한만 가질 수 있습니다.
권한 부여 규칙 적용:
이 장에서는 권한과 역할을 정의하고, 이를 바탕으로 각 엔드포인트에 접근 규칙을 적용하는 방법을 설명합니다. 실제 애플리케이션에서 권한과 역할을 어떻게 설정하고 이를 통해 리소스 접근을 어떻게 제한할 수 있는지에 대한 논의가 이어집니다.
이제 권한 부여가 인증 후 중요한 보안 작업으로서 어떻게 동작하는지 이해하고, 스프링 시큐리티를 활용하여 이를 구현하는 방법을 배우게 될 것입니다.
7.1 권한 및 역할에 따른 접근 제한
이 섹션에서는 Authorization과 Role 개념을 사용하여 애플리케이션의 엔드포인트에 보안을 적용하는 방법에 대해 다룹니다. 각 사용자가 가진 권한에 따라 애플리케이션에서 수행할 수 있는 작업을 제한하는 방식입니다.
권한과 역할:
- 권한(Authority): 사용자가 수행할 수 있는 특정 작업이나 행동을 의미합니다. 예를 들어, "읽기", "쓰기", "삭제"와 같은 권한이 될 수 있습니다.
- 역할(Role): 권한을 그룹화한 개념입니다. 예를 들어, "관리자" 역할은 더 많은 권한을 가질 수 있고, "일반 사용자" 역할은 제한된 권한만 가질 수 있습니다.
GrantedAuthority 인터페이스:
이 장에서는 GrantedAuthority 인터페이스와 그것이 UserDetails 인터페이스와 어떻게 관계를 맺는지 설명합니다. GrantedAuthority는 사용자가 가진 권한을 나타내며, getAuthority() 메소드를 통해 각 권한의 이름을 반환합니다.
GrantedAuthority 인터페이스 예시:
public interface GrantedAuthority extends Serializable {
String getAuthority(); // 권한의 이름을 반환
}
애플리케이션에서는 권한 부여 규칙을 정의할 때, 권한 이름을 사용하여 사용자가 어떤 행동을 할 수 있는지 명시합니다. 예를 들어, "Jane은 제품 기록을 삭제할 수 있다", "John은 문서 기록을 읽을 수 있다"와 같은 권한 규칙을 설정할 수 있습니다.
UserDetails 인터페이스와 권한:
UserDetails 인터페이스는 사용자에 대한 정보를 제공하는 계약이며, 사용자가 가진 권한을 GrantedAuthority 컬렉션으로 반환하는 getAuthorities() 메소드를 제공합니다. 이 메소드는 사용자가 로그인 후 부여받은 권한들을 애플리케이션에서 활용할 수 있도록 합니다.
UserDetails 인터페이스 예시:
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities(); // 사용자의 권한을 반환
// 기타 코드 생략
}
권한 부여 과정:
인증이 완료되면, 애플리케이션은 SecurityContext에 저장된 사용자의 권한 정보를 사용하여 사용자가 리소스에 접근할 수 있는지 결정합니다. 이 과정은 인증 후 권한 부여로, 인증된 사용자가 각 엔드포인트에 접근할 권한이 있는지를 검증합니다.
권한 이름 예시:
- Read: 문서나 데이터의 읽기 권한
- Write: 문서나 데이터의 쓰기 권한
- Delete: 문서나 데이터의 삭제 권한
권한 이름은 규칙에 따라 정의하며, 이 규칙에 따라 액세스 제어를 설정합니다.
이러한 개념을 바탕으로, 애플리케이션의 각 엔드포인트에 대한 권한 부여 규칙을 설정하여 다양한 사용자가 적절한 권한을 가진 경우에만 특정 리소스에 접근할 수 있도록 합니다.
7.1.1 사용자 권한에 따른 엔드포인트 접근 제한
이 섹션에서는 특정 사용자에게 특정 엔드포인트에 대한 접근을 제한하는 방법에 대해 다룹니다. 이전 예제에서는 인증된 모든 사용자가 애플리케이션의 모든 엔드포인트에 접근할 수 있었습니다. 하지만 실제 애플리케이션에서는 인증되지 않은 상태에서도 일부 엔드포인트에 접근할 수 있지만, 일부 엔드포인트는 특정 권한을 가진 사용자만 접근할 수 있도록 제한됩니다.
권한 개념
권한은 사용자가 애플리케이션에서 수행할 수 있는 행동을 정의합니다. 예를 들어, 사용자가 읽기, 쓰기, 수정 또는 삭제할 수 있는 권한을 가질 수 있습니다. 특정 권한을 가진 사용자만 특정 엔드포인트를 호출할 수 있도록 설정할 수 있습니다. 예를 들어, 'Jane'은 "READ"와 "WRITE" 권한을 가지고, 'John'은 "READ", "WRITE", "DELETE", "UPDATE" 권한을 가질 수 있습니다.
사용자 권한 기반 접근 제한 방법
Spring Security를 사용하여 사용자의 권한에 따라 엔드포인트에 대한 접근을 제한할 수 있습니다. 이를 위한 방법으로는 hasAuthority(), hasAnyAuthority(), access() 메서드를 사용할 수 있습니다.
- hasAuthority(): 특정 권한을 가진 사용자만 접근할 수 있도록 제한합니다.
- hasAnyAuthority(): 여러 권한 중 하나라도 가진 사용자가 접근할 수 있도록 제한합니다.
- access(): 더 복잡한 권한 부여 규칙을 정의할 수 있도록 사용자 정의 객체를 사용하는 방법입니다.
예시 코드
다음 예제에서는 hasAuthority()와 hasAnyAuthority() 메서드를 사용하여 엔드포인트 접근을 제한하는 방법을 설명합니다.
- 의존성 추가 pom.xml 파일에 spring-boot-starter-security와 spring-boot-starter-web 의존성을 추가합니다.
- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
- 사용자 정의 UserDetailsService 설정 InMemoryUserDetailsManager를 사용하여 두 명의 사용자(john, jane)를 설정하고, 각 사용자의 권한을 정의합니다.
- @Configuration public class ProjectConfig { @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(); } }
- 권한 부여 규칙 설정 SecurityFilterChain을 설정하여, 각 엔드포인트에 대한 접근 권한을 설정합니다. 예를 들어, 모든 사용자가 접근할 수 있는 엔드포인트를 설정하거나, 특정 권한을 가진 사용자만 접근할 수 있도록 설정할 수 있습니다.
- 모든 엔드포인트에 대한 접근 허용
- @Configuration public class ProjectConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.httpBasic(Customizer.withDefaults()); http.authorizeHttpRequests(c -> c.anyRequest().permitAll()); return http.build(); } }
- 특정 권한을 가진 사용자만 접근 허용
- @Configuration public class ProjectConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.httpBasic(Customizer.withDefaults()); http.authorizeHttpRequests(c -> c.anyRequest().hasAuthority("WRITE")); return http.build(); } }
- 여러 권한을 가진 사용자 접근 허용
- @Configuration public class ProjectConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.httpBasic(Customizer.withDefaults()); http.authorizeHttpRequests(c -> c.anyRequest().hasAnyAuthority("WRITE", "READ")); return http.build(); } }
- 테스트 curl 명령어를 사용하여 각 사용자(john, jane)가 엔드포인트에 접근할 수 있는지 테스트할 수 있습니다.
- Jane이 엔드포인트를 호출할 때:응답: Hello!
- curl -u jane:12345 http://localhost:8080/hello
- John이 엔드포인트를 호출할 때:응답: 403 Forbidden
- curl -u john:12345 http://localhost:8080/hello
access() 메서드를 사용한 더 복잡한 규칙 설정
access() 메서드는 더욱 복잡한 권한 부여 규칙을 설정할 때 사용됩니다. SpEL(Spring Expression Language)을 사용하여 표현식을 정의할 수 있습니다. 예를 들어, 특정 권한을 가진 사용자만 접근할 수 있도록 설정할 수 있습니다.
@Configuration
public class ProjectConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.httpBasic(Customizer.withDefaults());
http.authorizeHttpRequests(c -> c.anyRequest()
.access("hasAuthority('READ') and !hasAuthority('DELETE')"));
return http.build();
}
}
이 예제에서는 READ 권한을 가진 사용자만 접근할 수 있으며, DELETE 권한을 가진 사용자는 접근할 수 없습니다.
결론
Spring Security를 활용하여 애플리케이션의 엔드포인트에 대한 접근 권한을 세밀하게 제어할 수 있습니다. hasAuthority(), hasAnyAuthority(), access() 메서드를 사용하여 사용자의 권한을 기반으로 엔드포인트에 대한 접근을 제한할 수 있으며, 각 방법은 상황에 맞게 선택하여 사용할 수 있습니다.
7.1.2 사용자 역할 기반 엔드포인트 접근 제한
이 섹션에서는 사용자의 Roles에 따라 엔드포인트에 대한 접근을 제한하는 방법을 다룹니다. Roles는 사용자가 수행할 수 있는 작업을 정의하는 방식으로, 실제 애플리케이션에서도 매우 중요한 개념입니다. Roles과 권한의 차이를 이해하는 것이 중요하며, 이 섹션에서는 여러 실제 예제를 통해 이를 어떻게 적용할 수 있는지 알아봅니다.
역할과 권한의 차이
- Roles: 사용자가 시스템 내에서 수행할 수 있는 작업의 범위를 나타냅니다. 예를 들어, "READ"만 할 수 있는 사용자에게 "READER" 역할을 부여하거나, 모든 권한(읽기, 쓰기, 삭제 등)을 부여할 때 "ADMIN" 역할을 부여할 수 있습니다.
- 권한(Authorities): 시스템 내에서 세부적인 작업을 정의합니다. 예를 들어, "READ", "WRITE", "DELETE"와 같은 권한은 Roles을 구성하는 요소로 볼 수 있습니다.
이 차이를 바탕으로, Roles은 사용자에게 특정 작업을 허용하는 그룹을 정의하는데 사용되며, 이를 통해 접근 제어를 간편하게 관리할 수 있습니다.
역할 설정 예시
Spring Security에서 Roles은 GrantedAuthority 인터페이스를 구현하여 사용됩니다. Roles 이름은 반드시 ROLE_ 접두사를 포함해야 하며, 실제 시스템에서는 이를 통해 사용자의 접근 권한을 제어합니다.
다음은 Roles을 설정하는 예제입니다:
@Configuration
public class ProjectConfig {
@Bean
public UserDetailsService userDetailsService() {
var manager = new InMemoryUserDetailsManager();
var user1 = User.withUsername("john")
.password("12345")
.authorities("ROLE_ADMIN") // A
.build();
var user2 = User.withUsername("jane")
.password("12345")
.authorities("ROLE_MANAGER")
.build();
manager.createUser(user1);
manager.createUser(user2);
return manager;
}
}
위 예제에서 authorities() 메서드는 ROLE_ 접두사를 사용하여 각 사용자의 역할을 설정합니다. 예를 들어, "john"은 ROLE_ADMIN 역할을 가지고 있고, "jane"은 ROLE_MANAGER 역할을 가집니다.
엔드포인트 접근 제한 설정
엔드포인트에 대한 접근을 제한하려면 hasRole() 메서드를 사용하여 Roles을 기반으로 조건을 설정할 수 있습니다.
@Configuration
public class ProjectConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.httpBasic(Customizer.withDefaults());
http.authorizeHttpRequests(
c -> c.anyRequest().hasRole("ADMIN") // A
);
return http.build();
}
}
위 코드에서는 hasRole() 메서드를 사용하여, ROLE_ADMIN 역할을 가진 사용자만 모든 요청에 접근할 수 있도록 설정합니다. hasRole() 메서드는 Roles 이름을 인자로 받으며, ROLE_ 접두사는 생략됩니다.
역할과 권한을 설정하는 방법
Spring Security에서는 두 가지 방법으로 사용자의 Roles를 설정할 수 있습니다:
- authorities() 메서드: ROLE_ 접두사를 포함한 문자열로 역할을 설정합니다.
- roles() 메서드: ROLE_ 접두사를 포함하지 않는 역할 이름으로 설정합니다.
@Configuration
public class ProjectConfig {
@Bean
public UserDetailsService userDetailsService() {
var manager = new InMemoryUserDetailsManager();
var user1 = User.withUsername("john")
.password("12345")
.roles("ADMIN") // A
.build();
var user2 = User.withUsername("jane")
.password("12345")
.roles("MANAGER")
.build();
manager.createUser(user1);
manager.createUser(user2);
return manager;
}
}
이 예제에서는 roles() 메서드를 사용하여 역할을 설정하고, ROLE_ 접두사를 생략합니다. 이 방법은 authorities() 메서드와 비슷하지만 역할을 간단하게 설정할 수 있게 합니다.
access() 메서드 사용
access() 메서드를 사용하면 더 복잡한 조건을 설정할 수 있습니다. 예를 들어, SpEL(Spring Expression Language)을 사용하여 특정 조건에 따라 엔드포인트 접근을 제한할 수 있습니다.
http.authorizeHttpRequests()
.requestMatchers("/admin").access("hasRole('ADMIN') and T(java.time.LocalTime).now().isAfter(T(java.time.LocalTime).of(12, 0))");
위 예제는 /admin 경로에 대해, 사용자가 ADMIN 역할을 가지고 있으며, 현재 시간이 12시 이후일 때만 접근을 허용하는 규칙을 설정한 것입니다. SpEL을 사용하면 매우 유연한 규칙을 작성할 수 있습니다.
결론
이 섹션에서는 Spring Security에서 Roles을 사용하여 엔드포인트에 대한 접근을 제한하는 방법을 배웠습니다. Roles은 사용자가 수행할 수 있는 작업을 정의하며, hasRole() 및 access() 메서드를 사용하여 이를 기반으로 접근 제어를 설정할 수 있습니다. roles()와 authorities() 메서드를 통해 사용자의 역할을 정의하는 방식에도 차이가 있으며, 이를 적절히 활용하여 애플리케이션 보안을 강화할 수 있습니다.
7.1.3 모든 엔드포인트에 대한 접근 제한
이 섹션에서는 모든 요청에 대한 접근을 제한하는 방법을 다룹니다. 이전에 permitAll() 메서드를 사용하여 모든 요청에 대한 접근을 허용하는 방법을 배웠다면, 이번에는 그 반대로 모든 요청을 거부하는 방법에 대해 알아봅니다. 이를 위해 denyAll() 메서드를 사용할 수 있습니다.
denyAll() 메서드 사용
denyAll() 메서드는 모든 요청에 대한 접근을 거부하는 방법을 제공합니다. 이 메서드는 permitAll()의 반대 동작을 하며, 특정 요구 사항에 따라 유용하게 사용될 수 있습니다.
@Configuration
public class ProjectConfig {
// 생략된 코드
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.httpBasic(Customizer.withDefaults());
http.authorizeHttpRequests(
c -> c.anyRequest().denyAll() // A
);
return http.build();
}
}
위 코드에서 denyAll() 메서드는 모든 요청에 대해 접근을 거부하도록 설정합니다. 이는 애플리케이션의 모든 엔드포인트에 대해 접근을 제한할 때 유용합니다.
denyAll()의 사용 사례
denyAll() 메서드는 일반적인 사용 사례에서는 자주 사용되지 않지만, 특정 조건에서 유용할 수 있습니다. 예를 들어, 특정 형식의 이메일 주소에 대해서만 요청을 허용하려는 경우가 있습니다.
- 예를 들어, URL 템플릿 path 변수로 이메일 주소를 받는 엔드포인트가 있고, 이메일 주소가 .com으로 끝나는 경우에만 요청을 허용하고 싶다면, 정규 표현식을 사용하여 이 조건을 설정하고 denyAll()을 통해 .com 형식이 아닌 이메일 주소의 요청을 거부할 수 있습니다.
이와 같은 시나리오에서, .com으로 끝나는 이메일 주소를 받는 요청만 허용하고, 나머지 형식의 이메일 주소를 거부하려면 denyAll() 메서드를 사용하여 규칙에 맞지 않는 요청을 차단할 수 있습니다.
복잡한 아키텍처에서의 사용
복잡한 아키텍처에서 denyAll() 메서드는 특정 경로에 대한 요청을 제한하는 데 유용할 수 있습니다. 예를 들어, 여러 개의 게이트웨이 서비스가 있고, 각 게이트웨이는 자신이 처리하는 특정 경로에만 요청을 허용하며, 나머지 경로에 대한 요청은 거부해야 하는 경우입니다.
+--------------------+ +--------------------+
| Gateway A | | Gateway B |
| /products path | | /articles path |
+--------------------+ +--------------------+
| |
+--------> denyAll() <---+
이 시나리오에서는 Gateway A는 /products 경로에만 접근을 허용하고, Gateway B는 /articles 경로에만 접근을 허용합니다. 다른 경로에 대한 요청은 각각의 게이트웨이에서 denyAll()을 사용하여 거부됩니다.
결론
denyAll() 메서드는 모든 요청을 거부하는 강력한 방법으로, 특정 조건에 맞지 않는 요청을 차단하는 데 유용하게 사용할 수 있습니다. 복잡한 시스템에서는 경로 기반으로 요청을 제한하거나, 특정 형식의 데이터만을 허용하는 등의 요구 사항에 맞춰 denyAll()을 활용할 수 있습니다. 이 방법은 프레임워크가 제공하는 유연성을 극대화할 수 있는 중요한 도구입니다.
7.2 요약
권한 부여는 인증된 요청이 애플리케이션에서 허용될지 여부를 결정하는 과정입니다. 권한 부여는 항상 인증 후에 이루어지며, 애플리케이션은 인증된 사용자의 권한과 **역할(Role)**을 기반으로 요청을 승인하거나 거부하는 방식을 구성합니다.
- 인증되지 않은 사용자도 특정 요청을 할 수 있도록 설정할 수 있습니다.
- 애플리케이션은 denyAll() 메서드를 사용하여 모든 요청을 거부하거나, permitAll() 메서드를 사용하여 모든 요청을 허용할 수 있습니다.
이를 통해 애플리케이션의 보안 정책을 유연하게 관리할 수 있으며, 요구 사항에 따라 접근 제어를 설정할 수 있습니다.
'Spring Security in Action' 카테고리의 다른 글
9장 Configuring Cross-Site Request Forgery(CSRF) protection (0) | 2024.12.01 |
---|---|
8장 Configuring endpoint-level authorization: Applying restrictions (0) | 2024.12.01 |
5장 A web app's security begins with filters (0) | 2024.12.01 |
4장 비밀번호 인코더 구현 및 작업 (0) | 2024.12.01 |
3장 Managing users (0) | 2024.12.01 |