Skip to content

Added spring boot integration test which tests the login filter and a… #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 8-JTW
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,19 @@
</exclusion>
</exclusions>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.demo.jwt;

import org.springframework.security.core.AuthenticationException;

public class InvalidJwtTokenException extends AuthenticationException {
public InvalidJwtTokenException(String msg, Throwable t) {
super(msg, t);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@
import com.google.common.base.Strings;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;

import javax.crypto.SecretKey;
import javax.servlet.FilterChain;
Expand All @@ -23,29 +21,23 @@
import java.util.Set;
import java.util.stream.Collectors;

public class JwtTokenVerifier extends OncePerRequestFilter {
public class JwtTokenVerifierFilter extends AbstractAuthenticationProcessingFilter {

private final SecretKey secretKey;
private final JwtConfig jwtConfig;

public JwtTokenVerifier(SecretKey secretKey,
JwtConfig jwtConfig) {
public JwtTokenVerifierFilter(String url,
SecretKey secretKey,
JwtConfig jwtConfig) {
super(url);
this.secretKey = secretKey;
this.jwtConfig = jwtConfig;
}

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {

String authorizationHeader = request.getHeader(jwtConfig.getAuthorizationHeader());

if (Strings.isNullOrEmpty(authorizationHeader) || !authorizationHeader.startsWith(jwtConfig.getTokenPrefix())) {
filterChain.doFilter(request, response);
return;
}

String token = authorizationHeader.replace(jwtConfig.getTokenPrefix(), "");

try {
Expand All @@ -64,18 +56,26 @@ protected void doFilterInternal(HttpServletRequest request,
.map(m -> new SimpleGrantedAuthority(m.get("authority")))
.collect(Collectors.toSet());

Authentication authentication = new UsernamePasswordAuthenticationToken(
username,
null,
simpleGrantedAuthorities
);
return new UsernamePasswordAuthenticationToken(username,null, simpleGrantedAuthorities);

SecurityContextHolder.getContext().setAuthentication(authentication);

} catch (JwtException e) {
throw new IllegalStateException(String.format("Token %s cannot be trusted", token));
} catch (Exception e) {
throw new InvalidJwtTokenException(String.format("Token %s cannot be trusted", token), e);
}
}

@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
}

@Override
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
return super.requiresAuthentication(request, response) && containsToken(request);
}

filterChain.doFilter(request, response);
private boolean containsToken(HttpServletRequest request) {
String authorizationHeader = request.getHeader(jwtConfig.getAuthorizationHeader());
return !Strings.isNullOrEmpty(authorizationHeader) && authorizationHeader.startsWith(jwtConfig.getTokenPrefix());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ public JwtUsernameAndPasswordAuthenticationFilter(AuthenticationManager authenti
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {

try {
UsernameAndPasswordAuthenticationRequest authenticationRequest = new ObjectMapper()
.readValue(request.getInputStream(), UsernameAndPasswordAuthenticationRequest.class);
Expand All @@ -45,15 +44,14 @@ public Authentication attemptAuthentication(HttpServletRequest request,
authenticationRequest.getPassword()
);

Authentication authenticate = authenticationManager.authenticate(authentication);
return authenticate;
return authenticationManager.authenticate(authentication);

} catch (IOException e) {
throw new RuntimeException(e);
}

}


@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.example.demo.auth.ApplicationUserService;
import com.example.demo.jwt.JwtConfig;
import com.example.demo.jwt.JwtTokenVerifier;
import com.example.demo.jwt.JwtTokenVerifierFilter;
import com.example.demo.jwt.JwtUsernameAndPasswordAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
Expand All @@ -26,6 +26,8 @@
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {

private static final String SECURE_URL_PATTERN = "/api/**";

private final PasswordEncoder passwordEncoder;
private final ApplicationUserService applicationUserService;
private final SecretKey secretKey;
Expand All @@ -50,10 +52,10 @@ protected void configure(HttpSecurity http) throws Exception {
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager(), jwtConfig, secretKey))
.addFilterAfter(new JwtTokenVerifier(secretKey, jwtConfig),JwtUsernameAndPasswordAuthenticationFilter.class)
.addFilterAfter(new JwtTokenVerifierFilter(SECURE_URL_PATTERN, secretKey, jwtConfig),JwtUsernameAndPasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/", "index", "/css/*", "/js/*").permitAll()
.antMatchers("/api/**").hasRole(STUDENT.name())
.antMatchers(SECURE_URL_PATTERN).hasRole(STUDENT.name())
.anyRequest()
.authenticated();
}
Expand Down
63 changes: 63 additions & 0 deletions src/test/java/com/example/demo/student/StudentControllerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.example.demo.student;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class StudentControllerTest {

private static final String URL = "api/v1/students/";

@Autowired
private WebApplicationContext context;

private MockMvc mvc;

@BeforeEach
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}

@ParameterizedTest
@CsvSource({"annasmith,password,200,true", "fred,password123,401,false"})
public void testOnlyCorrectlyAuthenticatedUsersCanObtainJwtToken(String username, String password, int statusCode, boolean checkToken) throws Exception {
String jWtToken = performLogin(username, password, statusCode);
if(checkToken) {
assertThat(jWtToken.startsWith("Bearer"));
} else {
assertThat(jWtToken).isNullOrEmpty();
}
}

@ParameterizedTest
@CsvSource({"annasmith,password,200", "linda,password,403", "tom,password,403"})
public void testOnlyAuthenticatedUserWithStudentRoleCanAccessAPI(String username, String password, int statusCode) throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/api/v1/students/1")
.header("Authorization", performLogin(username, password, 200)))
.andExpect(status().is(statusCode));
}

private String performLogin(String username, String password, int expectedStatusCode) throws Exception {
String body = "{\"username\":\"" + username + "\", \"password\":\"" + password + "\"}";

MvcResult result = mvc.perform(post("/login").content(body)).andExpect(status().is(expectedStatusCode)).andReturn();

return result.getResponse().getHeader("Authorization");
}
}