I am creating a backend using Spring Boot and I have just added JWT security to it.
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.
I've added the @WithMockUser
annotation to them, but they are still not working:
@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();
}
}
For Spring Boot Security database authentication please refer here. We are going to cover – Spring Boot Security with JWT Example – Token Generation, Token Validation and Token Refresh. What is JWT? JWT stands for Json Web Token which is a token implementation in JSON format. Json tokens used for authentication and data sharing between parties.
Everything’s gonna work fine. Congratulation! Today we’ve learned so many interesting things about Spring Security and JWT Token based Authentication in just a Spring Boot example. Despite we wrote a lot of code, I hope you will understand the overall architecture of the application, and apply it in your project at ease.
Compile and the run the SpringBootHelloWorldApplication.java as a Java application. Generating JWT - Expose a POST API with mapping /authenticate. On passing correct username and password it will generate a JSON Web Token (JWT)
– Using MongoDB: Spring Boot JWT Authentication with Spring Security and MongoDB – Fullstack: Deployment: The example that uses HttpOnly Cookies instead.
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.
So I updated my class that generates the JWT tokens and validates them to be like this:
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.
PS: The test endpoint is below.
/**
* 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!";
}
}
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:
@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();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With