DimaMsuVmk22
DimaMsuVmk22

Reputation: 1

How to create Connect to web socket in Spring boot Java tests, 401 ERROR

This is the block of code integration test, where the HttpClientErrorException$Unauthorized: 401 error occurs. The problem occurs in the line this.session = stompClient.connect(...) when creating the session. URL and port is TRUE.

  @BeforeEach
  @Transactional
  void init() throws Exception {
    WebSocketStompClient stompClient = new WebSocketStompClient(new SockJsClient(
        Collections.singletonList(new WebSocketTransport(new StandardWebSocketClient()))));
    stompClient.setMessageConverter(new MappingJackson2MessageConverter());
    this.session = stompClient.connect(
        WEBSOCKET_URL.formatted(port),
        new StompSessionHandlerAdapter() {
        }).get(1, SECONDS);

In our project, authentication occurs via a token.

Below is the full test code

/**
 * Test for {@link ArticleBlockWebsocket}.
 */
@WithMockUser(username = TestUtil.DEFAULT_ADMIN)
@IntegrationTest
@AutoConfigureMockMvc
class ArticleBlockWebsocketIT {
  private static final String ARTICLE_CREATE_URL = "/api/spaces/{spaceId}/articles";
  private static final String ARTICLE_GET_URL = "/api/articles/{articleId}";
  private static final String SPACE_CREATE_URL = "/api/spaces";
  private static final String WEBSOCKET_URL = "ws://localhost:%d/websocket/tracker";
  private static final String TOPIC = "/topic";
  private static final String KERNEL = "/kernel";
  private static final String ADD_URL = "%s/articles/%d/block/add-after";
  private static final String EDIT_URL = "%s/articles/%d/block/edit";
  private static final String REMOVE_URL = "%s/articles/%d/block/remove";
  private static final String REPLACE_URL = "%s/articles/%d/replaceContent";
  private static final String COPY_URL = "%s/articles/%d/block/copy";

  @LocalServerPort
  private int port;
  @Autowired
  private MockMvc mockMvc;
  private StompSession session;
  private ArticleDTO articleDTO;
  private ArticleBlockDTO textBlock;
  private ArticleBlockDTO pageBlock;
  @Autowired
  private ArticleBlockRepository articleBlockRepository;
  @Autowired
  private ArticleRepository articleRepository;

  private WebSocketStompClient client;

  SpaceDTO createSpace() throws Exception {
    SpaceDTO request = new SpaceDTO().setTitle("TEST_SPACE").setProjectCode("TEST_SPACE")
        .setJagaProjectId(generateUniqueJagaProjectId());
    return TestUtil.convertJsonToObject(
        mockMvc.perform(post(SPACE_CREATE_URL)
                .with(csrf())
                .contentType(MediaType.APPLICATION_JSON)
                .content(TestUtil.convertObjectToJsonBytes(request))
            ).andExpect(status().isCreated())
            .andReturn().getResponse().getContentAsString(),
        SpaceDTO.class);
  }

  ArticleDTO createArticle(Long spaceId) throws Exception {
    RequestArticleDTO request = new RequestArticleDTO(null, "TEST_ARTICLE");
    return TestUtil.convertJsonToObject(mockMvc.perform(post(ARTICLE_CREATE_URL, spaceId)
            .with(csrf())
            .contentType(MediaType.APPLICATION_JSON)
            .content(TestUtil.convertObjectToJsonBytes(request))
        ).andExpect(status().isCreated())
        .andReturn().getResponse().getContentAsString(), ArticleDTO.class);
  }


  @BeforeEach
  @Transactional
  void init() throws Exception {
    WebSocketStompClient stompClient = new WebSocketStompClient(new SockJsClient(
        Collections.singletonList(new WebSocketTransport(new StandardWebSocketClient()))));
    stompClient.setMessageConverter(new MappingJackson2MessageConverter());
    this.session = stompClient.connect(
        WEBSOCKET_URL.formatted(port),
        new StompSessionHandlerAdapter() {
        }).get(1, SECONDS);

    SpaceDTO spaceDTO = createSpace();
    this.articleDTO = createArticle(spaceDTO.getId());
    ArticleBlockDTO textBlock = articleDTO.getPayload().getItems().values()
        .stream().filter(it -> it.payload().getType().equals(BlockType.TEXT)).findFirst()
        .orElse(null);
    assertThat(textBlock).isNotNull();

    ArticleBlockDTO pageBlock = articleDTO.getPayload().getItems().values()
        .stream().filter(it -> it.payload().getType().equals(BlockType.PAGE)).findFirst()
        .orElse(null);
    assertThat(pageBlock).isNotNull();
    this.textBlock = textBlock;
    this.pageBlock = pageBlock;
  } .......

/**
 * OAuth test utility.
 */
public class OAuth2TestUtil {

  public static final String TEST_USER_LOGIN = "test";

  public static final String ID_TOKEN =
      """
      eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9\
      .eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsIm\
      p0aSI6ImQzNWRmMTRkLTA5ZjYtNDhmZi04YTkzLTdjNmYwMzM5MzE1OSIsImlhdCI6MTU0M\
      Tk3MTU4MywiZXhwIjoxNTQxOTc1MTgzfQ.QaQOarmV8xEUYV7yvWzX3cUE_4W1luMcWCwpr\
      oqqUrg\
      """;

  /**
   * OAuth2AuthenticationToken for tests.
   *
   * @return test OAuth2AuthenticationToken.
   */
  public static OAuth2AuthenticationToken testAuthenticationToken() {
    Map<String, Object> claims = new HashMap<>();
    claims.put("sub", TEST_USER_LOGIN);
    claims.put("preferred_username", TEST_USER_LOGIN);
    claims.put("email", "[email protected]");
    claims.put("roles", Collections.singletonList(AuthoritiesConstants.ADMIN));

    return authenticationToken(claims);
  }

  /**
   * OAuth2AuthenticationToken for tests.
   *
   * @param claims prepared claims.
   * @return test token.
   */
  public static OAuth2AuthenticationToken authenticationToken(Map<String, Object> claims) {
    Instant issuedAt = Instant.now();
    Instant expiresAt = Instant.now().plus(1, ChronoUnit.DAYS);
    if (!claims.containsKey("sub")) {
      claims.put("sub", "jane");
    }
    if (!claims.containsKey("preferred_username")) {
      claims.put("preferred_username", "jane");
    }
    if (!claims.containsKey("email")) {
      claims.put("email", "[email protected]");
    }
    if (claims.containsKey("auth_time")) {
      issuedAt = (Instant) claims.get("auth_time");
    } else {
      claims.put("auth_time", issuedAt);
    }
    if (claims.containsKey("exp")) {
      expiresAt = (Instant) claims.get("exp");
    } else {
      claims.put("exp", expiresAt);
    }
    Collection<GrantedAuthority> authorities = SecurityUtils.extractAuthorityFromClaims(claims);
    OidcIdToken token = new OidcIdToken(ID_TOKEN, issuedAt, expiresAt, claims);
    OidcUserInfo userInfo = new OidcUserInfo(claims);
    DefaultOidcUser user = new DefaultOidcUser(authorities, token, userInfo, "preferred_username");
    return new OAuth2AuthenticationToken(user, user.getAuthorities(), "oidc");
  }

  /**
   * OAuth2AuthenticationToken for tests.
   *
   * @param authorizedClientService authorizedClientService.
   * @param clientRegistration      clientRegistration.
   * @param authentication          authentication.
   * @return test token.
   */
  public static OAuth2AuthenticationToken registerAuthenticationToken(
      OAuth2AuthorizedClientService authorizedClientService,
      ClientRegistration clientRegistration,
      OAuth2AuthenticationToken authentication
  ) {
    Map<String, Object> userDetails = authentication.getPrincipal().getAttributes();

    OAuth2AccessToken token = new OAuth2AccessToken(
        TokenType.BEARER,
        "Token",
        (Instant) userDetails.get("auth_time"),
        (Instant) userDetails.get("exp")
    );

    authorizedClientService.saveAuthorizedClient(
        new OAuth2AuthorizedClient(clientRegistration, authentication.getName(), token),
        authentication
    );

    return authentication;
  }
}

/**
 * This class allows you to run unit and integration tests without an IdP.
 */
@TestConfiguration
@Import(OAuth2Configuration.class)
public class TestSecurityConfiguration {

  @Bean
  ClientRegistration clientRegistration() {
    return clientRegistrationBuilder().build();
  }

  @Bean
  ClientRegistrationRepository clientRegistrationRepository(ClientRegistration clientRegistration) {
    return new InMemoryClientRegistrationRepository(clientRegistration);
  }

  private ClientRegistration.Builder clientRegistrationBuilder() {
    Map<String, Object> metadata = new HashMap<>();
    metadata.put("end_session_endpoint", "https://koschey.rt.ru/logout");

    return ClientRegistration
        .withRegistrationId("oidc")
        .issuerUri("{baseUrl}")
        .redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}")
        .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
        .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
        .scope("read:user")
        .authorizationUri("https://koschey.rt.ru/login/oauth/authorize")
        .tokenUri("https://koschey.rt.ru/login/oauth/access_token")
        .jwkSetUri("https://koschey.rt.ru/oauth/jwk")
        .userInfoUri("https://api.koschey.rt.ru/user")
        .providerConfigurationMetadata(metadata)
        .userNameAttributeName("id")
        .clientName("Client Name")
        .clientId("client-id")
        .clientSecret("client-secret");
  }

  @Bean
  JwtDecoder jwtDecoder() {
    return mock(JwtDecoder.class);
  }

  @Bean
  OAuth2AuthorizedClientService authorizedClientService(
      ClientRegistrationRepository clientRegistrationRepository) {
    return new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository);
  }

  @Bean
  @Primary
  KeycloakService keycloakService() {
    return new KeycloakService() {
      private final Map<String, User> userMap =
          Map.of("admin", new User()
                  .setId(UUID.randomUUID().toString())
                  .setEmail("admin@localhost")
                  .setLogin("admin@localhost")
                  .setFirstName("Admin")
                  .setLastName("Admin"),

              "user", new User()
                  .setId(UUID.randomUUID().toString())
                  .setEmail("user@localhost")
                  .setLogin("user@localhost")
                  .setFirstName("User")
                  .setLastName("User"));
      @Override
      public Optional<User> getUserByLogin(final String login) {
        return Optional.ofNullable(userMap.get(login));
      }

      @Override
      public String getBearerToken() {
        return "";
      }
    };
  }
}

Upvotes: 0

Views: 18

Answers (0)

Related Questions