I have a RestController class with a method that returns duplicate json. Weird thing is when I pass a string to the response object. The Json is okay. However when I pass any other object, be it a list, hashmap etc. I get the json duplicated. Am checking the response in PostMan.
Sample Code sending correct response with string.
@RequestMapping(value = "test", method = RequestMethod.GET)
public ResponseEntity<?> test() {
return ResponseEntity
.ok().
body("My test string");
}
Code sending duplicate Json with HashMap or any Model
@RequestMapping(value = "test", method = RequestMethod.GET)
public ResponseEntity<?> test() {
HashMap<String, Object> fullObject = new HashMap<String, Object>();
return ResponseEntity
.ok().
body(fullObject);
}
The response I get
Here is my security config where I pass a few filters during authentication.
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApplicationSecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtConfig jwtConfig;
private final PasswordEncoder passwordEncoder;
private final ApplicationUserService applicationUserService;
@Autowired
public ApplicationSecurityConfig(PasswordEncoder passwordEncoder, ApplicationUserService applicationUserService, JwtConfig jwtConfig) {
this.applicationUserService = applicationUserService;
this.passwordEncoder = passwordEncoder;
this.jwtConfig = jwtConfig;
}
//JWT AUTHENTICATION
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager(),jwtConfig))
.addFilterAfter(new JwtTokenVerifier(jwtConfig), JwtUsernameAndPasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/", "index", "/css/*", "/js/*", "/h2/**","/login", "/swagger-ui/**", "/v3/api-docs/**", "/token/refresh").permitAll()
.antMatchers("/api/**").hasRole(STUDENT.name())
.antMatchers(HttpMethod.DELETE, "/management/api/**").hasAuthority(COURSE_WRITE.getPermission())
.antMatchers(HttpMethod.POST, "/management/api/**").hasAuthority(COURSE_WRITE.getPermission())
.antMatchers(HttpMethod.PUT, "/management/api/**").hasAuthority(COURSE_WRITE.getPermission())
.antMatchers("/management/api/**").hasAnyRole(ADMIN.name(), ADMINTRAINEE.name())
.anyRequest()
.authenticated();
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder);
provider.setUserDetailsService(applicationUserService);
return provider;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
}
My JWTTokenVerifier
public class JwtTokenVerifier extends OncePerRequestFilter {
private final JwtConfig jwtConfig;
public JwtTokenVerifier(JwtConfig jwtConfig) {
this.jwtConfig = jwtConfig;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (request.getServletPath().equals("/login") || request.getServletPath().equals("/token/refresh")) {
filterChain.doFilter(request, 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 {
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT decodedJWT = verifier.verify(token);
String username = decodedJWT.getSubject();
String[] roles = decodedJWT.getClaim("authorities").asArray(String.class);
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
stream(roles).forEach(role -> {
authorities.add(new SimpleGrantedAuthority(role));
});
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
} catch (IllegalStateException e) {
throw new IllegalStateException(String.format("Token %s cannot be trusted", token));
}
filterChain.doFilter(request, response);
}
}
My JWTUsernameandpassword
public class JwtUsernameAndPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final JwtConfig jwtConfig;
public JwtUsernameAndPasswordAuthenticationFilter(AuthenticationManager authenticationManager,
JwtConfig jwtConfig) {
this.authenticationManager = authenticationManager;
this.jwtConfig = jwtConfig;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
try {
UsernameAndPasswordAuthenticationRequest authenticationRequest = new ObjectMapper()
.readValue(request.getInputStream(), UsernameAndPasswordAuthenticationRequest.class);
Authentication authentication = new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
authenticationRequest.getPassword()
);
Authentication authenticate = authenticationManager.authenticate(authentication);
return authenticate;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
/* String access_token = Jwts.builder()
.setSubject(authResult.getName())
.claim("authorities", authResult.getAuthorities())
.setIssuedAt(new Date())
.setExpiration(java.sql.Date.valueOf(LocalDate.now().plusDays(jwtConfig.getTokenExpirationAfterDays())))
.signWith(secretKey)
.compact();
String refresh_token = Jwts.builder()
.setSubject(authResult.getName())
.setIssuedAt(new Date())
.setExpiration(java.sql.Date.valueOf(LocalDate.now().plusDays(jwtConfig.getRefreshTokenExpirationAfterDays())))
.signWith(secretKey)
.compact();*/
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes());
String access_token = JWT.create()
.withSubject(authResult.getName())
.withExpiresAt(new Date(System.currentTimeMillis() 10 * 6000 * 1000))
.withIssuer(request.getRequestURL().toString())
.withClaim("authorities", authResult.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
.sign(algorithm);
String refresh_token = JWT.create()
.withSubject(authResult.getName())
.withExpiresAt(new Date(System.currentTimeMillis() 30 * 60 * 1000))
.withIssuer(request.getRequestURL().toString())
.sign(algorithm);
// response.addHeader(jwtConfig.getAuthorizationHeader(), jwtConfig.getTokenPrefix() access_token);
// response.setHeader("jwt_access", jwtConfig.getTokenPrefix() access_token);
// response.setHeader("jwt_refresh", jwtConfig.getTokenPrefix() refresh_token);
Map<String, String> tokens = new HashMap<>();
tokens.put("access_token", jwtConfig.getTokenPrefix() access_token);
tokens.put("refresh_token", refresh_token);
response.setContentType(APPLICATION_JSON_VALUE); //"application/json"
new ObjectMapper().writeValue(response.getOutputStream(), tokens);
}
}
CodePudding user response:
In your JwtTokenVerifier
you call filterChain.doFilter(request, response)
multiple times. For example, you call it both in the try/catch and outside:
try {
// ...
// Called here
filterChain.doFilter(request, response);
} catch (IllegalStateException e) {
throw new IllegalStateException(String.format("Token %s cannot be trusted", token));
}
// And here
filterChain.doFilter(request, response);
The solution is to refactor your code so that it's only calling the filterChain.doFilter(request, response)
statement once.
P.S. You also call filterChain.doFilter(request, response)
in the if-statements at the top of your logic. So for the /login
or /token/refresh
endpoint, the response might be tripled.