본문 바로가기
Java Spring/Security

Authorization(인가)-인가 내부 절차 & 권한 설정하기

by GGShin 2022. 8. 1.

지난번에는 Authentication(인증)에 대해 알아보았고, 이번에는 사용자마다 접근 권한을 다르게 설정하는 authorization(인가)에 대해서 알아보겠습니다. 블로그에 글을 쓰려면 해당 블로그에 대한 관리자 권한이 있어야 글 쓰기 및 수정이 가능합니다. 해당 블로그 수정 권한이 없는 유저는 글 수정 페이지에 접근할 수가 없습니다. 웹사이트를 이용할 때 이런식으로 권한을 차등적으로 부여하는 경우가 상당히 많이 있습니다. Spring Security에서도 이런 권한 부여와 관련된 기능을 제공하는데, 내부 절차는 어떤지와 어떻게 구현하는지 차근차근 알아보겠습니다.

 

Authorization 내부 절차

인가 작업은 FilterSecurityInterceptor에서 이루어질 수 있습니다. 

console창에 찍혀 나오는 security filter chain 내부 기본 security filter 중에는 가장 마지막에서 시행되나 봅니다. 

 

 

[Default]

 

🏷 공식문서를 보니, FilterSecurityInterceptor 대신 AuthorizationFilter에서 앞으로 인가처리가 이루어지도록 변경될 예정이라고 합니다. 다만 이전까지는 FilterSecurityInterceptor가 많이 사용되었으니 호환성을 위해 default로는 FilterSecurityInterceptor가 설정되어 있다고 합니다. 

사용자에게서 요청이 들어왔을 때, 권한 확인이 필요한 경우에는 SecurityFilterChain의 체인 중 FilterSecurityInterceptor가 동작하게 됩니다. 

 

 

그리고 이 FilterSecurityInterceptor는 유저의 신분증 역할을 하는 (1) Authentication을 SecurityContextHolder에서 얻게 됩니다. (지난번 인증 포스팅에서 SecurityContextHolder 안에 Authentication이 담겨있었던 그림 보셨죠?)

SecurityContextHolder & Authentication

요청을 보낸 이 유저가 어떤 유저인지에 대한 정보가 들어 있는 핵심 대상이니 이 Authentication을 먼저 얻으려고 하는게 당연하겠네요. 

그런다음 FilterSecurityInterceptor는 (2) FilterInvocation을 생성합니다. 

생성한 FilterInvocation을 SecurityMetaDataSource로 보낸다음 (3) ConfigAttributes들을 얻어냅니다. 

 

지금까지의 과정에서 얻은 (1) Authentication, (2) FilterInvocation 그리고 (3) ConfigAttributes 이 세 가지를 권한 여부를 판단해주는 AccessDecisionManager에게 전달해서 권한 확인을 요청합니다. 

인증 과정에서 AuthenticationManager가 그랬듯 AccessDecisionManager도 다른 친구들에게 업무를 위임합니다. 역시 Manager라는 이름 답습니다. AuthenticationManager는 AuthenticationProviders에게 업무를 위임했다면  AccessDecisionManager가 업무를 위임하는 대상의 이름은 AccessDecisionVoters입니다. 

 

AccessDesicionVoters를 통해서 권한 확인이 완료되면 권한 여부에 따라 이후 프로세스를 마저 진행하거나 접근을 제한하게 됩니다.

 

[recommended]

 

위에서 언급했던 것처럼 앞으로는 AuthorizationFilter 사용할 것을 권장하고 있습니다. 

Process를 한 번 살펴보니, FilterInvocation을 SecurityMetaDataSource로 보내 ConfigAttributes들을 얻어내는 과정이 없어졌습니다. 그리고 AccessDecisionManager가 아니라 RequestMatcherDelegatingAuthorizationManager가 사용자의 권한 여부를 판단합니다. 이 때 Authentication과 FilterInvocation만 AuthorizationManager에게 보내주면 됩니다.

 

 

