mockito 如何在Sping Boot 中以mock或其他方式在集成测试中使用承载令牌(401 Unauthorized)

z0qdvdin  于 2021-07-26  发布在  其他
关注(0)|答案(2)|浏览(96)

我在刷新令牌和注销中使用不记名令牌有问题,因为在Sping Boot 示例的集成测试中,所有这两个过程都是用不记名令牌处理的。
我创建了MockJwtTokenProvider来为下面所示的集成测试创建一个模拟承载令牌

public class MockJwtTokenProvider {

    private static final String SECRET_KEY = "404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970";
    public static final long EXPIRATION_TIME_MS = 3600000; // 1 hour

    public static String createMockJwtTokenForCustomer() {

        Claims claims = Jwts.claims()
                .setSubject("customer_1") // Set the username as the subject
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + MockJwtTokenProvider.EXPIRATION_TIME_MS));

        claims.put("roles", Collections.singletonList("ROLE_CUSTOMER"));
        claims.put("userFullName", "customer_fullname");
        claims.put("id", 1);
        claims.put("email", "[email protected]");

        // Create a secret key from your actual secret key string
        SecretKey secretKey = Keys.hmacShaKeyFor(SECRET_KEY.getBytes());

        // Build the JWT token with the provided claims
        String token = Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(new Date())
                .signWith(secretKey, SignatureAlgorithm.HS512)
                .compact();

        return "Bearer " + token;
    }
}

下面是AuthControllerTest的相关部分

@Test
void refreshToken_ReturnSuccess() throws Exception {

    // given
    TokenRefreshRequest request = TokenRefreshRequest.builder()
            .refreshToken("validRefreshToken")
            .build();

    TokenRefreshResponse mockResponse = TokenRefreshResponse.builder()
            .accessToken("newMockedToken")
            .refreshToken("validRefreshToken")
            .build();

    String mockBearerToken = MockJwtTokenProvider.createMockJwtTokenForCustomer();

    // when
    when(authService.refreshToken(request)).thenReturn(mockResponse);

    // then
    mockMvc.perform(post("/api/v1/auth/refreshtoken")
                    .header("Authorization", mockBearerToken)
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(objectMapper.writeValueAsString(request)))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON));

}

@Test
void logout_ReturnSuccess() throws Exception {

    // Given
    String mockBearerToken = MockJwtTokenProvider.createMockJwtTokenForCustomer();

    // When
    when(authService.logout(mockBearerToken)).thenReturn("success");

    // Then
    mockMvc.perform(post("/api/v1/auth/logout")
                    .header("Authorization", mockBearerToken))
            .andExpect(status().isOk());

    verify(authService).logout(mockBearerToken);

}

我总是得到这个问题如下所示的刷新令牌和注销。

MockHttpServletResponse:
           Status = 401
    Error message = null
          Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", Content-Type:"application/json", X-Content-Type-Options:"nosniff", X-XSS-Protection:"0", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = application/json
             Body = {"path":"","error":"Unauthorized","message":"Full authentication is required to access this resource","status":401}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

java.lang.AssertionError: Status expected:<200> but was:<401>
Expected :200
Actual   :401

我也使用真实的不记名令牌,但我得到了同样的错误。
我如何解决这个问题?
下面是repo:Link

e1xvtsh3

e1xvtsh31#

[更新]基于your github repository,我看到,在xml 2中,您正在使用另一个实用程序类JwtUtils,它与MockJwtTokenProvider没有相同的结构和内容!我想这就是你的问题背后的原因。
下面是 JwtUtils 类,它在运行时工作正常:

package com.example.demo.security.jwt;

import com.example.demo.security.CustomUserDetails;
import com.example.demo.util.Identity;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;
import java.util.Map;

@Component
@Log4j2
public class JwtUtils {

    @Value("${jwt.secret}")
    private String jwtSecret;

    @Value("${jwt.expireMs}")
    private int jwtExpirationMs;

    private final Identity identity;

    public JwtUtils(Identity identity) {
        this.identity = identity;
    }

    public String generateJwtToken(Authentication auth) {
        CustomUserDetails userDetails = (CustomUserDetails) auth.getPrincipal();
        Map<String, Object> claims = userDetails.getClaims();
        return createToken(claims, userDetails.getUsername());
    }

    public String createToken(Map<String, Object> claims, String subject) {
        Date now = new Date();
        Date expirationDate = new Date(now.getTime() + jwtExpirationMs);

        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(now)
                .setExpiration(expirationDate)
                .signWith(getSignInKey(),SignatureAlgorithm.HS512)
                .compact();
    }

    public String generateJwtToken(CustomUserDetails customUserDetails) {
        Map<String, Object> claims = customUserDetails.getClaims();
        claims.put(TokenClaims.ID.getValue(), customUserDetails.getId());
        return createToken(claims, customUserDetails.getUsername());
    }

    private Key getSignInKey() {
        byte[] keyBytes = Decoders.BASE64.decode(jwtSecret);
        return Keys.hmacShaKeyFor(keyBytes);
    }

