Spring/Spring Security

[Spring Security] 4-2. Authorization: HttpServletRequests

noahkim_ 2025. 7. 11. 10:12
  • request levet에서의 인가를 구현할 모델을 제공합니다.
  • 특정 prefix의 url에 대한 인가 권한을 요구할 수 있음
  • HttpSecurity 객체를 통해 인가 룰을 정의할 수 있음

 

1. Understanding How Request Authorization Components Work

 

인가 동작 순서 (AuthorizationFilter)

  1. SecurityContextHolder에서 인증 정보를 가져오는 Supplier<Authentication> 생성
  2. Supplier<Authentication>와 HttpServletRequest를 AuthorizationManager에 전달
  3. authorizeHttpRequests()에 정의된 규칙들과 매칭하여 인가 판정

 

인가 실패
  • AuthorizationDeniedEvent 이벤트 발생
  • AccessDeniedException이 던져짐
  • ExceptionTranslationFilter가 AccessDeniedException 예외 처리
    • AccessDeniedHandler가 예외 처리 담당

 

인가 성공

 

  • 다음 필터체인으로 진행

 

AuthorizationFilter Is Last By Default

  • 인가는 필터 맨 마지막에 진행됨
  • MVC Controller나 View 요청은 authorizeHttpRequests() 인가를 통과해야 접근 허용됨

 

All Dispatches Are Authorized

  • 즉, REQUEST, FORWARD, INCLUDE, ERROR 디스패치 모두 인가 판단 대상임
  • 디스패치별 중복 인가를 방지하는 설정 가능

 

예시) 디스패치별 중복 인가 발생

더보기
  • 내부적으로 Thymeleaf 같은 템플릿으로 렌더링하기 위해 FORWARD할 때 인가 발생
  • 예외 발생으로 인해 Spring Boot 기본 에러 페이지 응답 시, ERROR로 디스패치되므로 인가 발생

 

설정) 중복 인가 방지

더보기
http.authorizeHttpRequests((requests) -> requests
    .dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR).permitAll()
    .anyRequest().authenticated()
);
  • FORWARD, ERROR 디스패치는 중복 인가를 수행하지 않음

 

Authentication Lookup is Deferred

 

  • AuthorizationManager는 Supplier<Authentication>를 사용함
  • 인가 필요 없다고 판단되면, 인증 객체를 조회하지 않음 (permitAll(): 인증 정보 조회 안함)
  • 성능 최적화에 유리

 

 

2. Authorizing an Endpoint

특정 엔드포인트에 권한 인가 설정하기

  • Spring Security는 authorizeHttpRequests()에 설정한 패턴/츄기 쌍을 순서대로 평가함
  • 첫번째 매칭되는 규칙만 적용

 

코드

더보기
@Bean
public SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .requestMatchers("/endpoint").hasAuthority("USER")
            .anyRequest().authenticated()
        );
    return http.build();
}

 

테스트 코드로 인가 규칙 검증

@WithMockUser로 api 요청에 쓰일 가짜 사용자 정의 및 주입 가능

 

예시

더보기
@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()); // USER 권한이 없으므로 403
}
@Test
void anyWhenUnauthenticatedThenUnauthorized() {
    this.mvc.perform(get("/any"))
        .andExpect(status().isUnauthorized()); // 인증 자체가 안 되어 있으므로 401
}

 

3. Matching Requests

  • 요청을 매칭하여 인가를 적용하는 다양한 방법을 지원함
매칭 방식 특징
Matching Using Ant
기본 매칭 방식
와일드카드 지원 (*, **)
경로 변수 활용 가능
Matching Using Regular Expressions
정규식을 사용하여 경로를 정밀하게 매칭 가능
복잡한 규칙 표현에 적합
Matching By Http Method
HTTP 메서드 별로 인가 규칙 적용 가능 (GET, POST 등)
Matching By Dispatcher Type
서블릿 디스패치 종류별로 인가 처리 설정 가능 (FORWARD, ERROR 등)
Matching by Servlet Path
여러 DispatcherServlet이 있는 경우, 서블릿 prefix에 따라 경로 매칭 가능
Using a Custom Matcher
RequestMatcher 구현 또는 람다 사용
요청 속성 기반으로 유연한 매칭 가능

 

예시) ant

더보기

와일드 카드

.requestMatchers("/resource/**").hasAuthority("USER")

 

경로 변수

.requestMatchers("/resource/{name}")
    .access(new WebExpressionAuthorizationManager("#name == authentication.name"))

 

예시) 정규식

