[SPRING] Spring의 Websocket 인증 및 권한 부여
SPRINGSpring의 Websocket 인증 및 권한 부여
Spring Security로 Stomp (websocket) 인증 및 권한 부여를 제대로 구현하기 위해 많은 노력을 기울였습니다. 후손을 위해 나는 가이드를 제공하기 위해 내 자신의 질문에 대답 할 것이다.
Spring WebSocket 문서 (인증 용)는 ATM (IMHO)이 불분명합니다. 그리고 난 제대로 인증 및 권한을 처리하는 방법을 이해할 수 없었다.
해결법
-
==============================
1.위에서 언급했듯이 Spring이 몇 가지 분명한 문서를 제공하기까지는 문서 (ATM)가 명확하지 않습니다. 보안 체인이하는 일을 이해하려고 노력하는 데 2 일을 소비하지 않도록하는 보일러 플레이트가 있습니다.
위에서 언급했듯이 Spring이 몇 가지 분명한 문서를 제공하기까지는 문서 (ATM)가 명확하지 않습니다. 보안 체인이하는 일을 이해하려고 노력하는 데 2 일을 소비하지 않도록하는 보일러 플레이트가 있습니다.
정말 멋진 시도가 Rob-Leggett에 의해 만들어졌지만, 그는 스프링 스쿨 클래스를 포크로 찍고 있었고 편안하게 느끼지 않습니다.
알아 둘 사항 :
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-messaging</artifactId> </dependency>
아래의 설정은 간단한 메시지 브로커를 등록합니다 (인증이나 권한 부여와 아무 관계가 없습니다).
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig extends WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(final MessageBrokerRegistry config) { // These are endpoints the client can subscribes to. config.enableSimpleBroker("/queue/topic"); // Message received with one of those below destinationPrefixes will be automatically router to controllers @MessageMapping config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(final StompEndpointRegistry registry) { // Handshake endpoint registry.addEndpoint("stomp"); // If you want to you can chain setAllowedOrigins("*") } }
Stomp 프로토콜은 첫 번째 HTTP 요청에 의존하기 때문에 Stomp 핸드 셰이크 엔드 포인트에 대한 HTTP 호출을 인증해야합니다.
@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(final HttpSecurity http) throws Exception { // This is not for websocket authorization, and this should most likely not be altered. http .httpBasic().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests().antMatchers("/stomp").permitAll() .anyRequest().denyAll(); } }
그런 다음 사용자 인증을 담당하는 서비스를 만듭니다.
@Component public class WebSocketAuthenticatorService { // This method MSUT return a UsernamePasswordAuthenticationToken, another component in the security chain is testing it with 'instanceof' public UsernamePasswordAuthenticationToken getAuthenticatedOrFail(final String username, final String password) throws AuthenticationException { if (username == null || username.trim().length()) { throw new AuthenticationCredentialsNotFoundException("Username was null or empty."); } if (password == null || password.trim().length()) { throw new AuthenticationCredentialsNotFoundException("Password was null or empty."); } // Add your own logic for retrieving user in fetchUserFromDb() if (fetchUserFromDb(username, password) == null) { throw new BadCredentialsException("Bad credentials for user " + username); } // null credentials, we do not pass the password along return new UsernamePasswordAuthenticationToken( username, null, Collections.singleton((GrantedAuthority) () -> "USER") // MUST provide at least one role ); } }
참고 : UsernamePasswordAuthenticationToken은 GrantedAuthorities를 가져야하며, 다른 생성자를 사용하면 Spring은 isAuthenticated = false를 자동으로 설정합니다.
거의 대부분, 이제 우리는 simpUser 헤더를 설정하거나 CONNECT 메시지에서 AuthenticationException을 throw 할 인터셉터를 생성해야합니다.
@Component public class AuthChannelInterceptorAdapter extends ChannelInterceptor { private static final String USERNAME_HEADER = "login"; private static final String PASSWORD_HEADER = "passcode"; private final WebSocketAuthenticatorService webSocketAuthenticatorService; @Inject public AuthChannelInterceptorAdapter(final WebSocketAuthenticatorService webSocketAuthenticatorService) { this.webSocketAuthenticatorService = webSocketAuthenticatorService; } @Override public Message<?> preSend(final Message<?> message, final MessageChannel channel) throws AuthenticationException { final StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); if (StompCommand.CONNECT == accessor.getCommand()) { final String username = accessor.getFirstNativeHeader(USERNAME_HEADER); final String password = accessor.getFirstNativeHeader(PASSWORD_HEADER); final UsernamePasswordAuthenticationToken user = webSocketAuthenticatorService.getAuthenticatedOrFail(username, password); accessor.setUser(user); } return message; } }
preSend ()는 UsernamePasswordAuthenticationToken을 반환해야하며, 스프링 보안 체인의 또 다른 요소는이를 테스트해야합니다. 참고 : GrantedAuthority를 통과하지 않고 UsernamePasswordAuthenticationToken을 구축 한 경우 인증이 실패합니다. 권한이 부여되지 않은 생성자가 authenticated = false를 자동으로 설정했기 때문에 인증에 실패합니다. 이는 봄 보안에 문서화되지 않은 중요한 세부 사항입니다.
마지막으로 인증 및 인증 각각을 처리 할 두 개의 클래스를 추가로 작성하십시오.
@Configuration @Order(Ordered.HIGHEST_PRECEDENCE + 99) public class WebSocketAuthenticationSecurityConfig extends WebSocketMessageBrokerConfigurer { @Inject private AuthChannelInterceptorAdapter authChannelInterceptorAdapter; @Override public void registerStompEndpoints(final StompEndpointRegistry registry) { // Endpoints are already registered on WebSocketConfig, no need to add more. } @Override public void configureClientInboundChannel(final ChannelRegistration registration) { registration.setInterceptors(authChannelInterceptorAdapter); } }
참고 : @Order는 CRUCIAL입니다. 잊지 말고, 우리의 인터셉터를 보안 체인에 먼저 등록 할 수 있습니다.
@Configuration public class WebSocketAuthorizationSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { @Override protected void configureInbound(final MessageSecurityMetadataSourceRegistry messages) { // You can customize your authorization mapping here. messages.anyMessage().authenticated(); } // TODO: For test purpose (and simplicity) i disabled CSRF, but you should re-enable this and provide a CRSF endpoint. @Override protected boolean sameOriginDisabled() { return true; } }
행운을 빕니다 !
-
==============================
2.자바 클라이언트 측에서이 테스트 된 예제를 사용한다.
자바 클라이언트 측에서이 테스트 된 예제를 사용한다.
StompHeaders connectHeaders = new StompHeaders(); connectHeaders.add("login", "test1"); connectHeaders.add("passcode", "test"); stompClient.connect(WS_HOST_PORT, new WebSocketHttpHeaders(), connectHeaders, new MySessionHandler);
from https://stackoverflow.com/questions/45405332/websocket-authentication-and-authorization-in-spring by cc-by-sa and MIT license
'SPRING' 카테고리의 다른 글
[SPRING] 서로 다른 인수를 갖는 동일한 URL 패턴에 대해 두 개의 메소드를 작성하십시오. (0) | 2018.12.18 |
---|---|
[SPRING] 다른 속성의 속성 - 자리 표시 자 위치 (0) | 2018.12.18 |
[SPRING] Spring FileSystemResource에 환경 변수 기반 위치를 사용할 수 있습니까? (0) | 2018.12.18 |
[SPRING] java.lang.IllegalArgumentException : 디폴트 서블릿 처리를 설정하려면 ServletContext가 필요합니다. (0) | 2018.12.18 |
[SPRING] 봄, 잭슨 및 맞춤 설정 (예 : 맞춤 디시리얼라이저) (0) | 2018.12.18 |