栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

带有基于Spring的SockJS / STOMP Web套接字的JSON Web令牌(JWT)

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

带有基于Spring的SockJS / STOMP Web套接字的JSON Web令牌(JWT)

我发现了一种在测试中效果很好的黑客程序。绕过内置的Spring连接级Spring auth机制。相反,可以通过在客户端的Stomp标头中发送身份验证令牌来在消息级别设置身份验证令牌(这很好地反映了常规HTTP XHR调用已在执行的操作),例如:

stompClient.connect({'X-Authorization': 'token'}, ...);stompClient.subscribe(..., {'X-Authorization': 'token'});stompClient.send("/wherever", {'X-Authorization': 'token'}, ...);

在服务器端,使用以下命令从Stomp消息中获取令牌: ChannelInterceptor

@Overridepublic void configureClientInboundChannel(ChannelRegistration registration) {  registration.setInterceptors(new ChannelInterceptorAdapter() {     Message<*> preSend(Message<*> message,  MessageChannel channel) {      StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);      List tokenList = accessor.getNativeHeader("X-Authorization");      String token = null;      if(tokenList == null || tokenList.size < 1) {        return message;      } else {        token = tokenList.get(0);        if(token == null) {          return message;        }      }      // validate and convert to a Principal based on your own requirements e.g.      // authenticationManager.authenticate(JwtAuthentication(token))      Principal yourAuth = [...];      accessor.setUser(yourAuth);      // not documented anywhere but necessary otherwise NPE in StompSubProtocolHandler!      accessor.setLeaveMutable(true);      return MessageBuilder.createMessage(message.payload, accessor.messageHeaders)    }  })

这很简单,可以让我们达到85%的方式,但是,这种方法不支持向特定用户发送消息。这是因为Spring的将用户关联到会话的机制不受的结果影响

ChannelInterceptor
。Spring WebSocket假定身份验证是在传输层而不是消息层完成的,因此忽略了消息级身份验证。

使这项工作反正砍,就是创造我们的情况

DefaultSimpUserRegistry
DefaultUserDestinationResolver
,揭露那些环境,然后用拦截器来更新这些仿佛Spring本身是这样做。换句话说,类似:

@Configuration@EnableWebSocketMessageBroker@Order(HIGHEST_PRECEDENCE + 50)class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer() {  private DefaultSimpUserRegistry userRegistry = new DefaultSimpUserRegistry();  private DefaultUserDestinationResolver resolver = new DefaultUserDestinationResolver(userRegistry);  @Bean  @Primary  public SimpUserRegistry userRegistry() {    return userRegistry;  }  @Bean  @Primary  public UserDestinationResolver userDestinationResolver() {    return resolver;  }  @Override  public configureMessageBroker(MessageBrokerRegistry registry) {    registry.enableSimpleBroker("/queue", "/topic");  }  @Override  public registerStompEndpoints(StompEndpointRegistry registry) {    registry      .addEndpoint("/stomp")      .withSockJS()      .setWebSocketEnabled(false)      .setSessioncookieNeeded(false);  }  @Override public configureClientInboundChannel(ChannelRegistration registration) {    registration.setInterceptors(new ChannelInterceptorAdapter() {       Message<*> preSend(Message<*> message,  MessageChannel channel) {        StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);        List tokenList = accessor.getNativeHeader("X-Authorization");        accessor.removeNativeHeader("X-Authorization");        String token = null;        if(tokenList != null && tokenList.size > 0) {          token = tokenList.get(0);        }        // validate and convert to a Principal based on your own requirements e.g.        // authenticationManager.authenticate(JwtAuthentication(token))        Principal yourAuth = token == null ? null : [...];        if (accessor.messageType == SimpMessageType.CONNECT) {          userRegistry.onApplicationEvent(SessionConnectedEvent(this, message, yourAuth));        } else if (accessor.messageType == SimpMessageType.SUBSCRIBE) {          userRegistry.onApplicationEvent(SessionSubscribeEvent(this, message, yourAuth));        } else if (accessor.messageType == SimpMessageType.UNSUBSCRIBE) {          userRegistry.onApplicationEvent(SessionUnsubscribeEvent(this, message, yourAuth));        } else if (accessor.messageType == SimpMessageType.DISCONNECT) {          userRegistry.onApplicationEvent(SessionDisconnectEvent(this, message, accessor.sessionId, CloseStatus.NORMAL));        }        accessor.setUser(yourAuth);        // not documented anywhere but necessary otherwise NPE in StompSubProtocolHandler!        accessor.setLeaveMutable(true);        return MessageBuilder.createMessage(message.payload, accessor.messageHeaders);      }    })  }}

现在,Spring完全意识到了身份验证,即它将注入Principal到需要它的任何控制器方法中,将其公开给Spring Security 4.x的上下文,并将用户与WebSocket会话相关联,以向特定的用户/会话发送消息。

Spring Security Messaging

最后,如果你使用

Spring Security 4.x Messaging
支持,请确保将@Order你的
AbstractWebSocketMessageBrokerConfigurer
设置为高于
Spring Security
的值
AbstractSecurityWebSocketMessageBrokerConfigurer
Ordered.HIGHEST_PRECEDENCE + 50
将起作用,如上所示)。这样,你的拦截器将Principal在
Spring Security
执行其检查之前设置并设置安全上下文。

上面的代码中的这一行似乎使很多人感到困惑:

  // validate and convert to a Principal based on your own requirements e.g.  // authenticationManager.authenticate(JwtAuthentication(token))  Principal yourAuth = [...];

这不是问题的范围,因为它不是特定于Stomp的,但是无论如何我都会对其进行一些扩展,因为它与在Spring中使用auth令牌有关。使用基于令牌的身份验证时,Principal通常需要的

JwtAuthentication
是扩展Spring Security的
AbstractAuthenticationToken
类的自定义类。
AbstractAuthenticationToken
实现
Authentication
扩展该
Principal
接口的接口,并包含将令牌与
Spring Security
集成的大多数机制。

因此,在Kotlin代码中(很抱歉,我没有时间或意愿将其转换回Java),你

JwtAuthentication
可能看起来像这样,这是一个简单的包装
AbstractAuthenticationToken

import my.model.UserEntityimport org.springframework.security.authentication.AbstractAuthenticationTokenimport org.springframework.security.core.GrantedAuthorityclass JwtAuthentication(  val token: String,  // UserEntity is your application's model for your user  val user: UserEntity? = null,  authorities: Collection<GrantedAuthority>? = null) : AbstractAuthenticationToken(authorities) {  override fun getCredentials(): Any? = token  override fun getName(): String? = user?.id  override fun getPrincipal(): Any? = user}

现在你需要一个

AuthenticationManager
知道如何处理它的人。在Kotlin中,这可能看起来像以下内容:

@Componentclass CustomTokenAuthenticationManager @Inject constructor(  val tokenHandler: TokenHandler,  val authService: AuthService) : AuthenticationManager {  val log = logger()  override fun authenticate(authentication: Authentication?): Authentication? {    return when(authentication) {      // for login via username/password e.g. crash shell      is UsernamePasswordAuthenticationToken -> {        findUser(authentication).let {          //checkUser(it)          authentication.withGrantedAuthorities(it).also { setAuthenticated(true) }        }      }      // for token-based auth      is JwtAuthentication -> {        findUser(authentication).let {          val tokenTypeClaim = tokenHandler.parseToken(authentication.token)[CLAIM_TOKEN_TYPE]          when(tokenTypeClaim) { TOKEN_TYPE_ACCESS -> {   //checkUser(it)   authentication.withGrantedAuthorities(it).also { setAuthenticated(true) } } TOKEN_TYPE_REFRESH -> {   //checkUser(it)   JwtAuthentication(authentication.token, it, listOf(SimpleGrantedAuthority(Authorities.REFRESH_TOKEN))) } else -> throw IllegalArgumentException("Unexpected token type claim $tokenTypeClaim.")          }        }      }      else -> null    }  }  private fun findUser(authentication: JwtAuthentication): UserEntity =    authService.login(authentication.token) ?:      throw BadCredentialsException("No user associated with token or token revoked.")  private fun findUser(authentication: UsernamePasswordAuthenticationToken): UserEntity =    authService.login(authentication.principal.toString(), authentication.credentials.toString()) ?:      throw BadCredentialsException("Invalid login.")  @Suppress("unused", "UNUSED_PARAMETER")  private fun checkUser(user: UserEntity) {    // TODO add these and lock account on x attempts    //if(!user.enabled) throw DisabledException("User is disabled.")    //if(user.accountLocked) throw LockedException("User account is locked.")  }  fun JwtAuthentication.withGrantedAuthorities(user: UserEntity): JwtAuthentication {    return JwtAuthentication(token, user, authoritiesOf(user))  }  fun UsernamePasswordAuthenticationToken.withGrantedAuthorities(user: UserEntity): UsernamePasswordAuthenticationToken {    return UsernamePasswordAuthenticationToken(principal, credentials, authoritiesOf(user))  }  private fun authoritiesOf(user: UserEntity) = user.authorities.map(::SimpleGrantedAuthority)}

注入的内容将

TokenHandler
抽象出JWT令牌解析,但应使用通用的JWT令牌库,如jjwt。注入的内容
AuthService
是你的抽象,它实际上是
UserEntity
根据令牌中的声明创建你的抽象的,并且可以与你的用户数据库或其他后端系统对话。

现在,回到我们开始与线,它可能是这个样子,哪里

authenticationManager
是一个
AuthenticationManager
注入由spring我们适配器,是一个实例
CustomTokenAuthenticationManager
上面定义我们:

Principal yourAuth = token == null ? null : authenticationManager.authenticate(new JwtAuthentication(token));

然后,将此主体附加到消息中,如上所述。HTH!



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/411245.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号