    public Claims extractClaims(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(getSignInKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    public Long getIdFromToken(String token){
        String idValue = extractClaims(token).get(TokenClaims.ID.getValue()).toString();
        Double doubleValue = Double.parseDouble(idValue);
        return doubleValue.longValue();
    }

    public String getEmailFromToken(String token){
        return extractClaims(token).get(TokenClaims.EMAIL.getValue()).toString();
    }

    public boolean validateJwtToken(String authToken) {

        log.info("JwtUtils | validateJwtToken | authToken: {}", authToken);

        try {
            Jwts.parserBuilder().setSigningKey(getSignInKey()).build().parseClaimsJws(authToken);
            return true;
        } catch (MalformedJwtException e) {
            log.error("JwtUtils | validateJwtToken | Invalid JWT token: {}", e.getMessage());
        } catch (ExpiredJwtException e) {
            log.error("JwtUtils | validateJwtToken | JWT token is expired: {}", e.getMessage());
        } catch (UnsupportedJwtException e) {
            log.error("JwtUtils | validateJwtToken | JWT token is unsupported: {}", e.getMessage());
        } catch (IllegalArgumentException e) {
            log.error("JwtUtils | validateJwtToken | JWT claims string is empty: {}", e.getMessage());
        }

        return false;
    }

    public String extractTokenFromHeader(String authorizationHeader) {

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            return authorizationHeader.substring(7);
        }
        return null;
    }
}

我尝试在调试模式下运行测试,并注意到一个异常:

io.jsonwebtoken.security.weakkeyexception: The verification key's size is 48 bits which is not secure enough for the HS256 algorithm

也就是说密钥和加密算法不兼容。尝试在测试中使用JwtUtils,你会没事的。
此外,由于您正在使用spring-security-test,因此必须使用@WithMockUser注解定义一个mock user。检查以下示例:

class AuthControllerTest  extends BaseControllerTest {

    @MockBean
    private AuthService authService;

    @Autowired
    private JwtUtils jwtUtils;

    @WithMockUser("customer_1")
    @Test
    void refreshToken_ReturnSuccess() throws Exception {

        User mockUser = User.builder()
                .id(1L)
                .username("customer_1")
                .email("[email protected]")
                .role(Role.ROLE_CUSTOMER)
                .fullName("customer_fullname")
                .build();

        CustomUserDetails userDetails = new CustomUserDetails(mockUser);

        // Generate a JWT token for the user
        String mockToken = jwtUtils.generateJwtToken(userDetails);

        String mockBearerToken = "Bearer " + mockToken;

        // the rest of your method code
    }

    // your other test methods...
}
bakd9h0s

bakd9h0s2#

我也是通过这种方法解决了这个问题。
1 .在AuthControllerTest中定义customUserDetailsServicejwtUtils

@MockBean
private CustomUserDetailsService customUserDetailsService;

@Autowired
private JwtUtils jwtUtils;

2 .定义bearer token

// given
 User mockUser = User.builder()
          .id(1L)
          .username("customer_1")
          .email("[email protected]")
          .role(Role.ROLE_CUSTOMER)
          .fullName("customer_fullname")
          .build();

  CustomUserDetails userDetails = new CustomUserDetails(mockUser);

  String accessToken = jwtUtils.generateJwtToken(userDetails);

  String mockBearerToken = "Bearer " + accessToken;

3 .由于我通过testcontainerdocker上运行mysql,找不到值,返回userDetails,使用mock

when(customUserDetailsService.loadUserByUsername("[email protected]")).thenReturn(userDetails);

下面是最终代码

class AuthControllerTest extends BaseControllerTest {

@MockBean
private AuthService authService;

@MockBean
private CustomUserDetailsService customUserDetailsService;

@Autowired
private JwtUtils jwtUtils;

  
 
 @Test
    void refreshToken_ReturnSuccess() throws Exception {

        // given
        User mockUser = User.builder()
                .id(1L)
                .username("customer_1")
                .email("[email protected]")
                .role(Role.ROLE_CUSTOMER)
                .fullName("customer_fullname")
                .build();

        CustomUserDetails userDetails = new CustomUserDetails(mockUser);

        String accessToken = jwtUtils.generateJwtToken(userDetails);

        String mockBearerToken = "Bearer " + accessToken;

        TokenRefreshRequest request = TokenRefreshRequest.builder()
                .refreshToken("validRefreshToken")
                .build();

        TokenRefreshResponse mockResponse = TokenRefreshResponse.builder()
                .accessToken("newMockedToken")
                .refreshToken("validRefreshToken")
                .build();

        // when
        when(authService.refreshToken(request)).thenReturn(mockResponse);
        when(customUserDetailsService.loadUserByUsername("[email protected]")).thenReturn(userDetails);

        // then
        mockMvc.perform(post("/api/v1/auth/refreshtoken")
                        .header(HttpHeaders.AUTHORIZATION, mockBearerToken)
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(objectMapper.writeValueAsString(request)))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON));

    }

    @Test
    void logout_ReturnSuccess() throws Exception {

        // Given
        User mockUser = User.builder()
                .id(1L)
                .username("customer_1")
                .email("[email protected]")
                .role(Role.ROLE_CUSTOMER)
                .fullName("customer_fullname")
                .build();

        CustomUserDetails userDetails = new CustomUserDetails(mockUser);

        String accessToken = jwtUtils.generateJwtToken(userDetails);

        String mockBearerToken = "Bearer " + accessToken;

        // When
        when(customUserDetailsService.loadUserByUsername("[email protected]")).thenReturn(userDetails);
        when(authService.logout(mockBearerToken)).thenReturn("success");

        // Then
        mockMvc.perform(post("/api/v1/auth/logout")
                        .header(HttpHeaders.AUTHORIZATION, mockBearerToken))
                .andExpect(status().isOk());

        verify(authService).logout(mockBearerToken);

    }

}

相关问题