Grails spring security、servlet 过滤器和响应

Posted

技术标签:

【中文标题】Grails spring security、servlet 过滤器和响应【英文标题】:Grails spring security, servlet filters, and respond 【发布时间】:2015-11-11 19:41:38 【问题描述】:

我需要扩展 spring security 以散列 http 响应内容并将结果放在标头中。我的方法是创建一个 servlet 过滤器来读取响应并放置适当的标头。该过滤器通过一个单独的插件向 spring security 注册。实现主要取自here。

当最终应用程序在控制器中使用“render”将 JSON 输出到客户端时,整个设置完美运行。但是,如果通过“响应”对相同的数据进行格式化,则会向客户端返回 404。我无法解释其中的区别。

作为参考,一切都是 grails 版本 2.3.11 和 spring security core 版本 2.0-RC4

通过我插件的 doWithSpring 注册过滤器

responseHasher(ResponseHasher)

SpringSecurityUtils.registerFilter(
                'responseHasher', SecurityFilterPosition.LAST.order - 1)

我的过滤器实现

public class ResponseHasher implements Filter

    @Override
    public void init(FilterConfig config) throws ServletException 
    

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


        HttpServletResponseCopier wrapper = new HttpServletResponseCopier((HttpServletResponse)response);

        chain.doFilter(request, wrapper);
        wrapper.flushBuffer();

        /*
        Take the response, hash it, and set it in a header.  for brevity sake just prove we can read it for now
        and set a static header
        */
        byte[] copy = wrapper.getCopy();
        System.out.println(new String(copy, response.getCharacterEncoding()));
        wrapper.setHeader("foo","bar");

    

    @Override
    public void destroy() 

    


HttpServletResponseCopier 实现。源代码的唯一变化是覆盖所有 3 个 write 方法签名,而不仅仅是一个。

class HttpServletResponseCopier extends HttpServletResponseWrapper

    private ServletOutputStream outputStream;
    private PrintWriter writer;
    private ServletOutputStreamCopier copier;

    public HttpServletResponseCopier(HttpServletResponse response) throws IOException 
        super(response);
    

    @Override
    public ServletOutputStream getOutputStream() throws IOException 
        if (writer != null) 
            throw new IllegalStateException("getWriter() has already been called on this response.");
        

        if (outputStream == null) 
            outputStream = getResponse().getOutputStream();
            copier = new ServletOutputStreamCopier(outputStream);
        

        return copier;
    

    @Override
    public PrintWriter getWriter() throws IOException 
        if (outputStream != null) 
            throw new IllegalStateException("getOutputStream() has already been called on this response.");
        

        if (writer == null) 
            copier = new ServletOutputStreamCopier(getResponse().getOutputStream());
            writer = new PrintWriter(new OutputStreamWriter(copier, getResponse().getCharacterEncoding()), true);
        

        return writer;
    

    @Override
    public void flushBuffer() throws IOException 
        if (writer != null) 
            writer.flush();
         else if (outputStream != null) 
            copier.flush();
        
    

    public byte[] getCopy() 
        if (copier != null) 
            return copier.getCopy();
         else 
            return new byte[0];
        
    

    private class ServletOutputStreamCopier extends ServletOutputStream 

        private OutputStream outputStream;
        private ByteArrayOutputStream copy;

        public ServletOutputStreamCopier(OutputStream outputStream) 
            this.outputStream = outputStream;
            this.copy = new ByteArrayOutputStream(1024);
        

        @Override
        public void write(int b) throws IOException 
            outputStream.write(b);
            copy.write(b);
        

        @Override
        public void write(byte[] b,int off, int len) throws IOException 
            outputStream.write(b,off,len);
            copy.write(b,off,len);
        

        @Override
        public void write(byte[] b) throws IOException 
            outputStream.write(b);
            copy.write(b);
        

        public byte[] getCopy() 
            return copy.toByteArray();
        

    

最后是我在实际应用中的控制器方法

