I have a problem to use bearer token in refresh token and logout as all these two processes are tackled with bearer token in Integration Test of my Spring Boot example.
我在刷新令牌和注销时使用承载令牌有一个问题,因为在我的Spring Boot示例的集成测试中,所有这两个过程都用承载令牌来处理。
I created MockJwtTokenProvider to create a mock bearer token for Integration test shown below
我创建了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;
}
}
Here is the relevant part of AuthControllerTest shown below
下面是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);
}
I always got this issue shown below for refresh token and logout.
对于刷新令牌和注销,我总是收到如下所示的问题。
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
I also use real bearer token but I got the same error.
我也使用真实的持有者令牌,但我得到了相同的错误。
How can I fix the issue?
我如何解决这个问题?
Here is the repo : Link
以下是回购:链接
更多回答
优秀答案推荐
[Updated] Based on your github repository, I see that, in Runtime you're using another utility class JwtUtils
that doesn't have same structure and content like MockJwtTokenProvider
! I think it's the reason behind your issue.
[更新]基于您的GitHub存储库,我发现在Runtime中您正在使用另一个实用程序类JwtUtils,它的结构和内容与MockJwtTokenProvider不同!我想这就是你问题背后的原因。
The following is JwtUtils class which works fine at runtime:
以下是在运行时运行良好的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;
}
}
I've tried to run the test in debug mode, and noticed an exception:
我尝试在调试模式下运行测试,并注意到一个异常:
io.jsonwebtoken.security.weakkeyexception: The verification key's size is 48 bits which is not secure enough for the HS256 algorithm
Which means the key is not compatible with the encryption algorithm. Try to use JwtUtils
in your tests instead and you'll be fine.
这意味着密钥与加密算法不兼容。试着在测试中使用JwtUtils,这样就没问题了。
Also, as you're using spring-security-test, you must define a mock user with @WithMockUser
annotation. Check the following example :
此外,由于您使用的是Spring-Security-test,因此必须使用@WithMockUser注释定义一个模拟用户。请检查以下示例:
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...
}
I also solved the issue by following this approach
我也通过遵循这个方法解决了这个问题
1 . Define customUserDetailsService and jwtUtils in AuthControllerTest
1.在AuthControllerTest中定义CustomUserDetailsService和jwtUtils
@MockBean
private CustomUserDetailsService customUserDetailsService;
@Autowired
private JwtUtils jwtUtils;
2 . Define bearer token
2.定义持有者令牌
// 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 . As I used mysql running on docker through testcontainer, the value cannot be found and I return userDetails with the usage of mock
3.因为我通过测试容器使用了在docker上运行的MySQL,所以找不到这个值,所以我返回了带有mock用法的userDetail
when(customUserDetailsService.loadUserByUsername("[email protected]")).thenReturn(userDetails);
Here is the final code shown below
下面是显示的最终代码
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);
}
}
更多回答
I revised my repo. Can you check MockJwtTokenProvider ?
我修改了我的回购。你能检查一下MockJwtTokenProvider吗?
我是一名优秀的程序员,十分优秀!