[Spring Security] 인가 기능 구현 (2)

식빵·2022년 9월 17일
1
post-custom-banner

이 시리즈에 나오는 모든 내용은 인프런 인터넷 강의 - 스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 에서 기반된 것입니다. 그리고 여기서 인용되는 PPT 이미지 또한 모두 해당 강의에서 가져왔음을 알립니다. 추가적으로 여기에 작성된 코드들 또한 해당 강의의 github 에 올라와 있는 코드를 참고해서 만든 겁니다.



1. 인가처리 허용필터


그런데 인가 처리가 정말 필요가 없는 css 파일 같은 것들에 대한 처리가 아직 미비하다.
지금은 그냥 정적 자원도 모두 인가 필터를 거친다.

이를 해결하기 위해서 인증 및 권한심사를 할 필요가 없는 자원 등을 미리 설정해서 바로 리소스 접근이 가능한 필터(PermitAllFilter)를 만들어 보자.

기존에는 사용자 요청이 오면 AbstractSecurityInterceptor 에게 바로 인가처리를 맡겼지만,
이제는 그러지 않고 그전에 인증/인가 심사가 필요없는 자원을 따로 List 모아놓고 이곳을 먼저 뒤져보고, 매칭되는게 있으면 AbstractSecurityInterceptor 에게 인가처리를 위임하지 않고 바로 return 할 것이다.



✒ 구현

package io.security.corespringsecurity.security.factory;

import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class PermitAllFilter extends FilterSecurityInterceptor {
	
	
	private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
	private boolean observeOncePerRequest = true;
	
	private List<RequestMatcher> permitAllRequestMatchers = new ArrayList<>();
	
	public PermitAllFilter(String... permitAllResources) {
		for (String resource : permitAllResources) {
			permitAllRequestMatchers.add(new AntPathRequestMatcher(resource));
		}
	}
	
	@Override
	protected InterceptorStatusToken beforeInvocation(Object object) {
		
		boolean permitAll = false;
		
		HttpServletRequest request = ((FilterInvocation) object).getRequest();
		for (RequestMatcher matcher : permitAllRequestMatchers) {
			if (matcher.matches(request)) {
				permitAll = true;
				break;
			}
		}
		
		if (permitAll) {
			return null;
		}
		
		return super.beforeInvocation(object);
	}
	
	public void invoke(FilterInvocation fi) throws IOException, ServletException {
		if ((fi.getRequest() != null)
			&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
			&& observeOncePerRequest) {
			// filter already applied to this request and user wants us to observe
			// once-per-request handling, so don't re-do security checking
			fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
		}
		else {
			// first time this request being called, so perform security checking
			if (fi.getRequest() != null && observeOncePerRequest) {
				fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
			}
			
			InterceptorStatusToken token = beforeInvocation(fi);
			
			try {
				fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
			}
			finally {
				super.finallyInvocation(token);
			}
			
			super.afterInvocation(token, null);
		}
	}
}

✒ 설정


@Configuration
@EnableWebSecurity
@Slf4j
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    // ... 생략 ...
    
    private String[] permitAllResources = {"/", "/login", "/user/login/**"};
    
    @Bean
    public PermitAllFilter customFilterSecurityInterceptor() throws Exception {
        PermitAllFilter permitAllFilter = new PermitAllFilter(permitAllResources);
        permitAllFilter.setSecurityMetadataSource(myUrlFilterInvocationSecurityMetadataSource());
        permitAllFilter.setAccessDecisionManager(affirmativeBased());
        permitAllFilter.setAuthenticationManager(authenticationManagerBean());
        return permitAllFilter;
    }
    
}

사실 아래처럼 ignoring 하는 방법도 있다. 하지만 인가 처리에 대한 공통적인 프로세스를 만들려면 위의 방식으로 하는게 좋다.

@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
}
profile
백엔드를 계속 배우고 있는 개발자입니다 😊
post-custom-banner

0개의 댓글