Authorize HttpServletRequests
개요
Spring Security를 사용하면 요청 수준에서 권한을 모델링 할 수 있습니다 .
예를 들어 Spring Security를 사용하면 모든 페이지에 /admin하나의 권한이 필요하고 다른 모든 페이지에는 인증만 필요하다고 말할 수 있습니다 .
기본적으로 Spring Security는 모든 요청이 인증되어야 합니다. 즉, 인스턴스를 사용할 때마다 권한 HttpSecurity부여 규칙을 선언해야 합니다.
인스턴스 가 있을 때마다 HttpSecurity적어도 다음을 수행해야 합니다.
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
대부분의 경우 승인 규칙은 이보다 더 복잡하므로 다음 사용 사례를 고려해야합니다.
- 사용하는 앱이 있어서 마이그레이션하고authorizeRequests 싶어요authorizeHttpRequests
- 구성 요소가 어떻게 작동하는지 이해 하고 싶습니다AuthorizationFilter
- 패턴, 특히 정규 표현식을 기반으로 요청을 일치시키고 싶습니다.
- 요청을 일치시키고 싶고 Spring MVC를 기본 서블릿이 아닌 다른 것으로 매핑하고 싶습니다.
- 요청을 승인 하고 싶습니다
- 요청을 프로그래밍 방식으로 일치시키고 싶습니다.
- 프로그래밍 방식으로 요청을 승인 하고 싶습니다.
- 요청 권한을 정책 에이전트에게 위임 하고 싶습니다 .
요청 인증 구성 요소의 작동 방식 이해
- 먼저, SecurityContextHolder 에서 인증을 검색하는 AuthorizationFilter구성 요소가 있습니다 .
- 둘째, Supply<Authentication>과 HttpServletRequest를 AuthorizationManager에 전달합니다. AuthorizationManager는 요청을 AuthorizeHttpRequests의 패턴과 일치 시키고 해당 규칙을 실행합니다.
- 승인이 거부되고 AuthorizationDeniedEvent가 게시되며 AccessDeniedException이 발생합니다. 이 경우 ExceptionTranslationFilter는 AccessDeniedException을 처리합니다.
- 액세스가 허용되면 AuthorizationGrantedEvent가 게시되고 AuthorizationFilter는 애플리케이션이 정상적으로 처리될 수 있도록 FilterChain을 계속 사용합니다.
Authorizing an Endpoint
AuthorizationManager API는 Manufacturer<Authentication>을 사용한다는 점을 기억하세요. 요청이 항상 허용되거나 항상 거부되는 경우 AuthorizeHttpRequests에 중요합니다. 이러한 경우 인증이 조회 되지 않아 더 빠른 요청이 가능합니다. Authorize an Endpoint
@Bean
public SecurityFilterChain web(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/endpoint").hasAuthority("USER")
.anyRequest().authenticated()
)
// ...
return http.build();
}
AuthorizationFilter는 이러한 쌍을 나열된 순서대로 처리하여 요청에 첫 번째 일치 항목만 적용합니다. 이는 /**도 /endpoint와 일치하더라도 위 규칙은 문제가 되지 않음을 의미합니다.
위 규칙을 읽는 방법은 "요청이 /endpoint인 경우 사용자 권한이 필요하며, 그렇지 않은 경우 인증만 필요"입니다
스프링 보안은 여러 패턴과 몇 가지 규칙을 지원하며, 각각의 패턴을 프로그래밍 방식으로 작성할 수도 있으며, 하나의 다음과 같은 방법으로 Security의 테스트 지원을 사용하여 테스트할 수 있습니다.
Test Endpoint Authorization
@WithMockUser(authorities="USER")
@Test
void endpointWhenUserAuthorityThenAuthorized() {
this.mvc.perform(get("/endpoint"))
.andExpect(status().isOk());
}
@WithMockUser
@Test
void endpointWhenNotUserAuthorityThenForbidden() {
this.mvc.perform(get("/endpoint"))
.andExpect(status().isForbidden());
}
@Test
void anyWhenUnauthenticatedThenUnauthorized() {
this.mvc.perform(get("/any"))
.andExpect(status().isUnauthorized());
}
Matching Requests
두 번째는 URI 패턴으로 일치시키는 것입니다. Spring Security는 URI 패턴 일치를 위해 Ant(위에서 볼 수 있음)와 정규 표현식이라는 두 가지 언어를 지원합니다.
Matching Using Ant
Ant는 Spring Security가 요청을 일치시키기 위해 사용하는 기본 언어입니다.
이를 사용하여 단일 엔드포인트 또는 디렉터리를 일치시킬 수 있으며 나중에 사용할 수 있도록 자리 표시자를 캡처할 수도 있습니다.
특정 HTTP 메소드 세트와 일치하도록 이를 구체화할 수도 있습니다.
Match with Ant
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/resource/**").hasAuthority("USER")
.anyRequest().authenticated()
)
이를 읽는 방법은 "요청이 /resource 또는 일부 하위 디렉터리인 경우 USER 권한이 필요하고, 그렇지 않으면 인증만 필요합니다"입니다. 아래와 같이 요청에서 경로 값을 추출할 수도 있습니다.
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/resource/{name}").access(new WebExpressionAuthorizationManager("#name == authentication.name"))
.anyRequest().authenticated()
)
다음과 같은 방법으로 보안의 테스트 지원을 사용하여 테스트할 수 있습니다.
Test Directory Authorization
@WithMockUser(authorities="USER")
@Test
void endpointWhenUserAuthorityThenAuthorized() {
this.mvc.perform(get("/endpoint/jon"))
.andExpect(status().isOk());
}
@WithMockUser
@Test
void endpointWhenNotUserAuthorityThenForbidden() {
this.mvc.perform(get("/endpoint/jon"))
.andExpect(status().isForbidden());
}
@Test
void anyWhenUnauthenticatedThenUnauthorized() {
this.mvc.perform(get("/any"))
.andExpect(status().isUnauthorized());
}
Matching Using Regular Expressions
Spring Security는 정규 표현식에 대한 일치 요청을 지원합니다. 이는 하위 디렉터리에 **보다 더 엄격한 일치 기준을 적용하려는 경우 유용할 수 있습니다. 예를 들어 사용자 이름이 포함된 경로와 모든 사용자 이름이 영어 또는 숫자여야 한다는 규칙을 생각해 보세요. 다음과 같이 RegexRequestMatcher를 사용하여 이 규칙을 준수할 수 있습니다.
Match with Regex
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(RegexRequestMatcher.regexMatcher("/resource/[A-Za-z0-9]+")).hasAuthority("USER")
.anyRequest().denyAll()
)
Matching By Http Method
HTTP 방법으로 규칙을 일치 시킬 수도 있습니다. 이것이 편리한 곳 중 하나는 읽기 또는 쓰기 권한 부여와 같이 부여된 권한으로 인증할 때입니다. 모든 GET에 읽기 권한이 필요하고 모든 POST에 쓰기 권한이 필요하도록 하려면 다음과 같이 하면 됩니다.
Match by HTTP Method
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(HttpMethod.GET).hasAuthority("read")
.requestMatchers(HttpMethod.POST).hasAuthority("write")
.anyRequest().denyAll()
)
Test Http Method Authorization
@WithMockUser(authorities="read")
@Test
void getWhenReadAuthorityThenAuthorized() {
this.mvc.perform(get("/any"))
.andExpect(status().isOk());
}
@WithMockUser
@Test
void getWhenNoReadAuthorityThenForbidden() {
this.mvc.perform(get("/any"))
.andExpect(status().isForbidden());
}
@WithMockUser(authorities="write")
@Test
void postWhenWriteAuthorityThenAuthorized() {
this.mvc.perform(post("/any").with(csrf()))
.andExpect(status().isOk());
}
@WithMockUser(authorities="read")
@Test
void postWhenNoWriteAuthorityThenForbidden() {
this.mvc.perform(get("/any").with(csrf()))
.andExpect(status().isForbidden());
}
Matching By Dispatcher Type
앞서 언급했듯이 Spring Security는 기본적으로 모든 디스패처 유형을 승인합니다. REQUEST 디스패치에 설정된 보안 컨텍스트가 후속 디스패치로 이어지더라도 미묘한 불일치로 인해 예상치 못한 AccessDeniedException이 발생할 수 있습니다.
이를 해결하기 위해 FORWARD 및 ERROR와 같은 디스패처 유형을 허용하도록 Spring Security Java 구성을 구성할 수 있습니다.
Example 1. Match by Dispatcher Type
http
.authorizeHttpRequests((authorize) -> authorize
.dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
.requestMatchers("/endpoint").permitAll()
.anyRequest().denyAll()
)
Using an MvcRequestMatcher
일반적으로 위에서 설명한 대로 requestMatchers(String)을 사용할 수 있습니다. 그러나 Spring MVC를 다른 서블릿 경로에 매핑하는 경우 보안 구성에서 이를 고려해야 합니다. 예를 들어 Spring MVC가 /(기본값) 대신 /spring-mvc에 매핑된 경우 인증하려는 /spring-mvc/my/controller와 같은 엔드포인트가 있을 수 있습니다.
구성에서 서블릿 경로와 컨트롤러 경로를 분할하려면 MvcRequestMatcher를 사용해야 합니다.
Example 2. Match by MvcRequestMatcher
@Bean
MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) {
return new MvcRequestMatcher.Builder(introspector).servletPath("/spring-mvc");
}
@Bean
SecurityFilterChain appEndpoints(HttpSecurity http, MvcRequestMatcher.Builder mvc) {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(mvc.pattern("/my/controller/**")).hasAuthority("controller")
.anyRequest().authenticated()
);
return http.build();
}
이러한 필요성은 적어도 두 가지 다른 방식으로 발생할 수 있습니다.
- spring.mvc.servlet.path 부팅 속성을 사용하여 기본 경로(/)를 다른 경로로 변경하는 경우
- 둘 이상의 Spring MVC DispatcherServlet을 등록하는 경우(따라서 그 중 하나가 기본 경로가 아니어야 함)
Using a Custom Matcher
Java 구성에서는 다음과 같이 자신만의 RequestMatcher를 생성하여 DSL에 제공할 수 있습니다.
Example 3. Authorize by Dispatcher Type
RequestMatcher printview = (request) -> request.getParameter("print") != null;
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(printview).hasAuthority("print")
.anyRequest().authenticated()
)
Test Custom Authorization
@WithMockUser(authorities="print")
@Test
void printWhenPrintAuthorityThenAuthorized() {
this.mvc.perform(get("/any?print"))
.andExpect(status().isOk());
}
@WithMockUser
@Test
void printWhenNoPrintAuthorityThenForbidden() {
this.mvc.perform(get("/any?print"))
.andExpect(status().isForbidden());
}
Authorizing Requests
요청이 일치하면, allowedAll,denyAll, hasAuthority와 같이 이미 본 여러 가지 방법으로 요청을 승인할 수 있습니다.
간략하게 요약하면 다음은 DSL에 내장된 인증 규칙입니다.
- permitAll - 요청에는 승인이 필요하지 않으며 공개 엔드포인트입니다. 이 경우 세션에서 인증이 검색되지 않습니다.
- denyAll - 어떠한 상황에서도 요청이 허용되지 않습니다. 이 경우 세션에서 인증이 검색되지 않습니다.
- hasAuthority - 요청에는 인증에 주어진 값과 일치하는 GrantedAuthority가 있어야 합니다.
- hasRole - ROLE_ 접두사 또는 기본 접두사로 구성된 모든 항목을 포함하는 hasAuthority에 대한 바로가기
- hasAnyAuthority - 요청에는 인증에 지정된 값과 일치하는 GrantedAuthority가 있어야 합니다.
- hasAnyRole -ROLE_ 접두사 또는 기본 접두사로 구성된 항목을 지정하는 hasAnyAuthority에 대한 바로가기
- access - 요청은 이 맞춤 AuthorizationManager를 사용하여 액세스를 결정합니다.
import static jakarta.servlet.DispatcherType.*;
import static org.springframework.security.authorization.AuthorizationManagers.allOf;
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasAuthority;
import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole;
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
// ...
.authorizeHttpRequests(authorize -> authorize (1)
.dispatcherTypeMatchers(FORWARD, ERROR).permitAll() (2)
.requestMatchers("/static/**", "/signup", "/about").permitAll() (3)
.requestMatchers("/admin/**").hasRole("ADMIN") (4)
.requestMatchers("/db/**").access(allOf(hasAuthority("db"), hasRole("ADMIN"))) (5)
.anyRequest().denyAll() (6)
);
return http.build();
}
(1) 여러 권한 부여 규칙이 지정되어 있습니다. 각 규칙은 선언된 순서대로 고려됩니다.
(2) FORWARD 및 ERROR 디스패치는 Spring MVC가 뷰를 렌더링하고 Spring Boot가 오류를 렌더링하도록 허용됩니다.
(3) 우리는 모든 사용자가 액세스할 수 있는 여러 URL 패턴을 지정했습니다. 특히 URL이 "/static/"으로 시작하거나 "/signup" 또는 "/about"과 같으면 모든 사용자가 요청에 액세스할 수 있습니다.
(4) "/admin/"으로 시작하는 모든 URL은 "ROLE_ADMIN" 역할을 가진 사용자로 제한됩니다. hasRole 메소드를 호출하기 때문에 "ROLE_" 접두사를 지정할 필요가 없다는 것을 알 수 있습니다.
(5) "/db/"로 시작하는 모든 URL은 사용자에게 "db" 권한과 "ROLE_ADMIN" 권한이 모두 부여되어야 합니다. hasRole 표현식을 사용하고 있으므로 'ROLE_' 접두사를 지정할 필요가 없습니다.
(6) 아직 일치하지 않은 URL은 액세스가 거부됩니다. 실수로 권한 부여 규칙 업데이트를 잊어버리지 않으려는 경우 이는 좋은 전략입니다.
Security Matchers
RequestMatcher 인터페이스는 요청이 특정 규칙과 일치하는지 확인하는 데 사용됩니다. 우리는 HttpSecurity가 부여하는 요청에 적용해야 한다고 결정하기 위해 securityMatchers를 사용합니다. 동일한 방식으로 requestMatchers를 사용하여 특정 요청에 적용해야 하는 인증 규칙을 결정할 수 있습니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/api/**")
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/user/**").hasRole("USER")
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(withDefaults());
return http.build();
}
}
(1) /api/로 시작하는 URL에만 적용되도록 HttpSecurity를 구성합니다.
(2) USER 역할을 가진 사용자에게 /user/로 시작하는 URL에 대한 액세스를 허용합니다.
(3) ADMIN 역할을 가진 사용자에게 /admin/으로 시작하는 URL에 대한 액세스를 허용합니다.
(4) 위의 규칙과 일치하지 않는 기타 요청에는 인증이 필요합니다.
securityMatcher(s) 및 requestMatcher(s) 메소드는 애플리케이션에 가장 적합한 RequestMatcher 구현을 결정합니다. Spring MVC가 클래스 경로에 있으면 MvcRequestMatcher가 사용되고, 그렇지 않으면 AntPathRequestMatcher가 사용됩니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.securityMatcher(antMatcher("/api/**")) // 2
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(antMatcher("/user/**")).hasRole("USER") // 3
.requestMatchers(regexMatcher("/admin/.*")).hasRole("ADMIN") // 4
.requestMatchers(new MyCustomRequestMatcher()).hasRole("SUPERVISOR") //5
.anyRequest().authenticated()
)
.formLogin(withDefaults());
return http.build();
}
}
public class MyCustomRequestMatcher implements RequestMatcher {
@Override
public boolean matches(HttpServletRequest request) {
// ...
}
}
- AntPathRequestMatcher 및 RegexRequestMatcher에서 정적 팩토리 메소드를 가져와서 RequestMatcher 인스턴스를 생성하십시오.
- AntPathRequestMatcher를 사용하여 /api/로 시작하는 URL에만 적용되도록 HttpSecurity를 구성합니다.
- AntPathRequestMatcher를 사용하여 USER 역할을 가진 사용자에게 /user/로 시작하는 URL에 대한 액세스를 허용합니다.
- RegexRequestMatcher를 사용하여 ADMIN 역할이 있는 사용자에게 /admin/으로 시작하는 URL에 대한 액세스를 허용합니다.
- 사용자 정의 RequestMatcher를 사용하여 SUPERVISOR 역할이 있는 사용자에게 MyCustomRequestMatcher와 일치하는 URL에 대한 액세스를 허용합니다.
참고 링크 : https://docs.spring.io/spring-security/reference/servlet/authorization/architecture.html