AuthroizationFilter를 이용한 인가(AuthorizationManager, RequestMatcherDelegatingAuthorizationManager)
AuthorizationFilter가 도입되면서, 기존의 FilterSecurityInterceptor를 대체할 수 있는 옵션이 제공되었다.
이 변화는 Spring Security의 현대적이고 유연한 보안 구성 방식을 반영하며, 간결하고 효율적인 권한 관리를 가능하게 한다.
FilterSecurityInterceptor를 이용한 구현은 아래 포스팅을 참고하기 바란다.
[Spring/Spring Security] - Spring Security - FilterSecurityInterceptor 이해 및 Example
비교
FilterSecurityInterceptor | AuthorizationFilter | |
역할 | URL 요청에 대한 권한 확인 및 접근 제어 | URL 요청 권한 확인을 좀 더 간단하고 명시적으로 수행 |
구성 방식 | 기존의 필터 기반 구성 (SecurityMetadataSource 등) | HttpSecurity.authorizeHttpRequests()를 사용한 선언적 구성 |
유연성 | 복잡한 커스터마이징 가능 (but, 설정이 복잡할 수 있음) | 간결하고 선언적이며 최신 요구사항에 적합 |
public class AuthorizationFilter extends GenericFilterBean {
private final AuthorizationManager<HttpServletRequest> authorizationManager;
private AuthorizationEventPublisher eventPublisher = AuthorizationFilter::noPublish;
/**
* Creates an instance.
* @param authorizationManager the {@link AuthorizationManager} to use
*/
public AuthorizationFilter(AuthorizationManager<HttpServletRequest> authorizationManager) {
Assert.notNull(authorizationManager, "authorizationManager cannot be null");
this.authorizationManager = authorizationManager;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
...
try {
AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request);
this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, decision);
if (decision != null && !decision.isGranted()) {
throw new AccessDeniedException("Access Denied");
}
chain.doFilter(request, response);
}
finally {
request.removeAttribute(alreadyFilteredAttributeName);
}
}
...
}
@Configuration
@EnableWebSecurity
public class SecurityConfig extends SecurityFilterChain {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasRole("USER")
.anyRequest().authenticated()
)
...
return http.build();
}
}
@FunctionalInterface
public interface AuthorizationManager<T> {
default void verify(Supplier<Authentication> authentication, T object) {
AuthorizationDecision decision = check(authentication, object);
if (decision != null && !decision.isGranted()) {
throw new AccessDeniedException("Access Denied");
}
}
@Nullable
AuthorizationDecision check(Supplier<Authentication> authentication, T object);
}
AuthorizationManager는 AuthorizationFilter에서 권한 확인을 처리한다.
AuthorizationManager를 구현하여 더 세부적인 권한 제어를 구현할 수 있다.
public final class RequestMatcherDelegatingAuthorizationManager implements AuthorizationManager<HttpServletRequest> {
private final Log logger = LogFactory.getLog(getClass());
private final List<RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>>> mappings;
private RequestMatcherDelegatingAuthorizationManager(
List<RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>>> mappings) {
Assert.notEmpty(mappings, "mappings cannot be empty");
this.mappings = mappings;
}
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, HttpServletRequest request) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Authorizing %s", request));
}
for (RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>> mapping : this.mappings) {
RequestMatcher matcher = mapping.getRequestMatcher();
MatchResult matchResult = matcher.matcher(request);
if (matchResult.isMatch()) {
AuthorizationManager<RequestAuthorizationContext> manager = mapping.getEntry();
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Checking authorization on %s using %s", request, manager));
}
return manager.check(authentication,
new RequestAuthorizationContext(request, matchResult.getVariables()));
}
}
this.logger.trace("Abstaining since did not find matching RequestMatcher");
return null;
}
...
}
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity//Spring Security 사용
public class SecurityConfig {
@Value("${permit.url}")
private final String[] PERMIT_ALL_RESOURCES;
private final JwtProvider jwtProvider;
private final RequestHeader requestHeader;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* SecurityFilterChain 정의
*/
@Bean
protected SecurityFilterChain configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.httpBasic(httpBasic -> httpBasic.disable())
.formLogin(formLogin -> formLogin.disable())
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
/*
* 인가(Authorization)
*/
.authorizeHttpRequests(authorize -> authorize
.antMatchers(PERMIT_ALL_RESOURCES).permitAll()
.requestMatchers(new AntPathRequestMatcher("/**")).access(new UrlAuthorizationManager())
.anyRequest().authenticated())
/**
* exception handling
* 인증, 인가 과정에서 발생할 수 있는 예외를 처리하는 방법을 설정
*/
.exceptionHandling(exception -> exception
.accessDeniedHandler(new JwtAccessDeniedHandler()) //인가(Authorization) 실패시
.authenticationEntryPoint(new JwtAuthenticationEntryPoint())) //인증(Authentication) 실패시
// JWT 인증 필터
.addFilterBefore(new JwtAuthenticationFilter(jwtProvider, requestHeader, PERMIT_ALL_RESOURCES), UsernamePasswordAuthenticationFilter.class)
;
return httpSecurity.build();
}
}
@Slf4j
public class UrlAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
// URL 패턴과 해당 권한을 매핑할 Map
private final Map<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>();
public UrlAuthorizationManager() {
// URL 패턴과 권한 매핑 (예시)
requestMap.put(new AntPathRequestMatcher("/admin/**"), Collections.singletonList(new SecurityConfig("ADMIN")));
requestMap.put(new AntPathRequestMatcher("/api/v1/**"), Arrays.asList(new SecurityConfig("ADMIN"), new SecurityConfig("USER")));
}
@Override
public AuthorizationDecision check(Supplier<Authentication> supplier, RequestAuthorizationContext requestAuthorizationContext) {
Authentication authentication = supplier.get();
HttpServletRequest httpServletRequest = requestAuthorizationContext.getRequest();
if(authentication.getAuthorities().isEmpty()) {
return new AuthorizationDecision(false);
}
Collection<ConfigAttribute> attributes = this.getRequestAttribute(httpServletRequest);
boolean containAuthority = false;
SecurityConfig securityConfig = null;
for (final ConfigAttribute configAttribute : attributes) {
if (configAttribute instanceof SecurityConfig) {
securityConfig = (SecurityConfig) configAttribute;
for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
containAuthority = securityConfig.getAttribute().equals(grantedAuthority.getAuthority());
if (containAuthority) {
break;
}
}
if (containAuthority) {
break;
}
}
}
return new AuthorizationDecision(containAuthority);
}
private Collection<ConfigAttribute> getRequestAttribute(HttpServletRequest request) {
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : requestMap.entrySet()) {
if (entry.getKey().matches(request)) {
return entry.getValue();
}
}
return null;
}
}
OAuth 네이버, 카카오 애플리케이션 등록 (2/4) (51) | 2024.12.19 |
---|---|
OAuth 개념 파해치기 (1/4) (56) | 2024.12.18 |
Spring Security - FilterSecurityInterceptor 이해 및 Example (2) | 2024.11.11 |
Spring Security + JWT 인증 (2/2) - Spring Security 설정 (29) | 2024.08.21 |
Spring Security + JWT 인증 (1/2) - JWT 구현 (25) | 2024.08.20 |
댓글 영역