Hello

AOP

by 볼빵빵오춘기

AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)

  • 여러 모듈에서 공통적으로 필요하지만 특정 모듈의 핵심 로직과는 관련이 없는 기능(=횡단 관심사)을 모듈화하는 기법이다.
  • 횡단 관심사를 분리하여 핵심 관심사와 독립적으로 관리할 수 있도록 한다. 
  • 로깅(logging), 트랜잭션 관리(transaction management), 보안(security), 성능 모니터링(performance monitoring) 등이 해당된다.

 

 

※ 횡단 관심사(Cross-Cutting Concerns)
여러 모듈에서 공통적으로 발생하는 기능이나 로직을 의미한다.
예를 들어, 로그 기록이나 예외 처리 같은 기능은 여러 클래스나 메서드에서 필요하지만, 핵심 로직과는 직접 관련이 없다.

※ 핵심 관심사(Core Concerns)
시스템의 비즈니스 로직이나 애플리케이션의 핵심 기능을 처리하는 부분이다.

 

AOP의 주요 용어

Aspect

횡단 관심사를 모듈화한 단위이다.

로깅 기능을 분리해 Aspect로 정의하면, 비즈니스 로직에 직접 로깅 코드를 삽입할 필요 없이 애스펙트로 처리할 수 있다.

 

Join Point

프로그램 실행 중에 횡단 관심사가 삽입될 수 있는 지점을 의미한다.

일반적으로 메서드 호출 지점, 예외 발생 지점 등이 조인 포인트가 될 수 있다.

 

 

 

Advice

애스펙트가 조인 포인트에서 취하는 행동을 정의한 것이다.
어드바이스는 애스펙트가 수행해야 하는 실제 로직이다.

 

  • Before: 메서드 실행 전에 실행되는 어드바이스.
  • After: 메서드 실행 후에 실행되는 어드바이스.
  • After Returning: 메서드가 성공적으로 실행된 후에 실행되는 어드바이스.
  • After Throwing: 예외가 발생한 후에 실행되는 어드바이스.
  • Around: 메서드 호출 전후에 실행되는 어드바이스. 실행의 흐름을 제어할 수 있다.

 

Pointcut

조인 포인트 중에서 어느 지점에서 어드바이스가 실행될지 정의하는 식이다.

특정 메서드나 클래스에서만 실행되도록 필터링하는 역할을 한다.

 

Weaving

어드바이스를 핵심 비즈니스 로직에 적용하는 과정을 의미이다.

컴파일 타임, 클래스 로딩 타임, 런타임 등 다양한 시점에서 위빙이 일어날 수 있다.

 

AOP의 장점

 

관심사의 분리

핵심 로직과 공통적인 부가 기능을 분리함으로써 코드를 명확하게 유지할 수 있다.

 

유지보수성 향상

비즈니스 로직과는 별개로 횡단 관심사를 관리하기 때문에 코드 수정이 용이하다.

 

중복 코드 감소

여러 곳에서 사용되는 횡단 관심사(로깅, 보안, 트랜잭션 처리 등)를 공통 모듈로 관리하여 중복된 코드를 줄일 수 있다.

 

 

AOP의 사용 사례

 

로깅

각 메서드의 실행 전후에 로깅을 추가하는 데 사용한다.

 

트랜잭션 관리

데이터베이스 작업을 수행하는 메서드에 트랜잭션을 자동으로 관리한다.

 

보안

접근 제어를 위한 권한 체크 로직을 전역적으로 적용한다.

 

예외 처리

모든 메서드에서 발생하는 예외를 공통된 방식으로 처리한다.

 

 


🤔 그러면 토이프로젝트에서 권한에 따른 페이지 설정한 것도 AOP라고 볼 수 있나? Nope

더보기

토이프로젝트 진행 시 권한에 따른 페이지를 제공하고자 CustomAuthenticationEntryPoint를 만들어 예외가 났을 때 해당 페이지를 넘겨주도록 했었다. 

그러면 이 부분도 AOP의 보안 부분에 해당하는 것이 아닌가 생각이 들었다. 

 

한 설정과 접근 제어를 통해 로그인한 사용자와 비로그인 사용자, 그리고 권한에 따른 접근 제어를 구현한 방식은 AOP의 개념과 비슷한 측면이 있지만, 정확히 말하자면 AOP는 아니다.
대신, 이 방식은 스프링 시큐리티(Spring Security)의 인증 및 권한 부여 시스템을 사용한 것이다.

스프링 시큐리티는 인터셉터 기반으로 동작하며, 특정 요청이 들어왔을 때 이를 가로채서 인증(authentication)과 권한(authorization)을 확인하고 적절한 처리를 하는 방식이다. 이 과정에서 CustomAuthenticationEntryPoint와 같은 클래스를 사용하여 인증되지 않은 사용자나 권한이 없는 사용자가 접근할 때 처리 로직을 구현하는 것이다.

 

이것이 AOP와 비슷하게 보일 수 있지만, AOP는 횡단 관심사를 처리하는 일반적인 프로그래밍 기법으로, 주로 메서드 호출 전에 추가 작업을 하거나 메서드 호출 후에 로직을 실행하는 데 사용된다. 반면, 스프링 시큐리티의 권한 제어는 보안과 관련된 특정 영역에서 권한을 확인하고 처리하는 특화된 기능이다.

 

