1. 소개

이 기사에서는 반응형 애플리케이션 Security을 위한 Spring Security 5 프레임워크 의 새로운 기능을 살펴보겠습니다 . 이 릴리스는 Spring 5 및 Spring Boot 2에 맞춰져 있습니다.

이 기사에서는 Spring 5 프레임워크의 새로운 기능인 반응형 애플리케이션 자체에 대해서는 자세히 다루지 않을 것입니다. 자세한 내용 은 Reactor Core 소개 기사를 확인 하십시오.

2. 메이븐 설정

Spring Boot 스타터를 사용하여 필요한 모든 의존성과 함께 프로젝트를 부트스트랩합니다.

기본 설정에는 상위 선언, 웹 스타터 및 Security 스타터 의존성이 필요합니다. Spring Security 테스트 프레임워크도 필요합니다.

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.1</version>
    <relativePath/>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Maven Central에서 Spring Boot Security 스타터의 현재 버전을 확인할 수 있습니다 .

3. 프로젝트 설정

3.1. 반응형 애플리케이션 부트스트랩

표준 @SpringBootApplication 구성을 사용하지 않고 대신 Netty 기반 웹 서버를 구성합니다. Netty는 반응형 애플리케이션의 좋은 기반이 되는 비동기식 NIO 기반 프레임워크입니다.

@EnableWebFlux 어노테이션은 애플리케이션에 대한 표준 Spring Web Reactive 구성을 활성화합니다 .

@ComponentScan(basePackages = {"com.baeldung.security"})
@EnableWebFlux
public class SpringSecurity5Application {

    public static void main(String[] args) {
        try (AnnotationConfigApplicationContext context 
         = new AnnotationConfigApplicationContext(
            SpringSecurity5Application.class)) {
 
            context.getBean(NettyContext.class).onClose().block();
        }
    }

여기에서 새로운 애플리케이션 컨텍스트를 생성하고 Netty 컨텍스트에서 .onClose().block() 체인을 호출하여 Netty가 종료될 때까지 기다립니다 .

Netty가 종료된 후 컨텍스트는 try-with-resources 블록을 사용하여 자동으로 닫힙니다.

또한 Netty 기반 HTTP 서버, HTTP 요청 처리기 및 서버와 처리기 사이의 어댑터를 만들어야 합니다.

@Bean
public NettyContext nettyContext(ApplicationContext context) {
    HttpHandler handler = WebHttpHandlerBuilder
      .applicationContext(context).build();
    ReactorHttpHandlerAdapter adapter 
      = new ReactorHttpHandlerAdapter(handler);
    HttpServer httpServer = HttpServer.create("localhost", 8080);
    return httpServer.newHandler(adapter).block();
}

3.2. 스프링 Security 구성 클래스

기본 Spring Security 구성을 위해 SecurityConfig 라는 구성 클래스를 만듭니다 .

Spring Security 5에서 WebFlux 지원을 활성화하려면 @EnableWebFluxSecurity 어노테이션만 지정하면 됩니다.

@EnableWebFluxSecurity
public class SecurityConfig {
    // ...
}

이제 ServerHttpSecurity 클래스를 활용 하여 Security 구성을 구축할 수 있습니다.

이 클래스는 Spring 5의 새로운 기능입니다. HttpSecurity 빌더 와 유사 하지만 WebFlux 애플리케이션에서만 사용할 수 있습니다.

ServerHttpSecurity 는 이미 정상적인 기본값으로 미리 구성 되어 있으므로 이 구성을 완전히 건너뛸 수 있습니다. 그러나 우선 다음과 같은 최소 구성을 제공합니다.

@Bean
public SecurityWebFilterChain securityWebFilterChain(
  ServerHttpSecurity http) {
    return http.authorizeExchange()
      .anyExchange().authenticated()
      .and().build();
}

또한 사용자 세부 정보 서비스가 필요합니다. Spring Security는 편리한 모의 사용자 빌더와 사용자 세부 정보 서비스의 메모리 내 구현을 제공합니다.

@Bean
public MapReactiveUserDetailsService userDetailsService() {
    UserDetails user = User
      .withUsername("user")
      .password(passwordEncoder().encode("password"))
      .roles("USER")
      .build();
    return new MapReactiveUserDetailsService(user);
}

반응형 영역에 있으므로 사용자 세부 정보 서비스도 반응형이어야 합니다. ReactiveUserDetailsService 인터페이스를 확인하면 findByUsername 메소드가 실제로 Mono 게시자 를 반환하는 것을 볼 수 있습니다 .

public interface ReactiveUserDetailsService {

    Mono<UserDetails> findByUsername(String username);
}

이제 애플리케이션을 실행하고 일반 HTTP 기본 인증 양식을 관찰할 수 있습니다.

4. 스타일이 지정된 로그인 양식

Spring Security 5의 작지만 눈에 띄는 개선 사항은 Bootstrap 4 CSS 프레임워크를 사용하는 새로운 스타일의 로그인 양식입니다. 로그인 양식의 스타일시트는 CDN에 연결되므로 인터넷에 연결되었을 때만 개선 사항을 볼 수 있습니다.

새 로그인 양식을 사용하려면 해당 formLogin() 빌더 메서드를 ServerHttpSecurity 빌더에 추가해 보겠습니다.

public SecurityWebFilterChain securityWebFilterChain(
  ServerHttpSecurity http) {
    return http.authorizeExchange()
      .anyExchange().authenticated()
      .and().formLogin()
      .and().build();
}

이제 애플리케이션의 기본 페이지를 열면 이전 버전의 Spring Security 이후로 사용했던 기본 형식보다 훨씬 좋아 보이는 것을 볼 수 있습니다.

2017-11-16_00-10-07

 

이것은 생산 준비가 된 양식은 아니지만 우리 응용 프로그램의 좋은 부트스트랩입니다.

이제 로그인한 다음 http://localhost:8080/logout URL로 이동하면 스타일이 지정된 로그아웃 확인 양식이 표시됩니다.

5. 반응형 컨트롤러 Security

인증 양식 뒤에 있는 것을 보기 위해 사용자를 맞이하는 간단한 반응 컨트롤러를 구현해 보겠습니다.

@RestController
public class GreetingController {