중간에 ConfigAttributes를 얻는 과정을 생략하면서 절차를 좀 더 단순화 시킨 것 같습니다. 

 

Authorization 방법

위에서 Authorization(인가) 절차에 대해서 알아보았는데, 어떻게 사용하는지 알아보겠습니다.

Spring Security dependency를 정상적으로 추가하고 기타 다른 설정은 하지 않은 상황에서 local:8080으로 접속해보면 기본적인 로그인 페이지를 제공합니다. 아직까지 권한에 따른 차등 접근을 설정하지 않았기 때문에 어떤 url path (local:8080/**)로 접근해도 아래와 같이 인증을 한 다음에야 접근이 가능합니다. 

 

Username: user, Password: console 확인

경로에 따라서 권한을 다르게 부여하는 방식은, 위에서 언급한 필터들 중 어떤 필터를 사용하느냐에 따라 조금 다릅니다. 그리고 Configuration 파일에서 권한을 부여하는 방법도 있고 Controller를 통해 권한을 부여하는 방법도 있습니다. 두 가지 모두 알아보겠습니다.

 

어떤 방식을 사용하던지 먼저 권한 작업이 들어가는 SecurityFilterChain을 Bean으로 등록해주어야 합니다.

 

@Configuration
@EnableWebSecurity(debug = true) //Spring security filter가 Spring Filter Chain에 등록됨
public class AppSecurityConfig {

	//...

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
       
       //...

        return http.build();
    }

}

* @EnableWebSecurity(dubug = true) 로 설정해두면 Filter 작동 과정을 console에서 확인할 수 있습니다.

 

1. FilterSecurityInterceptor 사용하기

 

1.1 Configuration에서 인가 작업하기

 

먼저 지금까지 주로 사용되었다던 FilterSecurityInterceptor를 사용하려면,

.authorizeRequests() method를 사용해야 합니다.

 

.authorizeRequests()에 대한 설명을 보니 HttpServletRequest (예. URL 패턴)에 의한 요청 제한을 가능하게 해주는 메서드라고 되어있네요.

.authorizeRequests()의 용도 정의

 

메서드를 살펴보면 ExpressionInterceptUrlRegistry 타입을 return 합니다. 

어떻게 FilterSecurityInterceptor와 연결이 되는지는 좀 더 코드들을 살펴봐야 할 것 같습니다.

 

[AppSecurityConfig]

@Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
       
       //...

        http.authorizeRequests() //권한 요청
                .antMatchers("/user/**").authenticated() //(1)
                .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')") //(2)
                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')") //(3)
                .anyRequest().permitAll() //(4)
                .and()
                .formLogin() //(5)
                .loginPage("/login"); //(5-1)

        return http.build(); //(6)
    }

 

(1) .authenticated() 는 인증이 완료된 사용자의 경우 접근을 허가해준다는 의미입니다. 여기서는 /user/** 인 path로 접근을 시도할 때, 로그인(인증)에 성공한 사용자라면 모두 이용할 수 있도록 해줍니다.

 

(2) acess( ... ) 는 어떤 Role(지금까지 얘기해온 권한을 지칭하는 단어입니다)인지 명시를 해주고, 정해진 Role인 사용자만 해당 path에 접근하도록 허가해줍니다. (2)에서 처럼 여러 권한을 허용할 수도 있고, (3)에서 처럼 한가지 권한만 허용할 수도 있습니다.

 

(4) 그 외의 모든 요청은 모두가 접근 가능하도록 설정해줍니다.

 

(5) form login을 허용한다는 의미입니다. (5-1)에서 /login 페이지로 redirecting 해줍니다.

 

(6) return 시에는 http.build를 해주어서 리턴하면 됩니다. 

 

 

1.2 Controller에서 인가 작업하기

지금까지 알아본 방식은 Configuratrion file에서 권한부여를 하는 방식이었습니다. Controller에서도 인가작업을 할 수 있는데, 어떻게 하는지 알아보겠습니다. 

 

[AppSecurityConfig]

먼저 @EnableGlobalMethodSecurity를 붙여주어야 합니다. 

@Configuration
@EnableWebSecurity(debug = true) 
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) //추가
public class AppSecurityConfig {

  //...
   
   @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

		//...

        http.authorizeRequests()
                .antMatchers("/user/**").authenticated()
                
                /* comment 처리된 부분을 Controller에서 활성화 합니다. */
                
//                .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
//                .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")

                .anyRequest().permitAll()
                .and()
                .formLogin()
                .loginPage("/login");

        return http.build();
    }
}

 