차이점 요약하자면

  • AOP는 다양한 횡단 관심사(로깅, 트랜잭션 관리 등)를 처리하기 위한 프로그래밍 기법이며, 메서드 호출 전후의 특정 로직을 실행하는 데 중점을 둔다.
  • 스프링 시큐리티는 애플리케이션의 보안을 관리하기 위한 프레임워크로, 인증 및 권한 부여를 관리한다. 요청이 들어왔을 때 이를 가로채서 접근 권한을 확인하고, 필요한 경우 권한이 없거나 인증되지 않은 사용자에게 별도의 처리를 제공한다.

예시코드

특정 권한이 없는 사용자가 서비스 메서드에 접근할 경우 예외를 발생시켜 접근을 제한하는 방식으로 작성했다. 

 

Aspect 클래스 (권한 검사)

AOP를 이용해 서비스 메서드 실행 전에 권한을 검사하고, 필요한 권한이 없으면 예외를 던진다.

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class RoleCheckAspect {

    // 특정 권한이 필요한 메서드들에 대해 Pointcut 설정
    @Pointcut("@annotation(com.example.aop.CheckRole)")
    public void checkRoleMethods() {}

    // 메서드 실행 전에 권한 검사를 수행
    @Before("checkRoleMethods() && @annotation(checkRole)")
    public void checkRole(CheckRole checkRole) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        
        // 권한 검사 로직
        if (authentication == null || !authentication.getAuthorities().contains(new SimpleGrantedAuthority(checkRole.value()))) {
            throw new RuntimeException("접근 권한이 없습니다."); // 권한이 없는 경우 예외 발생
        }
    }
}
 

Custom Annotation 정의

AOP의 타겟이 되는 메서드를 지정하기 위한 커스텀 어노테이션을 만든다.

package com.example.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)  // 메서드에 적용할 수 있도록 설정
@Retention(RetentionPolicy.RUNTIME)  // 런타임 시점까지 어노테이션이 유지되도록 설정
public @interface CheckRole {
    String value();  // 권한을 설정할 수 있도록 함
}

 

서비스 클래스 작성

권한을 검사하는 메서드를 작성한다. @CheckRole 어노테이션을 사용하여 권한을 요구하는 메서드를 지정한다.

package com.example.service;

import com.example.aop.CheckRole;
import org.springframework.stereotype.Service;

@Service
public class ExampleService {

    @CheckRole("ROLE_ADMIN")
    public String adminOnlyMethod() {
        return "관리자 전용 메서드가 실행되었습니다.";
    }

    @CheckRole("ROLE_USER")
    public String userOnlyMethod() {
        return "사용자 전용 메서드가 실행되었습니다.";
    }
}

 

Controller 클래스 작성

컨트롤러에서 서비스 메서드를 호출한다.
사용자는 권한에 따라 adminOnlyMethod() 또는 userOnlyMethod()에 접근할 수 있다.

package com.example.controller;

import com.example.service.ExampleService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Autowired;

@RestController
public class ExampleController {

    @Autowired
    private ExampleService exampleService;

    // 관리자 전용 메서드 호출
    @GetMapping("/admin")
    public String admin() {
        return exampleService.adminOnlyMethod();
    }

    // 사용자 전용 메서드 호출
    @GetMapping("/user")
    public String user() {
        return exampleService.userOnlyMethod();
    }
}

 

Security Configuration (Spring Security 설정)

권한을 부여하는 설정을 추가해야 한다. 스프링 시큐리티 설정에 권한을 부여한다.

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

@Component
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(authz -> authz
                        .requestMatchers("/admin").hasRole("ADMIN")
                        .requestMatchers("/user").hasRole("USER")
                        .anyRequest().authenticated())
                .formLogin().permitAll()
                .and()
                .logout().permitAll();
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails admin = User.withUsername("admin")
                .password(passwordEncoder().encode("admin123"))
                .roles("ADMIN")
                .build();

        UserDetails user = User.withUsername("user")
                .password(passwordEncoder().encode("user123"))
                .roles("USER")
                .build();

        return new InMemoryUserDetailsManager(admin, user);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

 

설명

 

  • RoleCheckAspect: @CheckRole 어노테이션이 달린 메서드에 대해 권한을 검사하는 AOP Aspect이다.
    사용자가 지정된 권한을 가지고 있지 않으면 예외를 발생시킨다.
  • CheckRole: 커스텀 어노테이션으로, 메서드에 권한 검사를 적용하기 위해 사용한다.
  • ExampleService: adminOnlyMethod()와 userOnlyMethod()는 각각 ROLE_ADMIN과 ROLE_USER 권한을 요구한다.
  • ExampleController: 사용자 또는 관리자가 해당 권한에 맞는 메서드에 접근할 수 있도록 컨트롤러를 작성했다.
  • SecurityConfig: 스프링 시큐리티 설정으로, 메모리에 두 가지 사용자(admin, user)를 저장하고 권한을 부여했다.

 

실행 결과

  • /admin URL에 접근할 때, 로그인한 사용자가 ROLE_ADMIN 권한을 가지고 있어야만 접근할 수 있다.
  • /user URL에 접근할 때는 ROLE_USER 권한이 필요하다.

블로그의 정보

Hello 춘기's world

볼빵빵오춘기

활동하기