Spring security oauth2 - 在 oauth/token 调用后添加过滤器

Spring security oauth2 - add filter after oauth/token call

我正在使用 Spring 启动 2.1.1.RELEASE (spring-security-oauth2-2.3.4.RELEASE).

我想在 TokenEndpoint#postAccessToken 调用之后创建一个具有优先级的过滤器。为什么 ?因为在那个过滤器中我想从 tokenStore 中获取令牌并将其作为 cookie 添加到响应中。

我希望,这会给我我想要的东西:

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

但事实并非如此。我可以看到 BasicAuthenticationFilteroauth/token 上成功验证后被调用,但它没有进入我的 MyFilter.

我想在 oauth/token 调用后调用 MyFilter 做什么?


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.?

我有两个微服务。第一个是授权服务器,它提供 jwt 令牌(由其私钥签名)。第二个微服务是一个资源服务器,它根据授权服务器 public 密钥验证令牌(由 Auth 服务器通过 REST 端点公开)

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

没有。我希望授权服务器在前端应用程序进行 oauth/token 调用时设置一个 cookie。这样,浏览器负责为每个请求添加令牌,而不是我的前端应用程序。这可以保护我免受 XSS 攻击,因为 cookie 将被设置为 httpOnlysecure.

Is your plan is to read cookie for getting access_token?

正确。但这应该由资源服务器完成(还没有这样做)

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

您是否建议在前端应用程序和 auth/resource 服务器之间建立代理微服务之类的东西?将 jwt 令牌设置为 cookie 并从 cookie 中读取令牌的代理微服务?

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

您需要在所有过滤器之前添加过滤器,我的意思是过滤器顺序 1,以便请求首先到达并最后发送。

如果不是 spring-boot,使用 spring 配置的 web.xml 或 java 配置方式会容易得多。由于 spring 启动不依赖 web.xml 所有过滤器都是代理过滤器,除了 DelegatingFilterProxy(springSecurityFilterChain) 在此之前我们不能添加任何过滤器。

  • 实现您的要求的可能方法是在 FilterRegistrationBean 中使用 order(1).

  • 注册过滤器
  • 给过滤器url模式/oauth/token

  • 在您的过滤器中使用 HttpServletResponseWrapper 实现来读取响应并获取 access_token 并根据您的要求设置 cookie。

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();
        }
    }
}

之前的流量仍然存在,如下所示

您可以控制响应对象并添加cookie。仅显示日志供您参考。