Spring RSocket 마스터 — 보안·Spring Security·TLS

2026-05-03확률과 통계 마스터 노트

Spring RSocket 마스터 노트 시리즈 6편. RSocket의 보안이 HTTP와 다른 점, Spring Security RSocket으로 인증·인가 구현, SETUP-time vs Per-request 인증의 결정적 차이, JWT 토큰 검증, 라우트별 권한 제어, TLS·mTLS 설정, ReactiveAuthenticationManager 통합까지.

이 글은 Spring RSocket 마스터 노트 시리즈의 여섯 번째 편입니다. 1~5편이 기능이었다면, 이번엔 그것을 안전하게 — 보안.

RSocket은 메시징 프로토콜이라 보안 모델이 HTTP와 살짝 다릅니다. SETUP 시 한 번 인증·매 메시지 검증·연결 단위 권한. Spring Security RSocket이 답.

처음 RSocket 보안이 어렵게 느껴지는 이유

처음 이 단원이 어렵게 느껴지는 이유는 두 가지예요. 첫째, HTTP 보안과 다른 점이 막연합니다. JWT? OAuth? 둘째, SETUP 인증 vs Per-request 인증 차이가 헷갈립니다.

해결법은 한 가지예요. "SETUP = 한 번 인증 / Per-request = 매 메시지 인증" 한 줄. 일반 = SETUP만, 민감 정보 = 둘 다. 이 차이가 RSocket 보안의 핵심.

RSocket 보안 — 3 축

1. Authentication (인증) — 너 누구냐
2. Authorization (인가)  — 뭘 할 수 있냐
3. Encryption (암호화)   — TLS·mTLS

HTTP와 같은 3 축. 다만 구현 디테일이 다름.

SETUP 인증 vs Per-request 인증

SETUP 인증 — 한 번

Client → SETUP {auth: token} → Server
                                    ↓ 검증
                                연결 OK
                            ↓ 이후 모든 메시지 같은 인증
  • 한 번만 인증
  • 빠름·효율적
  • 일반 권장

Per-request 인증 — 매번

Request 1: 메타데이터에 토큰 → 검증
Request 2: 메타데이터에 토큰 → 검증
...
  • 매 메시지 검증
  • 느림
  • 민감 작업·외부 호출에만

여기서 정말 중요한 시험 함정 — 둘 결합도 OK. SETUP 시 일반 인증, 민감 라우트만 Per-request 추가 검증.

Spring Security RSocket 의존성

implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.security:spring-security-rsocket'

기본 설정

@Configuration
@EnableRSocketSecurity
public class RSocketSecurityConfig {