(1) securedEnabled = true는 @Secured를 활성화해줍니다. 

(2) prePostEnabled = true는 @PreAuthorize와 @PostAuthorize를 활성화해줍니다. 

이렇게 활성화된 세 가지 anootation들은 Controller에서 사용합니다.

 

[IndexController]

@NoArgsConstructor
@AllArgsConstructor
@Controller
public class IndexController {

	//...
    
    @Secured("ROLE_ADMIN") //한 권한에만 접근 허용할 때
    @GetMapping("/admin")
    @ResponseBody
    public String admin() {
        return "admin";
    }

    @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')") //여러 권한에 접근 허용할 때
    @GetMapping("/manager")
    @ResponseBody
    public String manager() {
        return "manager";
    }
    
 	//...
}

 

이런식으로 annotation을 사용하면 AppSecurityConfig에서 권한 처리한 것과 동일한 결과를 가져옵니다. 

 

 

/* 아래 내용은 업데이트 예정 */

2. AuthorizationFilter 사용하기

 

AuthorizationFilter 를 사용하려면, .authorizeHttpRequests() method를 사용해야 합니다.

.authorizeHttpRequests() 구조

method가 하는 역할은 위의 .authorizaeRequests()와 동일합니다. 다만 method의 return type이 여기서는 AuthorizationManagerRequestMatcherRegistry네요. 권한처리를 해주는 RequestMatcherDelegatingAuthorizationManager가 delegate하는 request matcher를 반환하는 것 같습니다.

 

AuthorizationFilter를 사용할 때는 권한 설정시의 코드가 조금 변하더라구요. 이 부분은 조금 더 학습이 필요해서 실제로 적용해 본 다음에 정확한 내용으로 포스팅에 추가하도록 하겠습니다 :) 

 

 

 

 

 

https://docs.spring.io/spring-security/reference/servlet/authorization/authorize-requests.html

 

Authorize HttpServletRequest with FilterSecurityInterceptor :: Spring Security

By default, Spring Security’s authorization will require all requests to be authenticated. The explicit configuration looks like:

docs.spring.io

https://docs.spring.io/spring-security/reference/servlet/authorization/authorize-http-requests.html

 

Authorize HttpServletRequests with AuthorizationFilter :: Spring Security

By default, the AuthorizationFilter does not apply to DispatcherType.ERROR and DispatcherType.ASYNC. We can configure Spring Security to apply the authorization rules to all dispatcher types by using the shouldFilterAllDispatcherTypes method:

docs.spring.io

 

https://velog.io/@jayjay28/2019-09-04-1109-%EC%9E%91%EC%84%B1%EB%90%A8

 

Spring Security 스프링 시큐리티

스프링 시큐리티에 대해 간단하게 개념 정리를 하고 단순 시큐리티를 적용해봅니다. 스프링 시큐리티 대략적인 기능 사용자 권한에 따른 URI 접근 제어 DB와 연동 하는 Local strategy 로그인 쿠키를

velog.io

 

https://catsbi.oopy.io/c0a4f395-24b2-44e5-8eeb-275d19e2a536

 

스프링 시큐리티 기본 API및 Filter 이해

목차

catsbi.oopy.io

 

반응형