?Content-Security-Policy
?對(duì)網(wǎng)絡(luò)安全很重要。然而,它還不是主流,它的語法很難,它相當(dāng)令人望而卻步,工具很少對(duì)其提供靈活的支持。
雖然 Spring Security 確實(shí)有一個(gè)內(nèi)置的內(nèi)容安全策略 (CSP) 配置,但它允許您指定策略字符串,而不是動(dòng)態(tài)構(gòu)建它。在某些情況下,您需要的不止這些。
特別是,CSP 不鼓勵(lì)用戶使用內(nèi)聯(lián) javascript,因?yàn)樗肓寺┒?。如果你真的需要它,你可以使?unsafe-inline
?,但這是一個(gè)糟糕的方法,因?yàn)樗穸?CSP 的全部意義。該頁面上顯示的替代方法是使用?hash
?或?nonce
?。
如果您使用?.and().headers().contentSecurityPolicy(policy).
?策略字符串是靜態(tài)的,因此您無法為每個(gè)請(qǐng)求生成隨機(jī)數(shù)。擁有靜態(tài)隨機(jī)數(shù)是沒有用的。首先,您定義一個(gè) CSP nonce 過濾器:
public class CSPNonceFilter extends GenericFilterBean {
private static final int NONCE_SIZE = 32; //recommended is at least 128 bits/16 bytes
private static final String CSP_NONCE_ATTRIBUTE = "cspNonce";
private SecureRandom secureRandom = new SecureRandom();
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
byte[] nonceArray = new byte[NONCE_SIZE];
secureRandom.nextBytes(nonceArray);
String nonce = Base64.getEncoder().encodeToString(nonceArray);
request.setAttribute(CSP_NONCE_ATTRIBUTE, nonce);
chain.doFilter(request, new CSPNonceResponseWrapper(response, nonce));
}
/**
* Wrapper to fill the nonce value
*/
public static class CSPNonceResponseWrapper extends HttpServletResponseWrapper {
private String nonce;
public CSPNonceResponseWrapper(HttpServletResponse response, String nonce) {
super(response);
this.nonce = nonce;
}
@Override
public void setHeader(String name, String value) {
if (name.equals("Content-Security-Policy") && StringUtils.isNotBlank(value)) {
super.setHeader(name, value.replace("{nonce}", nonce));
} else {
super.setHeader(name, value);
}
}
@Override
public void addHeader(String name, String value) {
if (name.equals("Content-Security-Policy") && StringUtils.isNotBlank(value)) {
super.addHeader(name, value.replace("{nonce}", nonce));
} else {
super.addHeader(name, value);
}
}
}
}
然后使用以下命令使用spring安全性對(duì)其進(jìn)行配置:??.addFilterBefore(new CSPNonceFilter()
?, ?HeaderWriterFilter.class)
??。
策略字符串?`nonce-{nonce}`
?應(yīng)該包含在每個(gè)請(qǐng)求中被隨機(jī)數(shù)替換的字符串。
過濾器設(shè)置在?HeaderWriterFilter
?之前,以便它可以包裝響應(yīng)并攔截對(duì)設(shè)置標(biāo)頭的所有調(diào)用。為什么它不能通過在 ?HeaderWriterFiilter
?設(shè)置標(biāo)題后,使用? response.setHeader(..)
?覆蓋標(biāo)題- 因?yàn)轫憫?yīng)已經(jīng)提交并且覆蓋沒有任何作用。
然后在您出于某種原因需要內(nèi)聯(lián)腳本的頁面中,您可以使用:
<script nonce="{{ cspNonce }}">...</script>
(我使用的是 Pebble 模板語法;但您可以使用任何模板來輸出請(qǐng)求屬性csp-nonce
?)
再一次,內(nèi)聯(lián) javascript 很少是一個(gè)好主意,但有時(shí)它是必要的,至少是暫時(shí)的。
例如,如果您將 CSP 添加到遺留應(yīng)用程序,并且無法重寫所有內(nèi)容。
我們應(yīng)該到處都有 CSP,但是構(gòu)建策略應(yīng)該得到我們使用的框架的幫助,否則編寫一個(gè)不會(huì)破壞您的應(yīng)用程序并且同時(shí)安全的適當(dāng)策略是相當(dāng)乏味的。