@Secured()
    def myAction() 
        def thing = Thing.get(1)  //thing can be any domain object really.  in this case we created thing 1 in bootstap

         //throws a 404
         respond(thing)
         /*
         works as expected, output is both rendered 
         and sent to system out, header "foo" is in response
         /*
         //render thing as JSON

任何见解都将不胜感激,因为我不明白为什么渲染会起作用而响应不会。此外,如果我正在尝试的方法在 grails 中不起作用,我愿意接受其他方法来解决此需求。提前致谢。

【问题讨论】:

我猜这与内容协商有关:grails.github.io/grails-doc/2.3.x/guide/…。尝试使用参数format=json 调用myAction 或添加Accept 请求标头。 谢谢。我试过了,它没有任何影响。我可以附加一个调试器,并看到它似乎至少是通过响应方法进入 json 渲染器,踢出有效的 JSON。我(诚然无知)的猜测是问题出在堆栈中更远的地方 你为什么不使用默认的 Grails 过滤器并将新标题放在 after() 部分? 【参考方案1】:

我将所有项目都放在 grails 中,但遇到了类似的问题。我不得不做出一些改变。

对于注册,我使用SpringSecurityUtils.clientRegisterFilter 方法,就像我在Bootstrap.groovy 应用程序中所做的那样。 另外,我在resources.groovy中声明了过滤器

它适用于渲染,但 404 响应。于是将响应改为:

respond user, [formats:['json']]

在我删除您的过滤器后它起作用了。每当我放置你的过滤器并且它试图找到 action.gsp 时,我都会得到 404。

我对@9​​87654325@ 进行了更改,实现了closeflush 方法的委托,并且它在渲染和响应方面工作得很好:

class HttpServletResponseCopier extends HttpServletResponseWrapper 

    private ServletOutputStream outputStream;
    private PrintWriter writer;
    private ServletOutputStreamCopier copier;

    public HttpServletResponseCopier(HttpServletResponse response)
            throws IOException 
        super(response);
    

    @Override
    public ServletOutputStream getOutputStream() throws IOException 
        if (writer != null) 
            throw new IllegalStateException(
                    "getWriter() has already been called on this response.");
        

        if (outputStream == null) 
            outputStream = getResponse().getOutputStream();
            copier = new ServletOutputStreamCopier(outputStream);
        

        return copier;
    

    @Override
    public PrintWriter getWriter() throws IOException 
        if (outputStream != null) 
            throw new IllegalStateException(
                    "getOutputStream() has already been called on this response.");
        

        if (writer == null) 
            copier = new ServletOutputStreamCopier(getResponse()
                    .getOutputStream());
            writer = new PrintWriter(new OutputStreamWriter(copier,
                    getResponse().getCharacterEncoding()), true);
        

        return writer;
    

    @Override
    public void flushBuffer() throws IOException 
        if (writer != null) 
            writer.flush();
         else if (outputStream != null) 
            copier.flush();
        
    

    public byte[] getCopy() 
        if (copier != null) 
            return copier.getCopy();
         else 
            return new byte[0];
        
    

    private class ServletOutputStreamCopier extends ServletOutputStream 

        private OutputStream outputStream;
        private ByteArrayOutputStream copy;

        public ServletOutputStreamCopier(OutputStream outputStream) 
            this.outputStream = outputStream;
            this.copy = new ByteArrayOutputStream(1024);
        

        @Override
        public void write(int b) throws IOException 
            outputStream.write(b);
            copy.write(b);
        

        @Override
        public void write(byte[] b, int off, int len) throws IOException 
            outputStream.write(b, off, len);
            copy.write(b, off, len);
        

        @Override
        public void write(byte[] b) throws IOException 
            outputStream.write(b);
            copy.write(b);
        

        @Override
        public void flush() throws IOException 
            outputStream.flush();
            copy.flush();
        

        @Override
        public void close() throws IOException 
            outputStream.close();
            copy.close();
        

        public byte[] getCopy() 
            return copy.toByteArray();
        

    

我没有详细介绍响应实现细节,但我认为它会引起一些混乱,因为它没有刷新或关闭的方法,并且有一个后备来调用视图而不是渲染 json。

我知道这有点晚了,但现在它正在工作。

resources.groovy

beans = 

    responseHasher(ResponseHasher)

    


Boostrap.groovy

def init =  servletContext ->

    .

    SpringSecurityUtils.clientRegisterFilter('responseHasher', SecurityFilterPosition.LAST.order - 1)


最好, 埃德

【讨论】:

以上是关于Grails spring security、servlet 过滤器和响应的主要内容,如果未能解决你的问题,请参考以下文章

grails-spring-security-rest 插件和悲观锁定

Grails + spring-security-core:用户登录后如何分配角色?

Grails Spring Security注释问题

Grails Spring Security 插件网址

Grails spring-security-oauth-google:如何设置

Grails/Spring Security:无法使用新创建的用户登录