Ho bisogno di estendere la sicurezza di primavera per cancellare il contenuto della risposta http e posizionare il risultato all'interno di un'intestazione. Il mio approccio è quello di creare un filtro servlet che legge la risposta e posiziona l'intestazione appropriata. Il filtro è registrato con sicurezza Spring tramite un plugin separato. L'implementazione è in gran parte presa da .Grails sicurezza molla, filtri servlet e risposta
L'intera configurazione funziona perfettamente quando l'applicazione finale utilizza "rendering" nel controller per inviare JSON al client. Tuttavia, se gli stessi dati vengono formattati tramite "rispondi", un 404 viene restituito al client. Sono in perdita per spiegare la differenza.
Per riferimento tutto è graal versione 2.3.11 e la molla del nucleo di sicurezza versione 2.0-RC4
Registrare il filtro tramite doWithSpring di mio plug
responseHasher(ResponseHasher)
SpringSecurityUtils.registerFilter(
'responseHasher', SecurityFilterPosition.LAST.order - 1)
mia implementazione filtro
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() {
}
}
Il Implementazione HttpServletResponseCopier. L'unico cambiamento dalla fonte è quello di scavalcare tutte e 3 le firme dei metodi di scrittura anziché solo quella.
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();
}
}
}
E infine il mio metodo di controllo nell'applicazione effettiva
@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
}
Tutta la comprensione sarebbe apprezzata come io non capisco perché rende avrebbe funzionato e rispondere no. Inoltre, sono aperto ad altri approcci per risolvere questo bisogno se quello che sto tentando non funzionerà solo con i graal. Grazie in anticipo.
Credo che abbia qualcosa a che fare con il contenuto negoziale: http://grails.github.io/grails-doc/2.3.x/guide/single.html #contentNegotiation. Prova a chiamare 'myAction' con parametro' format = json' o aggiungi un'intestazione di richiesta 'Accept'. – defectus
Grazie. Ci ho provato e non ha avuto alcun effetto. Posso collegare un debugger e vedere che sembra che stia facendo il metodo di risposta e nel renderer json, eliminando JSON valido. La mia (presumibilmente ignorante) ipotesi è che il problema è da qualche parte più in basso nello stack –
Perché non usi i filtri predefiniti di Grails e metti una nuova intestazione nella sezione 'after()'? –