Spring security oauth2 - add filter after oauth/to

2020-07-24 04:37发布

问题:

I am using Spring Boot 2.1.1.RELEASE (spring-security-oauth2-2.3.4.RELEASE).

I would like to create a filter with precedence after TokenEndpoint#postAccessToken call. Why ? 'cause in that filter I want to take the token from the tokenStore and add it as a cookie to the response.

I would expect that, this will give me what I want:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
    .(...)
    .addFilterAfter(new MyFilter(), BasicAuthenticationFilter.class);
}

But it doesn't. I can see, that BasicAuthenticationFilter is called after successfull authentication on oauth/token but it doesn't enter my MyFilter.

What am I suppose to do to call MyFilter after oauth/token call ?


You want to set cookie from authorization server or from resource server? Is your auth server and resource server both are in same context? or different applications.?

I have two microservices. First one is authorization server, that provides jwt tokens (signed by its private key). Second microservice is a resource server, that validates tokens based on authorization server public key (exposed via REST endpoint by Auth server)

Do you want to set after receiving access_token from authorization server? What > do you want to do by setting cookie?

No. I would like the authorization server to set a cookie when oauth/token call is made by the frontend application. That way the browser is responsible for adding a token to each request rather than my frontend app. That protects me against XSS attack, as the cookie will be set as httpOnly and secure.

Is your plan is to read cookie for getting access_token?

Correct. But that supposed to be done by resource server (haven't done that, yet)

simple way is to create an API for the same functionality. Which takes access_token as request parameter and sets the cookie.

Are you suggesting something like a proxy microservice that stands between frontend application and auth/resource servers ? proxy microservice that is setting jwt token as cookie, and read token from cookie ?

回答1:

No. I would like the authorization server to set a cookie when oauth/token call is made by the frontend application.

You need to add filter before all filters i mean filter order 1 so that request first reaches and last dispatches.

If it was not spring-boot it was much easier with web.xml or java config way of spring configuration. As spring boot does not rely on web.xml all filters are proxy filters except DelegatingFilterProxy(springSecurityFilterChain) before this we can not add any filters.

  • Possible way of achieving your requirement is registering a filter in FilterRegistrationBean with order(1).

  • Give the filter url pattern/oauth/token

  • In your filter use HttpServletResponseWrapper implementation to read response and get access_token and set cookie as per your requirement.

In Any configuration class register filter into FilterRegistrationBean
@Configuration
public class AppInitializer
{
    @Bean
    public FilterRegistrationBean<AccessTokenAlterFilter> sessionTimeoutFilter()
    {
        FilterRegistrationBean<AccessTokenAlterFilter> registrationBean = new FilterRegistrationBean<>();
        AccessTokenAlterFilter filter = new AccessTokenAlterFilter();

        registrationBean.setFilter(filter);
        registrationBean.addUrlPatterns("/oauth/token");
        registrationBean.setOrder(1); // set precedence
        return registrationBean;
    }
}
Your Filter
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AccessTokenAlterFilter implements Filter
{

    Logger OUT = LoggerFactory.getLogger(AccessTokenAlterFilter.class);

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
    {
        OUT.info("[[[[[[[[[[[[STARTED]]]]]]]]]]]]]]");

        CharResponseWrapper wrappedResponse = new CharResponseWrapper((HttpServletResponse) response);
        chain.doFilter(request, wrappedResponse);
        byte[] bytes = wrappedResponse.getByteArray();
        String out = new String(bytes);
        OUT.info("Response String: {}", out);

        response.getOutputStream().write(out.getBytes());

        OUT.info("[[[[[[[[[[[[ENDED]]]]]]]]]]]]]]");
    }

    private static class ByteArrayServletStream extends ServletOutputStream
    {
        ByteArrayOutputStream baos;

        ByteArrayServletStream(ByteArrayOutputStream baos)
        {
            this.baos = baos;
        }

        public void write(int param) throws IOException
        {
            baos.write(param);
        }

        @Override
        public boolean isReady()
        {
            return false;
        }

        @Override
        public void setWriteListener(WriteListener listener)
        {}
    }

    private static class ByteArrayPrintWriter
    {
        private ByteArrayOutputStream   baos    = new ByteArrayOutputStream();
        private PrintWriter             pw      = new PrintWriter(baos);
        private ServletOutputStream     sos     = new ByteArrayServletStream(baos);

        public PrintWriter getWriter()
        {
            return pw;
        }

        public ServletOutputStream getStream()
        {
            return sos;
        }

        byte[] toByteArray()
        {
            return baos.toByteArray();
        }
    }

    public class CharResponseWrapper extends HttpServletResponseWrapper
    {
        private ByteArrayPrintWriter    output;
        private boolean                 usingWriter;

        public CharResponseWrapper(HttpServletResponse response)
        {
            super(response);
            usingWriter = false;
            output = new ByteArrayPrintWriter();
        }

        public byte[] getByteArray()
        {
            return output.toByteArray();
        }

        @Override
        public ServletOutputStream getOutputStream() throws IOException
        {
            if (usingWriter)
            {
                super.getOutputStream();
            }
            usingWriter = true;
            return output.getStream();
        }

        @Override
        public PrintWriter getWriter() throws IOException
        {
            if (usingWriter)
            {
                super.getWriter();
            }
            usingWriter = true;
            return output.getWriter();
        }

        public String toString()
        {
            return output.toString();
        }
    }
}

Previous flow will be still there as given below

You can get response object under control and add the cookie. Just showing logs for your reference.