    @GetMapping("/")
    public Mono<String> greet(Mono<Principal> principal) {
        return principal
          .map(Principal::getName)
          .map(name -> String.format("Hello, %s", name));
    }

}

로그인하면 인사말이 표시됩니다. 관리자만 액세스할 수 있는 다른 반응형 핸들러를 추가해 보겠습니다.

@GetMapping("/admin")
public Mono<String> greetAdmin(Mono<Principal> principal) {
    return principal
      .map(Principal::getName)
      .map(name -> String.format("Admin access: %s", name));
}

이제 사용자 세부 정보 서비스에서 ADMIN 역할을 가진 두 번째 사용자를 생성해 보겠습니다.

UserDetails admin = User.withDefaultPasswordEncoder()
  .username("admin")
  .password("password")
  .roles("ADMIN")
  .build();

이제 사용자에게 ROLE_ADMIN 권한 이 있어야 하는 관리 URL에 대한 매처 규칙을 추가할 수 있습니다 .

.anyExchange() 체인 호출 앞에 매처를 넣어야 합니다. 이 호출은 아직 다른 매처에서 다루지 않은 다른 모든 URL에 적용됩니다.

return http.authorizeExchange()
  .pathMatchers("/admin").hasAuthority("ROLE_ADMIN")
  .anyExchange().authenticated()
  .and().formLogin()
  .and().build();

이제 user 또는 admin 으로 로그인 하면 인증된 모든 사용자가 액세스할 수 있도록 설정했기 때문에 둘 다 초기 인사말을 관찰하는 것을 볼 수 있습니다.

그러나 admin 사용자 만 http://localhost:8080/admin URL로 이동하여 인사말을 볼 수 있습니다.

6. 반응 방식 Security

우리는 URL을 보호하는 방법을 살펴보았습니다. 하지만 메소드는 어떻습니까?

반응형 메서드에 대한 메서드 기반 Security을 활성화하려면 @EnableReactiveMethodSecurity 어노테이션을 SecurityConfig 클래스에 추가하기만 하면 됩니다.

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {
    // ...
}

이제 다음 콘텐츠로 반응 인사말 서비스를 만들어 보겠습니다.

@Service
public class GreetingService {

    public Mono<String> greet() {
        return Mono.just("Hello from service!");
    }
}

컨트롤러에 주입하고 http://localhost:8080/greetingService 로 이동하여 실제로 작동하는지 확인할 수 있습니다.

@RestController
public class GreetingController {

    private GreetingService greetingService

    // constructor...

    @GetMapping("/greetingService")
    public Mono<String> greetingService() {
        return greetingService.greet();
    }

}

그러나 이제 ADMIN 역할 을 사용하여 서비스 메서드에 @PreAuthorize 어노테이션을 추가하면 일반 사용자가 인사말 서비스 URL에 액세스할 수 없습니다.

@Service
public class GreetingService {

    @PreAuthorize("hasRole('ADMIN')")
    public Mono<String> greet() {
        // ...
    }
}

7. 테스트에서 사용자 모의하기

반응형 Spring 애플리케이션을 테스트하는 것이 얼마나 쉬운지 확인해 봅시다.

먼저 주입된 애플리케이션 컨텍스트를 사용하여 테스트를 만듭니다.

@ContextConfiguration(classes = SpringSecurity5Application.class)
public class SecurityTest {

    @Autowired
    ApplicationContext context;

    // ...
}

이제 Spring 5 테스트 프레임워크의 기능인 간단한 반응형 웹 테스트 클라이언트를 설정하겠습니다.

@Before
public void setup() {
    this.webTestClient = WebTestClient
      .bindToApplicationContext(this.context)
      .configureClient()
      .build();
}

이를 통해 승인되지 않은 사용자가 애플리케이션의 기본 페이지에서 로그인 페이지로 리디렉션되는지 신속하게 확인할 수 있습니다.

@Test
void whenNoCredentials_thenRedirectToLogin() {
    webTestClient.get()
      .uri("/")
      .exchange()
      .expectStatus().is3xxRedirection();
}

이제 테스트 메서드에 @WithMockUser 어노테이션을 추가하면 이 메서드에 대해 인증된 사용자를 제공할 수 있습니다.

이 사용자의 로그인 및 비밀번호는 각각 사용자비밀번호 이며 역할은 USER 입니다. 물론 이것은 모두 @WithMockUser 어노테이션 매개변수로 구성할 수 있습니다.

이제 인증된 사용자가 인사말을 보는지 확인할 수 있습니다.

@Test
@WithMockUser
void whenHasCredentials_thenSeesGreeting() {
    webTestClient.get()
      .uri("/")
      .exchange()
      .expectStatus().isOk()
      .expectBody(String.class).isEqualTo("Hello, user");
}

@WithMockUser 어노테이션은 Spring Security 4부터 사용할 수 있습니다 . 그러나 Spring Security 5에서도 반응형 엔드포인트 및 메소드를 다루기 위해 업데이트되었습니다.

8. 결론

이 사용방법(예제)에서는 특히 반응형 프로그래밍 영역에서 곧 출시될 Spring Security 5 릴리스의 새로운 기능을 발견했습니다.

항상 그렇듯이 기사의 소스 코드는 GitHub 에서 사용할 수 있습니다 .

Security footer banner