더보기
.requestMatchers(RegexRequestMatcher.regexMatcher("/resource/[A-Za-z0-9]+"))
	.hasAuthority("USER")

 

예시) HTTP 메서드 

더보기
.requestMatchers(HttpMethod.GET).hasAuthority("read")
.requestMatchers(HttpMethod.POST).hasAuthority("write")

 

예시) DispatcherType

더보기
.dispatcherTypeMatchers(DispatcherType.FORWARD, DispatcherType.ERROR)
	.permitAll()

 

예시) Servlet Path

더보기
PathPatternRequestMatcher.Builder mvc = withDefaults().basePath("/spring-mvc");

.requestMatchers(mvc.matcher("/admin/**")).hasAuthority("admin")

 

예시) custom matcher

더보기

request parameter

RequestMatcher printview = (request) -> request.getParameter("print") != null;

.requestMatchers(printview).hasAuthority("print")
  • 쿼리 파라미터 존재 여부에 따라 인가 체크

 

4. Authorizing Requests

Authorization DSL

DSL 메서드 설명
permitAll()
모든 사용자 허용.
- 인증 정보조차 조회하지 않음.
- 퍼블릭 엔드포인트 처리에 사용
denyAll()
무조건 접근 거부
- 인증 정보도 조회 안 함
- 보안상 기본값이나 fallback 용도로 사용
hasAuthority("X")
인증 객체에 GrantedAuthority("X")가 있어야 접근 가능
hasRole("X")
hasAuthority("ROLE_X")의 축약형
- 내부적으로 ROLE_ prefix 자동 추가
hasAnyAuthority("A", "B")
주어진 권한 중 하나라도 있으면 접근 허용
hasAnyRole("A", "B")
각각 ROLE_A, ROLE_B로 변환 후 검사
access(...)
커스텀 AuthorizationManager 로직 적용
복합 조건 가능 (allOf, anyOf 등) 

 

예제) 복합 인가

더보기
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(authorize -> authorize                                  
            .dispatcherTypeMatchers(FORWARD, ERROR).permitAll() 
            .requestMatchers("/static/**", "/signup", "/about").permitAll()         
            .requestMatchers("/admin/**").hasRole("ADMIN")                             
            .requestMatchers("/db/**").access(allOf(hasAuthority("db"), hasRole("ADMIN")))   
            .anyRequest().denyAll()                                                
        );
	
    return http.build();
}

 

5. Expressing Authorization with SpEL

주요 SpEL 메서드

표현식 설명
permitAll
누구나 접근 가능. (인증 정보 조회도 안 함)
denyAll
무조건 거부. (인증 정보 조회도 안 함)
hasAuthority('X')
Authentication 객체에 GrantedAuthority("X") 권한이 있어야 접근 가능
hasRole('X')
hasAuthority('ROLE_X')와 동일. ROLE_ prefix 자동 추가됨
hasAnyAuthority(...)
여러 권한 중 하나라도 만족하면 접근 허용
hasAnyRole(...)
각각 ROLE_이 붙은 권한 중 하나라도 만족하면 허용
hasPermission(...)
PermissionEvaluator를 활용한 도메인 객체 수준 인가

 

SpEL 표현식에서 사용 가능한 주요 필드

필드명 설명
authentication
현재 인증된 사용자에 대한 Authentication 객체
principal
인증된 사용자의 principal 정보 (getPrincipal())
#변수명
경로 변수.
예: /resource/{name}에서 #name으로 참조 가능

 

6. Migrating from authorizeRequests

7. Security Matchers

항목 securityMatcher()
requestMatchers()
역할 보안 설정이 어떤 요청에 적용될지 결정
인가 규칙을 어떤 요청에 적용할지 결정
필터 체인 분기용 ✅ (다중 필터 체인 구성 시 사용됨)
❌ (필터 체인 내에서 인가 제어에만 사용됨)
위치 http.securityMatcher(...)
authorizeHttpRequests().requestMatchers(...) 내부에서 사용
예시 /api/** 요청에만 해당 보안 설정을 적용
/api/admin/** 요청은 ROLE_ADMIN만 접근 가능

 

예시

더보기
http
	.securityMatcher("/api/**")  // 보안 필터체인 적용 범위: /api/** 요청만
	.authorizeHttpRequests(authz -> authz
		.requestMatchers("/api/user/**").hasRole("USER")       // USER 역할 필요
		.requestMatchers("/api/admin/**").hasRole("ADMIN")     // ADMIN 역할 필요
		.anyRequest().authenticated()                          // 그 외는 인증만 필요
	)

 

출처