Java 使用 JWT 令牌安全性进行 Spring Boot 单元测试
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/45241566/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring Boot Unit Tests with JWT Token Security
提问by Felipe
I am creating a backend using Spring Boot and I have just added JWT security to it.
我正在使用 Spring Boot 创建后端,并且我刚刚为其添加了 JWT 安全性。
I have done some tests using a REST Client and the JWT security is working fine, however all of my unit tests are now returning a 403 error code.
我已经使用 REST 客户端进行了一些测试,并且 JWT 安全性工作正常,但是我所有的单元测试现在都返回 403 错误代码。
I've added the @WithMockUser
annotation to them, but they are still not working:
我已经@WithMockUser
为它们添加了注释,但它们仍然无法正常工作:
@Test
@WithMockUser
public void shouldRedirectToInstaAuthPage() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/instaAuth")).andExpect(status().is3xxRedirection());
}
Is there some other configuration that I am missing here?
我在这里缺少其他一些配置吗?
Here is the security configuration:
这是安全配置:
@Configuration
@EnableWebSecurity
public class ServerSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
// And filter other requests to check the presence of JWT in header
.addFilterBefore(new JWTAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Create a default account
auth.inMemoryAuthentication()
.withUser("john")
.password("123")
.roles("ADMIN");
}
}
And Method security:
和方法安全:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
采纳答案by Felipe
I believe that I solved the problem (and I hope I am not doing a bad practice or creating a security vulnerability on my backend).
我相信我解决了这个问题(我希望我没有做不好的做法或在我的后端创建安全漏洞)。
I followed @punkrocker27ka's advice and looked at this answer. In it they say that they are generating an Oauth token manually for the tests, so I decided to do the same thing for my JWT token.
我遵循@punkrocker27ka 的建议并查看了这个答案。他们在其中说他们正在为测试手动生成 Oauth 令牌,所以我决定为我的 JWT 令牌做同样的事情。
So I updated my class that generates the JWT tokens and validates them to be like this:
因此,我更新了生成 JWT 令牌并验证它们的类,如下所示:
public class TokenAuthenticationService {
static final long EXPIRATIONTIME = 864_000_000; // 10 days
static final String SECRET = "ThisIsASecret";
static final String TOKEN_PREFIX = "Bearer";
static final String HEADER_STRING = "Authorization";
public static void addAuthentication(HttpServletResponse res, String username) {
String jwt = createToken(username);
res.addHeader(HEADER_STRING, TOKEN_PREFIX + " " + jwt);
}
public static Authentication getAuthentication(HttpServletRequest request) {
String token = request.getHeader(HEADER_STRING);
if (token != null) {
// parse the token.
String user = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.getBody()
.getSubject();
return user != null ?
new UsernamePasswordAuthenticationToken(user, null, Collections.emptyList()) :
null;
}
return null;
}
public static String createToken(String username) {
String jwt = Jwts.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATIONTIME))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
return jwt;
}
}
And then I created a new test for it:
然后我为它创建了一个新的测试:
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class TokenAuthenticationServiceTest {
@Autowired
private MockMvc mvc;
@Test
public void shouldNotAllowAccessToUnauthenticatedUsers() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/test")).andExpect(status().isForbidden());
}
@Test
public void shouldGenerateAuthToken() throws Exception {
String token = TokenAuthenticationService.createToken("john");
assertNotNull(token);
mvc.perform(MockMvcRequestBuilders.get("/test").header("Authorization", token)).andExpect(status().isOk());
}
}
Then I ran the tests and they passed, so the token was accepted without the need for the @WithMockUser
annotation. I will add this to my other tests classes.
然后我运行了测试并且它们通过了,因此不需要@WithMockUser
注释就可以接受令牌。我会将它添加到我的其他测试类中。
PS: The test endpoint is below.
PS:测试端点如下。
/**
* This controller is used only for testing purposes.
* Especially to check if the JWT authentication is ok.
*/
@RestController
public class TestController {
@RequestMapping(path = "/test", method = RequestMethod.GET)
public String testEndpoint() {
return "Hello World!";
}
}
回答by Eric Hendrickson
One thing you need to be aware of when testing using this createToken() method is that your tests cannot test for a nonexistent user.
This is because createToken() only makes a JWT token based off of the string you put into it.
If you want to make sure nonexistent users cannot gain access, I recommend making your createToken() method private and instead use requests to gain the token, like this:
使用此 createToken() 方法进行测试时需要注意的一件事是,您的测试无法测试不存在的用户。
这是因为 createToken() 仅根据您放入的字符串生成 JWT 令牌。
如果您想确保不存在的用户无法获得访问权限,我建议您将 createToken() 方法设为私有,而是使用请求来获取令牌,如下所示:
@Test
public void existentUserCanGetTokenAndAuthentication() throws Exception {
String username = "existentuser";
String password = "password";
String body = "{\"username\":\"" + username + "\", \"password\":\"
+ password + "\"}";
MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/v2/token")
.content(body))
.andExpect(status().isOk()).andReturn();
String response = result.getResponse().getContentAsString();
response = response.replace("{\"access_token\": \"", "");
String token = response.replace("\"}", "");
mvc.perform(MockMvcRequestBuilders.get("/test")
.header("Authorization", "Bearer " + token))
.andExpect(status().isOk());
}
In a similar way, you can show that a nonexistent user will not be able to get this result:
以类似的方式,您可以证明不存在的用户将无法获得此结果:
@Test
public void nonexistentUserCannotGetToken() throws Exception {
String username = "nonexistentuser";
String password = "password";
String body = "{\"username\":\"" + username + "\", \"password\":\"
+ password + "\"}";
mvc.perform(MockMvcRequestBuilders.post("/v2/token")
.content(body))
.andExpect(status().isForbidden()).andReturn();
}