// package: com.example.security
import javax.servlet.*;
import javax.servlet.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Base64;
@Component
public class CspNonceFilter extends OncePerRequestFilter {
private static final SecureRandom RNG = new SecureRandom();
private static String newNonce() {
byte[] b = new byte[16]; // 128-bit
RNG.nextBytes(b);
return Base64.getEncoder().encodeToString(b);
}
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse resp, FilterChain chain)
throws ServletException, IOException {
String nonce = newNonce();
// 給 Thymeleaf 取用
req.setAttribute("cspNonce", nonce);
// 建議的 CSP(object-src 改成 none,減少攻擊面)
String csp = "default-src 'self'; "
+ "script-src 'self' 'nonce-" + nonce + "'; "
+ "object-src 'none'; "
+ "frame-ancestors 'self'";
resp.setHeader("Content-Security-Policy", csp);
chain.doFilter(req, resp);
}
}
Ly8gcGFja2FnZTogY29tLmV4YW1wbGUuc2VjdXJpdHkKCmltcG9ydCBqYXZheC5zZXJ2bGV0Lio7CgppbXBvcnQgamF2YXguc2VydmxldC5odHRwLio7CgppbXBvcnQgb3JnLnNwcmluZ2ZyYW1ld29yay5zdGVyZW90eXBlLkNvbXBvbmVudDsKCmltcG9ydCBvcmcuc3ByaW5nZnJhbWV3b3JrLndlYi5maWx0ZXIuT25jZVBlclJlcXVlc3RGaWx0ZXI7CgppbXBvcnQgamF2YS5pby5JT0V4Y2VwdGlvbjsKCmltcG9ydCBqYXZhLnNlY3VyaXR5LlNlY3VyZVJhbmRvbTsKCmltcG9ydCBqYXZhLnV0aWwuQmFzZTY0OwoKCgpAQ29tcG9uZW50CgpwdWJsaWMgY2xhc3MgQ3NwTm9uY2VGaWx0ZXIgZXh0ZW5kcyBPbmNlUGVyUmVxdWVzdEZpbHRlciB7CgogICAgcHJpdmF0ZSBzdGF0aWMgZmluYWwgU2VjdXJlUmFuZG9tIFJORyA9IG5ldyBTZWN1cmVSYW5kb20oKTsKCgoKICAgIHByaXZhdGUgc3RhdGljIFN0cmluZyBuZXdOb25jZSgpIHsKCiAgICAgICAgYnl0ZVtdIGIgPSBuZXcgYnl0ZVsxNl07IC8vIDEyOC1iaXQKCiAgICAgICAgUk5HLm5leHRCeXRlcyhiKTsKCiAgICAgICAgcmV0dXJuIEJhc2U2NC5nZXRFbmNvZGVyKCkuZW5jb2RlVG9TdHJpbmcoYik7CgogICAgfQoKCgogICAgQE92ZXJyaWRlCgogICAgcHJvdGVjdGVkIHZvaWQgZG9GaWx0ZXJJbnRlcm5hbChIdHRwU2VydmxldFJlcXVlc3QgcmVxLCBIdHRwU2VydmxldFJlc3BvbnNlIHJlc3AsIEZpbHRlckNoYWluIGNoYWluKQoKICAgICAgICAgICAgdGhyb3dzIFNlcnZsZXRFeGNlcHRpb24sIElPRXhjZXB0aW9uIHsKCgoKICAgICAgICBTdHJpbmcgbm9uY2UgPSBuZXdOb25jZSgpOwoKCgogICAgICAgIC8vIOe1piBUaHltZWxlYWYg5Y+W55SoCgogICAgICAgIHJlcS5zZXRBdHRyaWJ1dGUoImNzcE5vbmNlIiwgbm9uY2UpOwoKCgogICAgICAgIC8vIOW7uuitsOeahCBDU1DvvIhvYmplY3Qtc3JjIOaUueaIkCBub25l77yM5rib5bCR5pS75pOK6Z2i77yJCgogICAgICAgIFN0cmluZyBjc3AgPSAiZGVmYXVsdC1zcmMgJ3NlbGYnOyAiCgogICAgICAgICAgICAgICAgICAgKyAic2NyaXB0LXNyYyAnc2VsZicgJ25vbmNlLSIgKyBub25jZSArICInOyAiCgogICAgICAgICAgICAgICAgICAgKyAib2JqZWN0LXNyYyAnbm9uZSc7ICIKCiAgICAgICAgICAgICAgICAgICArICJmcmFtZS1hbmNlc3RvcnMgJ3NlbGYnIjsKCiAgICAgICAgcmVzcC5zZXRIZWFkZXIoIkNvbnRlbnQtU2VjdXJpdHktUG9saWN5IiwgY3NwKTsKCgoKICAgICAgICBjaGFpbi5kb0ZpbHRlcihyZXEsIHJlc3ApOwoKICAgIH0KCn0K