The Coder
The Coder

Reputation: 4007

How to do basic authentication using cookies in spring security?

I am securing my REST api using Basic-Auth. On correct credentials passed by user, a controller is responsible for sending a httpOnly and secure cookie in response.

@GetMapping
@ResponseStatus(value=HttpStatus.OK)
public void loginUser( final HttpServletRequest request ,final HttpServletResponse response) throws UnsupportedEncodingException {

    setAuthCookieToResonse(request,response);

}

private void setAuthCookieToResonse(final HttpServletRequest request ,final HttpServletResponse response) throws UnsupportedEncodingException {
    String cookieKey = "auth";
    String cookieValue = request.getHeader("Authorization");

    if (cookieValue != null) {
        Cookie cookie = new Cookie(cookieKey, cookieValue);
        cookie.setHttpOnly(true);
        cookie.setSecure(true);

        response.addCookie(cookie);
    }
}

So, now with each request a cookie is being sent by the browser, which will contain Basic-Auth details. But the problem is, how do the spring security extract those credentials from that cookie?

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {// @formatter:off 
        httpSecurity
        .cors()
        .and().authorizeRequests()
        .antMatchers("/signup/**").permitAll()
        .anyRequest().authenticated()
        .and().httpBasic()
        .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and().csrf().disable()
        ;
     }

My guess was:
To add a filter before BasicAuthenticationFilter.class and extract the credentials from cookie and than add those credentials to the HttpServletRequest's Authorizaton header which is going to be passed to spring-security layer. But the problem is, HttpServletRequest doesn't have API to add headers.

What would be the right way to implement this?

Upvotes: 0

Views: 13203

Answers (1)

The Coder
The Coder

Reputation: 4007

I made this working after following this blog (archived). But I would love to hear other solutions, especially using some spring configuration itself. Spring is a very matured framework, it must(should) have something to handle this common problem.

Since, the HttpServletRequest don't have any method to add the new headers, I need to create a custom class which can add new headers to the request, this can be achived by HttpServletRequestWrapper. Here is the implementation.

public final class MutableHttpServletRequest extends HttpServletRequestWrapper {
    // holds custom header and value mapping
    private final Map<String, String> customHeaders;

    public MutableHttpServletRequest(HttpServletRequest request) {
        super(request);
        this.customHeaders = new HashMap<String, String>();
    }

    public void putHeader(String name, String value) {
        this.customHeaders.put(name, value);
    }

    public String getHeader(String name) {
        // check the custom headers first
        String headerValue = customHeaders.get(name);

        if (headerValue != null) {
            return headerValue;
        }
        // else return from into the original wrapped object
        return ((HttpServletRequest) getRequest()).getHeader(name);
    }

    public Enumeration<String> getHeaderNames() {
        // create a set of the custom header names
        Set<String> set = new HashSet<String>(customHeaders.keySet());

        // now add the headers from the wrapped request object
        Enumeration<String> e = ((HttpServletRequest) getRequest()).getHeaderNames();
        while (e.hasMoreElements()) {
            // add the names of the request headers into the list
            String n = e.nextElement();
            set.add(n);
        }

        // create an enumeration from the set and return
        return Collections.enumeration(set);
    }
}

The filter which checks for the cookie, before the Spring-secuirty:

public class CheckAuthCookieFilter implements Filter {

    private Logger logger = LoggerFactory.getLogger(getClass());

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

        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        MutableHttpServletRequest mutableRequest = new MutableHttpServletRequest(httpServletRequest);

        Cookie[] cookies = httpServletRequest.getCookies();

        if (cookies != null && cookies.length > 0) {
            for (Cookie cookie : cookies) {
                logger.debug(cookie.getName() + " : " + cookie.getValue());
                if (cookie.getName().equalsIgnoreCase("auth")) {
                    mutableRequest.putHeader("Authorization", URLDecoder.decode(cookie.getValue(), "utf-8"));
                }
            }
        }

        chain.doFilter(mutableRequest, response);

    }

}

and finally the security configuration:

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {// @formatter:off 
    httpSecurity
    .cors()
    .and().authorizeRequests()
    .antMatchers("/signup/**").permitAll()
    .anyRequest().authenticated()
    .and().httpBasic()
    .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    .and().csrf().disable()
    ;
    
    httpSecurity.addFilterBefore(new CheckAuthCookieFilter(), BasicAuthenticationFilter.class); 

}

My custom filter will run before the Spring's BasicAuthenticationFilter.If there is a cookie present with name auth(which the application created on successful login), than that's the cookie which holds the basic auth credentials. The credentials are extracted from that, and added to the header of request. Then the BasicAuthenticationFilter will run and look for the Authorization and proceed with its normal flow.

Upvotes: 3

Related Questions