    @Bean
    public PayloadSocketAcceptorInterceptor security(RSocketSecurity security) {
        return security
            .authorizePayload(authorize -> authorize
                .route("user.public.**").permitAll()
                .route("user.list").authenticated()
                .route("admin.**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .simpleAuthentication(Customizer.withDefaults())
            .build();
    }

    @Bean
    public MapReactiveUserDetailsService users() {
        UserDetails user = User.withDefaultPasswordEncoder()
            .username("alice")
            .password("pass")
            .roles("USER")
            .build();
        UserDetails admin = User.withDefaultPasswordEncoder()
            .username("admin")
            .password("admin")
            .roles("USER", "ADMIN")
            .build();
        return new MapReactiveUserDetailsService(user, admin);
    }
}

클라이언트 — Basic Authentication

import org.springframework.security.rsocket.metadata.UsernamePasswordMetadata;

UsernamePasswordMetadata credentials = new UsernamePasswordMetadata("alice", "pass");

requester.route("user.list")
    .metadata(credentials, UsernamePasswordMetadata.BASIC_AUTHENTICATION_MIME_TYPE)
    .retrieveFlux(User.class);

SETUP 시 인증

RSocketRequester.Builder builder = RSocketRequester.builder()
    .setupMetadata(credentials, UsernamePasswordMetadata.BASIC_AUTHENTICATION_MIME_TYPE)
    .rsocketStrategies(strategies);

SETUP 시 한 번 인증 → 이후 모든 메시지 같은 사용자.

JWT 토큰 인증

서버

@Bean
public PayloadSocketAcceptorInterceptor security(RSocketSecurity security) {
    return security
        .authorizePayload(authorize -> authorize
            .anyExchange().authenticated()
        )
        .jwt(jwt -> jwt.authenticationManager(jwtAuthManager()))
        .build();
}

@Bean
public ReactiveAuthenticationManager jwtAuthManager() {
    JwtReactiveAuthenticationManager manager = new JwtReactiveAuthenticationManager(...);
    return manager;
}

클라이언트

import org.springframework.security.rsocket.metadata.BearerTokenMetadata;

BearerTokenMetadata token = new BearerTokenMetadata("eyJhbGc...");

requester.route("api.secure")
    .metadata(token, BearerTokenMetadata.BEARER_AUTHENTICATION_MIME_TYPE)
    .retrieveMono(Result.class);

여기서 시험 함정이 하나 있어요. JWT는 stateless 친화. 서버가 토큰 자체로 검증. DB 조회 X. RSocket 마이크로서비스에 적합.

라우트별 권한 — Method Security

@Controller
public class UserController {

    @MessageMapping("user.{id}")
    @PreAuthorize("hasRole('USER')")
    public Mono<User> get(@DestinationVariable String id) { ... }

    @MessageMapping("admin.delete")
    @PreAuthorize("hasRole('ADMIN')")
    public Mono<Void> delete(String id) { ... }

    @MessageMapping("user.profile")
    @PreAuthorize("authentication.name == #id")    // 본인만
    public Mono<Profile> getProfile(String id) { ... }
}

@EnableReactiveMethodSecurity 활성화 필요.

Authentication 객체 주입

@MessageMapping("user.profile")
public Mono<Profile> getProfile(
    @AuthenticationPrincipal UserDetails user
) {
    return profileService.getByUser(user.getUsername());
}

또는

@MessageMapping("user.profile")
public Mono<Profile> getProfile(Mono<Authentication> auth) {
    return auth.flatMap(a -> profileService.getByUser(a.getName()));
}

TLS — Transport Layer Security

서버

spring:
  rsocket:
    server:
      port: 7000
      ssl:
        enabled: true
        key-store: classpath:keystore.p12
        key-store-password: ${KS_PASS}
        key-store-type: PKCS12
        protocol: TLSv1.3

클라이언트

SslContext sslContext = SslContextBuilder.forClient()
    .trustManager(InsecureTrustManagerFactory.INSTANCE)   // 개발만!
    .build();

TcpClient tcpClient = TcpClient.create()
    .host("server")
    .port(7000)
    .secure(spec -> spec.sslContext(sslContext));

RSocketRequester requester = RSocketRequester.builder()
    .transport(TcpClientTransport.create(tcpClient));

여기서 정말 중요한 시험 함정 — 운영 = TLS 필수. 메타데이터·페이로드 모두 평문 노출 가능. 인증 토큰 = TLS 없으면 가로채임.

mTLS — Mutual TLS

서버·클라이언트 모두 인증서로 인증.

서버

spring.rsocket.server.ssl:
  enabled: true
  key-store: classpath:server-keystore.p12
  trust-store: classpath:server-truststore.p12     # 클라이언트 인증서 검증
  client-auth: need                                  # 강제

클라이언트

SslContext sslContext = SslContextBuilder.forClient()
    .keyManager(clientCert, clientKey)
    .trustManager(serverCert)
    .build();

용도:

  • 서비스 간 인증 — 마이크로서비스 ↔ 마이크로서비스
  • API 키 대체
  • 제로 트러스트 환경

OAuth2 / OIDC

@Bean
public PayloadSocketAcceptorInterceptor security(
    RSocketSecurity security,
    ReactiveJwtDecoder jwtDecoder
) {
    return security
        .authorizePayload(authorize -> authorize.anyExchange().authenticated())
        .jwt(jwt -> jwt.authenticationManager(
            new JwtReactiveAuthenticationManager(jwtDecoder)))
        .build();
}

@Bean
public ReactiveJwtDecoder jwtDecoder() {
    return ReactiveJwtDecoders
        .fromIssuerLocation("https://auth.example.com");
}

OAuth2 Resource Server처럼 동작.

CORS — RSocket WebSocket Transport

WebSocket transport 시 CORS 고려. TCP는 무관.

@Bean
public CorsWebFilter corsFilter() {
    CorsConfiguration config = new CorsConfiguration();
    config.addAllowedOrigin("https://example.com");
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");
    
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/rsocket", config);
    return new CorsWebFilter(source);
}

보안 체크리스트

✓ TLS 활성화 (운영)
✓ SETUP 시 인증 (한 번)
✓ 토큰 만료 시간 짧게 (수 분~수 시간)
✓ JWT 또는 OAuth2 (stateless)
✓ 라우트별 @PreAuthorize
✓ Method Security 활성화
✓ 메타데이터에 민감 정보 X (TLS 없을 때)
✓ Refresh Token 별도 보관
✓ 로그에 토큰 출력 X
✓ Audit Log
✓ Rate Limit (per user)
✓ Input Validation

DDoS·Rate Limit

@Bean
public PayloadInterceptor rateLimitInterceptor() {
    return new RateLimitInterceptor(...);
}

또는 외부 (Nginx·API Gateway).

시험 직전 한 번 더 — 자주 헷갈리는 함정 모음

여기까지가 6편의 핵심입니다. 시험 직전 또는 실무에서 헷갈릴 때 다시 펼쳐 볼 수 있게 압축 노트로 마무리할게요.

  • 보안 3 축 — 인증 / 인가 / 암호화
  • SETUP 인증 = 한 번 (일반 권장)
  • Per-request 인증 = 매 메시지 (민감 작업)
  • 둘 결합 가능
  • spring-security-rsocket 의존성
  • @EnableRSocketSecurity
  • RSocketSecurityauthorizePayload + simpleAuthentication / jwt
  • 라우트별 — permitAll·authenticated·hasRole
  • Basic AuthUsernamePasswordMetadata + BASIC_AUTHENTICATION_MIME_TYPE
  • JWT (Bearer)BearerTokenMetadata + BEARER_AUTHENTICATION_MIME_TYPE
  • JWT = stateless, 마이크로서비스 친화
  • @PreAuthorize — 라우트별·SpEL 표현식
  • @EnableReactiveMethodSecurity
  • @AuthenticationPrincipal 또는 Mono<Authentication> 주입
  • TLS 필수 (운영)
  • 메타데이터·페이로드 평문 → 가로채임
  • TLS 1.3 권장
  • mTLS = 서버·클라이언트 모두 인증서
  • 마이크로서비스 사이·제로 트러스트
  • OAuth2 / OIDC = ReactiveJwtDecoder.fromIssuerLocation
  • WebSocket transport = CORS 고려
  • 운영 체크 — TLS·짧은 토큰·JWT·@PreAuthorize·민감 정보 노출 X·Audit·Rate Limit

시리즈 다른 편

공식 문서: Spring Security RSocket 에서 더 깊이.

다음 글(7편)에서는 로드 밸런싱·확장 — 클라이언트 사이드 LB, RSocketLoadbalanceTarget, 헬스 체크, 다중 서버 라우팅까지 풀어 갑니다.

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!