권한 관련된 내용들이 문자열로 저장되어있다
SimpleGrantedAuthority 생성시 접두사를 사용하여 ROLE_USER 등의 권한으로 저장되어 Collection에 담기고
Authentication 인증 객체안에 collection으로 GrantedAuthority가 저장된다
스프링 시큐리티는 ROLE_접두사를 기본적으로 사용하나
커스텀하여 사용이 가능하다
@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("MYPREFIX_");
}
//hasRole("ADMIN") => ROLE_ADMIN
hasRole("ADMIN") => MYPREFIX_ADM//IN
verify()는 디폴트로 check()으로 구현된 메서드를 호출한다
위 둘을 권한 심사하는 것
http.authorizeHttpRequests(auth -> auth.requestMatcher().access(AuthorizationManager)
public class CustomAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
private static final String REQUIRED_ROLE = "ROLE_SECURE";
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext object) {
//인증 정보 가져오기
Authentication auth = authentication.get();
//인증정보가없거나인증되지않은경우
if (auth == null || !auth.isAuthenticated() || auth instanceof AnonymousAuthenticationToken) {
return new AuthorizationDecision(false);
}
// "ROLE_SECURE" 권한을 가진 사용자인지 확인
boolean hasRequiredRole = auth
.getAuthorities()
.stream()
.anyMatch(grantedAuthority -> REQUIRED_ROLE.equals(grantedAuthority.getAuthority())
);
return new AuthorizationDecision(hasRequiredRole);
}
}
요청 패턴에 매핑된 AuthorizationManager 객체를 반환
요청 패턴을 저장한 RequestMatcher 객체를 반환
http.authorizeHttpRequests(auth -> auth
.anyRequest().access(new CustomRequestMatcherDelegatingAuthorizationManager())
모든 요청에 적용하기 때문에 url을 따로 지정하지 않아도 된다
1.RequestMatcherDelegatingAuthorizationManager 내부적으로 무조건 생성되고 mappings에 add함 이것은 변경할 수 없는 필수 절차이다.
2. 1번에 생성된 manager가 custom으로 만든 manager안에 들어가 있는 것이다.
3. 2번 객체안에 또 RequestMatcherDelegatingAuthorizationManager를 넣었지만 이 구조는 테스트용 구조로 좋은 구조는 아니다. 원리를 알기위해 학습용 구현
//하드코딩으로 수동적인 방법
@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests(auth -> auth
.anyRequest()
.access(authorizationManager(null)))
.build();
}
@Bean
public AuthorizationManager<RequestAuthorizationContext> authorizationManager(HandlerMappingIntrospector introspector){
List<RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>>> mappings = new ArrayList<>();
RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>> requestMatcherEntry1 =
new RequestMatcherEntry<>(new MvcRequestMatcher(introspector, "/user"), AuthorityAuthorizationManager.hasAuthority("ROLE_USER"));
RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>> requestMatcherEntry2 =
new RequestMatcherEntry<>(new MvcRequestMatcher(introspector, "/admin"), AuthorityAuthorizationManager.hasRole("ADMIN"));
RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>> requestMatcherEntry3 =
new RequestMatcherEntry<>(AnyRequestMatcher.INSTANCE, new AuthenticatedAuthorizationManager<>());
mappings.add(requestMatcherEntry1);
mappings.add(requestMatcherEntry2);
mappings.add(requestMatcherEntry3);
return new CustomRequestMatcherDelegatingAuthorizationManager(mappings);
}
public class CustomRequestMatcherDelegatingAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
private final RequestMatcherDelegatingAuthorizationManager manager;
public CustomRequestMatcherDelegatingAuthorizationManager(List<RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>>> mappings) {
Assert.notEmpty(mappings, "mappings cannot be empty");
manager = RequestMatcherDelegatingAuthorizationManager.builder()
.mappings(maps -> maps.addAll(mappings))
.build();
}
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext object) {
//단순 manager에 위임하는 일
//TODO: 중복되는 위임 과정이 존재하기 때문에 개선 필요
return manager.check(authentication,object.getRequest());
}
@Override
public void verify(Supplier<Authentication> authentication, RequestAuthorizationContext object) {
AuthorizationManager.super.verify(authentication, object);
}
}
@PreAuthorize("hasAuthority('ROLE_USER')")
public List<User> users() {
System.out.println("users: " + UserRepositiry.findAll());
}
위 두가지를 비교하여 인가 처리 진행하는 것
📌보안이 설정된 메소드가 있는 경우 그 빈의 프록시 객체를 자동으로 생성하는것이고
없다면 생성되지않는다 생성된 proxy 객체로 어떠한 작업을 진행하기 때문.
@EnableMethodSecurity(prePostEnabled=false)
//클래스 단위에 적용.
//시큐리티가 제공하는 클래스들을 비활성화한다. 그렇지않으면 중복해서 검사하게 된다
//메서드에 어노테이션 사용
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public Advisor preAuthorize() {
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(new MyPreAuthorizationManager());
}
위의 메서드 보안 기반 권한 규칙을 어노테이션 사용을 하지않고 선언할 수 있다
어노테이션이 아닌 패턴 형태
로 권한 규칙을 선언
할수있으며 이는 요청 수준의 인가와 유사한 방식이다포인트컷(PointCut)
을 사용하여 AOP 표현식
을 애플리케이션의 인가 규칙에 맞게 매칭할 수 있으며 이를 통해 어노테이션을 사용하지 않고도 메소드 수준
에서 보안 정책을 구현할 수 있다📌 AspectJExpressionPointcut을 사용하기 위해서
의존성 추가해야 한다.
implementation 'org.springframework.boot:spring-boot-starter-aop:3.4.0'
여러가지 표현식이 존재한다.
포인트컷 사용의 장점
유연한 적용 범위 지정:
애너테이션 기반 접근 제어(@PreAuthorize 등)는 주로 특정 메소드나 클래스에 직접 부착하는 방식으로 접근 제어를 수행한다. 반면 포인트컷을 사용하면 메소드 시그니처(메소드명 패턴), 패키지 구조, 파라미터 타입 등 다양한 기준을 통해 원하는 범위에 접근 제어를 유연하게 적용할 수 있다.
비침투적(Non-invasive) 접근 제어:
포인트컷을 사용하면 대상 코드(비즈니스 로직)에는 별다른 주석이나 어노테이션을 추가하지 않고도 권한 검사를 적용할 수 있다. 즉, 보안 로직이 애플리케이션 로직에 덜 침투적이며, 관심사의 분리가 더욱 명확해진다.
재사용성과 유지보수성 향상:
특정 패키지 혹은 특정 네이밍 컨벤션에 따라 접근 제어를 일괄 적용하거나 변경할 때, 포인트컷 정의만 수정하면 전체 적용 대상에 영향을 줄 수 있다. 이를 통해 재사용성과 유지보수성을 높일 수 있다.
복잡한 규칙 적용 용이:
애너테이션만으로 처리하기 애매한 복잡한 권한 규칙(예: 특정 파라미터 값 조건, 특정 리턴 타입에 따른 접근 제어 등)도 AOP 포인트컷 표현식과 어드바이스 로직을 조합하면 유연하게 처리할 수 있다.
만약 PRE_FILTER와 PRE_AUTHORIZE 둘다 설정되어있는 경우 PRE_FILTER가 먼저 수행된다.