[Spring Security] 인가 프로세스
스프링 시큐리티는 요청과 메서드 단위로 권한을 부여해 보안을 관리한다.
요청 기반 권한 부여는 HttpServletRequest에 대한 권한 부여를 모델링해 HttpSecurity 인스턴스를 사용해 권한 규칙을 선언할 수 있다.
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/user").hasAuthority("USER")
.requestMatchers("/mypage/**").hasAuthority("USER")
.requestMatchers(HttpMethod.GET, "/**").hasAuthority("read")
.requestMatchers(RegexRequestMatcher.regexMatcher("/resource/[A-Z]")).hasAuthority("USER")
.anyRequest().authenticated()
);
authorizeHttpRequests()로 어떤 요청에 대해 어떤 권한을 설정할 지 정의한다.
requestMatchers 메서드로 보호가 필요한 엔드포인트와 권한 규칙을 정의한다.
이 때 정규표현식, HTTP 요청 메서드, Ant 패턴을 사용할 수 있다.
위에서 정의한 규칙 외 모든 엔드포인트는 인증을 필요하도록 설정한다. (익명사용자도 ROLE_ANONYMOUS를 가진다)
위에서부터 아래로 나열된 순서대로 처리해 요청에 대해 첫 번째 일치만 적용하니.. 좁은 범위의 경로부터 설정하자.
스프링 시큐리티는 기본적으로 MvcRequestMatcher를 사용해서 경로를 매칭하고, AntPathRequestMatcher를 명시적으로 사용하는 경우 해당 Matcher를 사용한다.
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/user/{name}").access(new WebExpressionAuthorizationManager("#name == authentication.name"))
.requestMatchers("/admin/db").access(new WebExpressionAuthorizationManager("hasAuthority('ROLE_DB') or hasAuthority('ROLE_ADMIN')"))
.anyRequest().authenticated()
);
URL에 대한 권한을 좀 더 복잡하게 지정해야 할 때는 SpEL을 사용할 수 있다.
스프링 시큐리티는 WebExpressionAuthorizationManager를 제공해 권한 규칙 설정에 표현식을 사용할 수 있도록 지원한다.
이미지, CSS, JS에 대한 요청은 정적 자원 요청으로 보안 필터를 거치지 않도록 해야 한다.
webSecurity를 사용해 정적 자원에 대한 요청만 필터를 거치지 않도록 설정하거나 requestMatchers에서 permitAll로 설정하는 방법이 있는데, 스프링 시큐리티6 버전부터는 permitAll을 사용하는걸 권장한다.
이전 버전까지는 모든 요청마다 세션을 확인해야 해 성능 저하가 있었지만, 6버전부터는 필요한 경우에만 세션을 확인하게 돼 성능 문제가 해결됐고, 필터를 무조건 거치게 돼 보안 수준이 올라가 permitAll을 사용하는 편이 합리적이다.
// SecurityConfig.java
DefaultHttpSecurityExpressionHandler expressionHandler = new DefaultHttpSecurityExpressionHandler();
expressionHandler.setApplicationContext(context);
WebExpressionAuthorizationManager authorizationManager = new WebExpressionAuthorizationManager("@customWebSecurity.check(authentication, request)");
authorizationManager.setExpressionHandler(expressionHandler);
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/custom/**").access(authorizationManager)
.anyRequest().authenticated()
);
// CustomWebSecurity.java
@Component("customWebSecurity")
public class CustomWebSecurity {
public boolean check(Authentication authentication, HttpServletRequest request) {
return authentication.isAuthenticated(); // 권한 관련 로직 설정 가능 ..
}
}
사용자 정의 빈을 생성해 새로운 표현식으로 사용할 메서드를 정의하고, 메서드에 권한 검사 로직을 구현한다.
이후 빈 이름을 통해 접근 제어 로직을 수행하도록 설정할 수 있다.
// SecurityConfig.java
http.authorizeHttpRequests(authorize -> authorize
.requestMatchers(new CustomRequestMatcher("/admin")).hasAuthority("ROLE_ADMIN")
.anyRequest().authenticated()
);
// CustomRequestMatcher.java
public class CustomRequestMatcher implements RequestMatcher {
private final String urlPattern;
public CustomRequestMatcher(String urlPattern) {
this.urlPattern = urlPattern;
}
@Override
public boolean matches(HttpServletRequest request) {
String requestURI = request.getRequestURI();
return requestURI.startsWith(urlPattern);
}
}
경로를 직접 설정하려면 RequestMatcher 인터페이스를 구현하는 객체를 만들고 해당 객체를 URL으로 등록해주면 된다.
RequestMatcher의 matches 메서드를 사용해 클라이언트의 요청 객체로부터 값을 검증한다.
@PreFilter("filterObject.owner == authentication.name")
public void filterList(List<Item> items) {
}
@PostFilter("filterObject.owner == authentication.name")
public List<Item> getItems() {
return allItems;
}
@PreAuthorize("hasRole('ROLE_ADMIN')")
public void adminMethod() {
}
@PostAuthorize("returnObject.owner == authentication.principal.username")
public Item getItem(Long itemId) {
return itemDao.findItemById(itemId);
}
@GetMapping("path")
@PreAuthorize("@customAuthorize.isUser(#root)")
public String getMethodName(@RequestParam String param) {
return new String();
}
@Component("customAuthorize")
public class CustomAuthorize {
public boolean isUser(MethodSecurityExpressionOperations root) {
return root.hasAuthority("ROLE_USER");
}
}
메서드 단위의 보안 설정이 필요한 경우 @PostFilter, @Prefilter, @PostAuthorize, @PreAuthorize 애너테이션을 사용한다.
Filter 애너테이션은 파라미터로 받는 컬렉션이나 결과로 반환되는 컬렉션들의 값을 검사해 특정 조건을 만족하는 항목만 선택하는 역할을 수행한다.
Authorize 애너테이션은 메서드가 실행되기 전이나 실행된 후의 결과를 검사해 접근을 허용하거나 거부한다.
클래스에 애너테이션을 붙이면 해당 클래스에 포함되는 모든 메서드에 애너테이션이 적용되는데, 클래스와 메서드 두 군데 모두 애너테이션이 붙어있는 경우 메서드에 붙은 애너테이션이 우선된다.
애너테이션의 값으로 여러 표현식은 기본적으로 제공되기도 하고, 커스텀 빈을 정의해서 직접 표현식을 구현할 수 있으니.. 비즈니스 로직이 복잡한 경우 빈을 정의해서 표현식을 작성하자.
// AuthorizationManagerBeforeMethodInterceptor.class
/**
* Creates an interceptor for the {@link PreAuthorize} annotation
* @return the interceptor
*/
public static AuthorizationManagerBeforeMethodInterceptor preAuthorize() {
return preAuthorize(new PreAuthorizeAuthorizationManager());
}
/**
* Creates an interceptor for the {@link PreAuthorize} annotation
* @param authorizationManager the {@link PreAuthorizeAuthorizationManager} to use
* @return the interceptor
*/
public static AuthorizationManagerBeforeMethodInterceptor preAuthorize(
PreAuthorizeAuthorizationManager authorizationManager) {
AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(
AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class), authorizationManager);
interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder());
return interceptor;
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
attemptAuthorization(mi);
return mi.proceed();
}
소스코드를 살펴보면, AuthorizationManagerBeforeMethodInterceptor 클래스에서 @PreAuthorize 애너테이션을 처리하는 인터셉터를 생성하는걸 확인할 수 있다.
인터셉터가 생성될 때 처리할 애너테이션을 기반으로 포인트컷을 설정하고, invoke 메서드가 메서드 호출을 가로채 권한을 검사한다.
'Spring > Spring Security' 카테고리의 다른 글
[Spring Security] 예외 처리 (0) | 2024.06.12 |
---|---|
[Spring Security] 인증 상태 영속성 (0) | 2024.06.10 |
[Spring Security] 인증 아키텍처 (0) | 2024.06.08 |
[Spring Security] 인증 메커니즘 (0) | 2024.05.30 |
[Spring Security] 초기화 과정 (0) | 2024.05.27 |
댓글
이 글 공유하기
다른 글
-
[Spring Security] 예외 처리
[Spring Security] 예외 처리
2024.06.12 -
[Spring Security] 인증 상태 영속성
[Spring Security] 인증 상태 영속성
2024.06.10 -
[Spring Security] 인증 아키텍처
[Spring Security] 인증 아키텍처
2024.06.08 -
[Spring Security] 인증 메커니즘
[Spring Security] 인증 메커니즘
2024.05.30