How to add values to the header from a filter in Spring Security?

Viewed 6843

I am developing a web service with JWT in spring security and spring session. My intention is to add a filter that validates the JWT, extract its JTI and add it in the header service as "x-auth-token". The Json Web Token JTI matches the "Session_id" that generates spring session when a new user is authenticated (it can see with RequestContextHolder.currentRequestAttributes().GetSessionId()). This was put in the Json Web Token JTI when the user was authenticated.

I already have the filter that validates the JWT, however, I will not put it here now for simplicity. What I will put will be only the filter class with the written method doFilter.

What I'm trying to do is add a value to the header, something like this:

public class CustomFilter extends GenericFilterBean {

    @Override
    public void doFilter(
      ServletRequest request, 
      ServletResponse response,
      FilterChain chain) throws IOException, ServletException {

        /*
         * In this part I validate the token and extract the JTI, which is equal to the session_id of spring session.
         * Suppose that JTI = 71b0b8c1-1eac-46ce-80b6-f14c2e08c0de
         */

        //I want to do something like this:
        request.addHeader("x-auth-token", "71b0b8c1-1eac-46ce-80b6-f14c2e08c0de");

        chain.doFilter(request, response);

    }
}

This way the spring session token will not be inserted by the user, but by the filter once it is extracted from the JWT.

I have tried to do it through a class that extends from HttpServletRequestWrapper, like this:

public class HeaderMapRequestWrapper extends HttpServletRequestWrapper {
    /**
     * construct a wrapper for this request
     * 
     * @param request
     */
    public HeaderMapRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    private Map<String, String> headerMap = new HashMap<String, String>();

    /**
     * add a header with given name and value
     * 
     * @param name
     * @param value
     */
    public void addHeader(String name, String value) {
        headerMap.put(name, value);
    }

    @Override
    public String getHeader(String name) {
        String headerValue = super.getHeader(name);
        if (headerMap.containsKey(name)) {
            headerValue = headerMap.get(name);
        }
        return headerValue;
    }

    /**
     * get the Header names
     */
    @Override
    public Enumeration<String> getHeaderNames() {
        List<String> names = Collections.list(super.getHeaderNames());
        for (String name : headerMap.keySet()) {
            names.add(name);
        }
        return Collections.enumeration(names);
    }

    @Override
    public Enumeration<String> getHeaders(String name) {
        List<String> values = Collections.list(super.getHeaders(name));
        if (headerMap.containsKey(name)) {
            values.add(headerMap.get(name));
        }
        return Collections.enumeration(values);
    }

}

and defining the doFilter method like this:

@Override
    public void doFilter(
      ServletRequest request, 
      ServletResponse response,
      FilterChain chain) throws IOException, ServletException {

        /*
             * In this part I validate the token and extract the JTI, which is equal to the session_id of spring session.
             * Suppose that JTI = 71b0b8c1-1eac-46ce-80b6-f14c2e08c0de
             */

        HttpServletRequest r = (HttpServletRequest) request;

        HeaderMapRequestWrapper requestWrapper = new HeaderMapRequestWrapper(r);

        requestWrapper.addHeader("x-auth-token", "71b0b8c1-1eac-46ce-80b6-f14c2e08c0de");

        chain.doFilter(requestWrapper, response);

    }

However it does not work, I do not know if I have missed something or is not the way to do it.

Edit 02/10/2017:

When I run the service spring recognizes that there is no token (x-auth-token) in the header and automatically sends me the filter to authenticate the new user, which causes a Forbidden error because there is no user and password.

If I send the token (x-auth-token) from the beginning in the header everything works fine.

Edit 05/10/2017:

I have created an second filter to check the value that was added by the first filer in the header. The first filter does not receive the value "x-auth-token" from the ServletRequest, it adds it with "requestWrapper".

The second filter was added to configuration class like this:

 .addFilterAfter (getCustomFilter (),
 UsernamePasswordAuthenticationFilter.class) 
 .addFilterAfter
 (getCustomFilter2 (), UsernamePasswordAuthenticationFilter.class)

where getCustomFilter () and getCustomFilter2 () were created using a bean like this:

@Bean
    public CustomFilter getCustomFilter(){
        return new CustomFilter();
    }

    @Bean
    public CustomFilter2 getCustomFilter2(){
        return new CustomFilter2();
    }

The second filter is defined as follows:

public class CustomFilter2  implements Filter {
    @Override
    public void doFilter(
      ServletRequest request, 
      ServletResponse response,
      FilterChain chain) throws IOException, ServletException {     

        HttpServletRequest req = (HttpServletRequest) request;

        System.out.println("Result: " + req.getHeader("x-auth-token"));
        chain.doFilter(request, response);

    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub
    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub
    }
}

When I run the service spring recognizes that there is no token (x-auth-token) in the original header and automatically sends me the filter to authenticate the new user.

I think the problem is the order in which spring session is executed.

How can I call spring session after our filters?

The configuration class is the follows:

@Configuration
@EnableWebSecurity 
public class SeguridadConfiguracion extends  WebSecurityConfigurerAdapter {

    @Autowired 
    @Qualifier("seguridadServicio")
    private UserDetailsService objSeguridadServicio;

    @Autowired 
    public void configure(AuthenticationManagerBuilder auth) throws Exception { 
        auth.userDetailsService(objSeguridadServicio); 
    } 

    @Override 
    protected void configure(HttpSecurity http) throws Exception { 
            http
              .authorizeRequests() 
                         .antMatchers("/**").hasAnyAuthority("ComisionadoSI","GerenciaSI")
                         .anyRequest().authenticated()
                         .and() 
             .logout().clearAuthentication(true)
                      .invalidateHttpSession(true)
                      .and()
             .formLogin()
                        .and()
             .httpBasic() 
                        .and()

             .addFilterAfter(getCustomFilter(), UsernamePasswordAuthenticationFilter.class)
             .addFilterAfter(getCustomFilter2(), UsernamePasswordAuthenticationFilter.class)

             .csrf().disable();

    }

    @Bean
    public CustomFilter getCustomFilter(){
        return new CustomFilter();
    }

    @Bean
    public CustomFilter2 getCustomFilter2(){
        return new CustomFilter2();
    }

    @Bean
    public HttpSessionStrategy httpSessionStrategy() {
        return new HeaderHttpSessionStrategy();
     }

}
2 Answers

We can do this via addingAttribute in the filter

httpServletRequest.setAttribute("key name","value");

And in Controller we can access them via @RequestAttribute

Have you verified the order of your filters to make sure this filter is executed in the correct order?

Update to add more info:

Looking at the spring docs: https://docs.spring.io/spring-session/docs/current/reference/html5/#httpsession-rest

You enable spring session by adding the annotation @EnableRedisHttpSession.

The @EnableRedisHttpSession annotation creates a Spring Bean with the name of springSessionRepositoryFilter that implements Filter. The filter is what is in charge of replacing the HttpSession implementation to be backed by Spring Session. In this instance Spring Session is backed by Redis.

According to this the springSessionRepositoryFilter is an instance of springSessionRepositoryFilter: https://docs.spring.io/spring-session/docs/current/api/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.html

RedisHttpSessionConfiguration exposes the SessionRepositoryFilter as a bean named "springSessionRepositoryFilter". In order to use this a single RedisConnectionFactory must be exposed as a Bean.

Based on that, I think you need to add your filter before the SessionRepositoryFilter like:

.addFilterAfter(getCustomFilter(), SessionRepositoryFilter.class)
.addFilterAfter(getCustomFilter2(), SessionRepositoryFilter.class)
Related