一、技术实现方案
1.1 内容安全策略架构
内容安全策略体系
├── CSP (Content Security Policy)
│ ├── 脚本源限制
│ ├── 样式源限制
│ ├── 图片源限制
│ ├── 字体源限制
│ ├── 连接源限制
│ └── Frame源限制
│
├── SRI (Subresource Integrity)
│ ├── 完整性校验
│ ├── Hash生成
│ ├── CDN资源保护
│ └── 降级处理
│
├── HTTPS部署
│ ├── SSL/TLS配置
│ ├── HSTS策略
│ ├── 证书管理
│ └── 混合内容处理
│
└── Cookie安全
├── Secure标志
├── HttpOnly标志
├── SameSite属性
├── Domain和Path设置
└── 过期时间管理
1.2 技术栈
- CSP: Content-Security-Policy响应头
- SRI: integrity属性、sha256/sha384/sha512
- HTTPS: Let's Encrypt、SSL Labs
- Cookie: Secure、HttpOnly、SameSite
二、CSP (内容安全策略) 配置
2.1 CSP管理器
csp-manager.js
export class CSPManager {
constructor() {
this.policies = {
'default-src': ["'self'"],
'script-src': ["'self'"],
'style-src': ["'self'", "'unsafe-inline'"],
'img-src': ["'self'", 'data:', 'https:'],
'font-src': ["'self'", 'data:'],
'connect-src': ["'self'"],
'frame-src': ["'none'"],
'object-src': ["'none'"],
'base-uri': ["'self'"],
'form-action': ["'self'"],
'frame-ancestors': ["'none'"]
}
this.reportUri = '/api/csp-report'
this.reportOnly = false
}
// 添加源
addSource(directive, source) {
if (!this.policies[directive]) {
this.policies[directive] = []
}
if (!this.policies[directive].includes(source)) {
this.policies[directive].push(source)
}
}
// 移除源
removeSource(directive, source) {
if (this.policies[directive]) {
this.policies[directive] = this.policies[directive].filter(s => s !== source)
}
}
// 允许内联脚本(使用nonce)
allowInlineScript(nonce) {
this.addSource('script-src', `'nonce-${nonce}'`)
}
// 允许内联样式(使用nonce)
allowInlineStyle(nonce) {
this.addSource('style-src', `'nonce-${nonce}'`)
}
// 生成CSP字符串
generateCSP() {
const directives = Object.entries(this.policies)
.filter(([_, sources]) => sources.length > 0)
.map(([directive, sources]) => `${directive} ${sources.join(' ')}`)
.join('; ')
const reportDirective = `report-uri ${this.reportUri}`
return `${directives}; ${reportDirective}`
}
// 生成CSP响应头
generateHeader() {
const headerName = this.reportOnly
? 'Content-Security-Policy-Report-Only'
: 'Content-Security-Policy'
return {
[headerName]: this.generateCSP()
}
}
// 生成meta标签
generateMetaTag() {
return `<meta http-equiv="Content-Security-Policy" content="${this.generateCSP()}">`
}
// 解析CSP违规报告
parseViolationReport(report) {
return {
documentUri: report['document-uri'],
violatedDirective: report['violated-directive'],
effectiveDirective: report['effective-directive'],
blockedUri: report['blocked-uri'],
sourceFile: report['source-file'],
lineNumber: report['line-number'],
columnNumber: report['column-number'],
statusCode: report['status-code']
}
}
// 常见场景配置
// 严格模式(最安全)
strictMode() {
this.policies = {
'default-src': ["'none'"],
'script-src': ["'self'"],
'style-src': ["'self'"],
'img-src': ["'self'"],
'font-src': ["'self'"],
'connect-src': ["'self'"],
'frame-src': ["'none'"],
'object-src': ["'none'"],
'base-uri': ["'self'"],
'form-action': ["'self'"],
'frame-ancestors': ["'none'"],
'upgrade-insecure-requests': []
}
}
// CDN模式(允许CDN资源)
cdnMode(cdnDomains = []) {
this.addSource('script-src', ...cdnDomains)
this.addSource('style-src', ...cdnDomains)
this.addSource('font-src', ...cdnDomains)
this.addSource('img-src', ...cdnDomains)
}
// 开发模式(宽松配置)
developmentMode() {
this.policies['script-src'].push("'unsafe-eval'")
this.policies['style-src'].push("'unsafe-inline'")
this.reportOnly = true
}
}
export default new CSPManager()
2.2 CSP配置演示组件
CSPDemo.vue
<script setup>
import { ref, computed } from 'vue'
import cspManager from './csp-manager.js'
const selectedMode = ref('custom')
const customPolicies = ref({
'script-src': ["'self'", 'https://cdn.jsdelivr.net'],
'style-src': ["'self'", "'unsafe-inline'"],
'img-src': ["'self'", 'data:', 'https:'],
'connect-src': ["'self'", 'https://api.example.com']
})
const violations = ref([])
// CSP预设模式
const modes = [
{
id: 'strict',
name: '严格模式',
description: '最安全的配置,禁止所有外部资源',
config: {
'default-src': ["'none'"],
'script-src': ["'self'"],
'style-src': ["'self'"],
'img-src': ["'self'"],
'font-src': ["'self'"],
'connect-src': ["'self'"]
}
},
{
id: 'standard',
name: '标准模式',
description: '允许常用CDN和必要的内联样式',
config: {
'default-src': ["'self'"],
'script-src': ["'self'", 'https://cdn.jsdelivr.net'],
'style-src': ["'self'", "'unsafe-inline'"],
'img-src': ["'self'", 'data:', 'https:'],
'font-src': ["'self'", 'data:'],
'connect-src': ["'self'"]
}
},
{
id: 'development',
name: '开发模式',
description: '宽松配置,方便开发调试',
config: {
'default-src': ["'self'"],
'script-src': ["'self'", "'unsafe-eval'", "'unsafe-inline'"],
'style-src': ["'self'", "'unsafe-inline'"],
'img-src': ["'self'", 'data:', 'https:', 'http:'],
'connect-src': ["'self'", '*']
}
}
]
// 生成的CSP字符串
const generatedCSP = computed(() => {
const config = selectedMode.value === 'custom'
? customPolicies.value
: modes.find(m => m.id === selectedMode.value)?.config || {}
const directives = Object.entries(config)
.map(([directive, sources]) => `${directive} ${sources.join(' ')}`)
.join('; ')
return directives
})
// 生成meta标签
const metaTag = computed(() => {
return `<meta http-equiv="Content-Security-Policy" content="${generatedCSP.value}">`
})
// 生成服务器配置
const serverConfigs = computed(() => {
return [
{
server: 'Express (Node.js)',
code: `app.use((req, res, next) => {
res.setHeader('Content-Security-Policy', '${generatedCSP.value}')
next()
})`
},
{
server: 'Nginx',
code: `add_header Content-Security-Policy "${generatedCSP.value}";`
},
{
server: 'Apache',
code: `Header set Content-Security-Policy "${generatedCSP.value}"`
}
]
})
// 添加自定义指令
const newDirective = ref('')
const newSource = ref('')
const addCustomDirective = () => {
if (newDirective.value && newSource.value) {
if (!customPolicies.value[newDirective.value]) {
customPolicies.value[newDirective.value] = []
}
if (!customPolicies.value[newDirective.value].includes(newSource.value)) {
customPolicies.value[newDirective.value].push(newSource.value)
}
newDirective.value = ''
newSource.value = ''
}
}
// 移除源
const removeSource = (directive, source) => {
customPolicies.value[directive] = customPolicies.value[directive].filter(s => s !== source)
}
// 模拟CSP违规
const simulateViolation = (type) => {
const violation = {
id: Date.now(),
type: type,
timestamp: new Date().toLocaleString(),
documentUri: 'https://example.com/page',
blockedUri: '',
violatedDirective: '',
message: ''
}
switch (type) {
case 'inline-script':
violation.blockedUri = 'inline'
violation.violatedDirective = 'script-src'
violation.message = '页面尝试执行内联脚本'
break
case 'external-script':
violation.blockedUri = 'https://evil.com/malicious.js'
violation.violatedDirective = 'script-src'
violation.message = '页面尝试加载外部脚本'
break
case 'inline-style':
violation.blockedUri = 'inline'
violation.violatedDirective = 'style-src'
violation.message = '页面尝试使用内联样式'
break
case 'external-image':
violation.blockedUri = 'http://untrusted.com/image.jpg'
violation.violatedDirective = 'img-src'
violation.message = '页面尝试加载不安全来源的图片'
break
}
violations.value.unshift(violation)
if (violations.value.length > 10) {
violations.value.pop()
}
}
// 清空违规记录
const clearViolations = () => {
violations.value = []
}
</script>
<template>
<div class="csp-demo">
<div class="demo-header">
<h2>CSP (内容安全策略) 配置</h2>
<p class="subtitle">配置和测试Content Security Policy</p>
</div>
<!-- 模式选择 -->
<div class="mode-section">
<h3>选择配置模式</h3>
<div class="mode-grid">
<div
v-for="mode in modes"
:key="mode.id"
class="mode-card"
:class="{ active: selectedMode === mode.id }"
@click="selectedMode = mode.id"
>
<h4>{{ mode.name }}</h4>
<p>{{ mode.description }}</p>
</div>
<div
class="mode-card"
:class="{ active: selectedMode === 'custom' }"
@click="selectedMode = 'custom'"
>
<h4>自定义模式</h4>
<p>完全自定义CSP配置</p>
</div>
</div>
</div>
<!-- 自定义配置 -->
<div v-if="selectedMode === 'custom'" class="custom-section">
<h3>自定义CSP配置</h3>
<div class="add-directive">
<input
v-model="newDirective"
type="text"
placeholder="指令名称 (如: script-src)"
class="directive-input"
/>
<input
v-model="newSource"
type="text"
placeholder="源 (如: https://cdn.example.com)"
class="source-input"
/>
<button @click="addCustomDirective" class="add-btn">
添加
</button>
</div>
<div class="directives-list">
<div
v-for="(sources, directive) in customPolicies"
:key="directive"
class="directive-item"
>
<div class="directive-name">{{ directive }}</div>
<div class="sources">
<span
v-for="source in sources"
:key="source"
class="source-tag"
>
{{ source }}
<button @click="removeSource(directive, source)" class="remove-btn">×</button>
</span>
</div>
</div>
</div>
</div>
<!-- 生成的CSP -->
<div class="output-section">
<h3>生成的CSP策略</h3>
<div class="output-box">
<div class="output-label">CSP字符串:</div>
<pre class="output-code">{{ generatedCSP }}</pre>
</div>
<div class="output-box">
<div class="output-label">HTML Meta标签:</div>
<pre class="output-code">{{ metaTag }}</pre>
</div>
</div>
<!-- 服务器配置 -->
<div class="server-config-section">
<h3>服务器配置示例</h3>
<div class="config-grid">
<div
v-for="config in serverConfigs"
:key="config.server"
class="config-card"
>
<h4>{{ config.server }}</h4>
<pre><code>{{ config.code }}</code></pre>
</div>
</div>
</div>
<!-- CSP违规测试 -->
<div class="violation-test-section">
<h3>CSP违规模拟</h3>
<p class="hint">点击按钮模拟不同类型的CSP违规</p>
<div class="test-buttons">
<button @click="simulateViolation('inline-script')" class="test-btn">
内联脚本
</button>
<button @click="simulateViolation('external-script')" class="test-btn">
外部脚本
</button>
<button @click="simulateViolation('inline-style')" class="test-btn">
内联样式
</button>
<button @click="simulateViolation('external-image')" class="test-btn">
不安全图片
</button>
</div>
</div>
<!-- 违规记录 -->
<div class="violations-section">
<div class="violations-header">
<h3>CSP违规记录</h3>
<button @click="clearViolations" class="clear-btn">清空</button>
</div>
<div class="violations-list">
<div
v-for="violation in violations"
:key="violation.id"
class="violation-item"
>
<div class="violation-header">
<span class="violation-type">{{ violation.type }}</span>
<span class="violation-time">{{ violation.timestamp }}</span>
</div>
<div class="violation-details">
<div class="detail-row">
<label>违反指令:</label>
<code>{{ violation.violatedDirective }}</code>
</div>
<div class="detail-row">
<label>被阻止的URI:</label>
<code>{{ violation.blockedUri }}</code>
</div>
<div class="detail-row">
<label>说明:</label>
<span>{{ violation.message }}</span>
</div>
</div>
</div>
<div v-if="violations.length === 0" class="empty-violations">
暂无违规记录
</div>
</div>
</div>
<!-- CSP指令说明 -->
<div class="directives-doc-section">
<h3>CSP指令说明</h3>
<div class="doc-grid">
<div class="doc-card">
<h4>default-src</h4>
<p>默认策略,作为其他指令的后备</p>
</div>
<div class="doc-card">
<h4>script-src</h4>
<p>控制JavaScript来源</p>
</div>
<div class="doc-card">
<h4>style-src</h4>
<p>控制CSS样式来源</p>
</div>
<div class="doc-card">
<h4>img-src</h4>
<p>控制图片来源</p>
</div>
<div class="doc-card">
<h4>font-src</h4>
<p>控制字体文件来源</p>
</div>
<div class="doc-card">
<h4>connect-src</h4>
<p>控制AJAX、WebSocket等连接来源</p>
</div>
<div class="doc-card">
<h4>frame-src</h4>
<p>控制iframe来源</p>
</div>
<div class="doc-card">
<h4>object-src</h4>
<p>控制object、embed、applet标签</p>
</div>
</div>
</div>
<!-- 最佳实践 -->
<div class="best-practices-section">
<h3>CSP最佳实践</h3>
<ul class="practices-list">
<li>从严格策略开始,逐步放宽而非反向</li>
<li>先使用report-only模式测试,确认无误后再启用强制模式</li>
<li>避免使用unsafe-inline和unsafe-eval</li>
<li>使用nonce或hash允许特定的内联脚本</li>
<li>将所有资源迁移到HTTPS</li>
<li>定期审查和更新CSP策略</li>
<li>配置report-uri收集违规报告</li>
<li>在生产环境禁用unsafe指令</li>
</ul>
</div>
</div>
</template>
<style scoped>
.csp-demo {
padding: 20px;
background: #f5f7fa;
min-height: 100vh;
}
.demo-header {
text-align: center;
margin-bottom: 40px;
}
.demo-header h2 {
margin: 0 0 10px 0;
font-size: 32px;
color: #303133;
}
.subtitle {
margin: 0;
font-size: 16px;
color: #909399;
}
/* 各个section的通用样式 */
.mode-section,
.custom-section,
.output-section,
.server-config-section,
.violation-test-section,
.violations-section,
.directives-doc-section,
.best-practices-section {
margin-bottom: 30px;
padding: 24px;
background: white;
border-radius: 8px;
}
.mode-section h3,
.custom-section h3,
.output-section h3,
.server-config-section h3,
.violation-test-section h3,
.violations-section h3,
.directives-doc-section h3,
.best-practices-section h3 {
margin: 0 0 20px 0;
font-size: 18px;
color: #303133;
}
/* 模式选择 */
.mode-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 16px;
}
.mode-card {
padding: 20px;
border: 2px solid #e4e7ed;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
}
.mode-card:hover {
border-color: #409eff;
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.2);
}
.mode-card.active {
border-color: #409eff;
background: #ecf5ff;
}
.mode-card h4 {
margin: 0 0 8px 0;
font-size: 16px;
color: #303133;
}
.mode-card p {
margin: 0;
font-size: 14px;
color: #606266;
line-height: 1.6;
}
/* 自定义配置 */
.add-directive {
display: flex;
gap: 12px;
margin-bottom: 20px;
}
.directive-input,
.source-input {
flex: 1;
padding: 10px;
border: 1px solid #dcdfe6;
border-radius: 4px;
font-size: 14px;
}
.add-btn {
padding: 10px 24px;
background: #409eff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
}
.directives-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.directive-item {
padding: 16px;
background: #f5f7fa;
border-radius: 8px;
}
.directive-name {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
}
.sources {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.source-tag {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
background: white;
border: 1px solid #409eff;
border-radius: 16px;
font-size: 13px;
color: #409eff;
}
.source-tag .remove-btn {
padding: 0;
width: 16px;
height: 16px;
background: #f56c6c;
color: white;
border: none;
border-radius: 50%;
cursor: pointer;
font-size: 12px;
line-height: 1;
}
/* 输出区域 */
.output-box {
margin-bottom: 20px;
}
.output-label {
font-size: 14px;
font-weight: 600;
color: #606266;
margin-bottom: 8px;
}
.output-code {
margin: 0;
padding: 16px;
background: #282c34;
border-radius: 4px;
color: #abb2bf;
font-size: 13px;
line-height: 1.6;
overflow-x: auto;
font-family: 'Courier New', monospace;
}
/* 服务器配置 */
.config-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 20px;
}
.config-card {
padding: 20px;
background: #f5f7fa;
border-radius: 8px;
}
.config-card h4 {
margin: 0 0 12px 0;
font-size: 16px;
color: #303133;
}
.config-card pre {
margin: 0;
padding: 16px;
background: #282c34;
border-radius: 4px;
overflow-x: auto;
}
.config-card code {
font-size: 13px;
line-height: 1.6;
color: #abb2bf;
font-family: 'Courier New', monospace;
}
/* 违规测试 */
.hint {
margin: 0 0 16px 0;
padding: 12px;
background: #ecf5ff;
border-left: 4px solid #409eff;
border-radius: 4px;
font-size: 14px;
color: #409eff;
}
.test-buttons {
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.test-btn {
padding: 10px 20px;
background: #e6a23c;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
transition: all 0.3s;
}
.test-btn:hover {
opacity: 0.8;
}
/* 违规记录 */
.violations-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.violations-header h3 {
margin: 0;
}
.clear-btn {
padding: 6px 16px;
background: #f4f4f5;
color: #606266;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
}
.violations-list {
max-height: 400px;
overflow-y: auto;
}
.violation-item {
padding: 16px;
background: #fef0f0;
border-left: 4px solid #f56c6c;
border-radius: 4px;
margin-bottom: 12px;
}
.violation-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.violation-type {
padding: 4px 12px;
background: #f56c6c;
color: white;
border-radius: 12px;
font-size: 12px;
font-weight: 700;
}
.violation-time {
font-size: 12px;
color: #c0c4cc;
}
.violation-details {
display: flex;
flex-direction: column;
gap: 8px;
}
.detail-row {
display: flex;
gap: 12px;
font-size: 13px;
}
.detail-row label {
font-weight: 600;
color: #606266;
min-width: 80px;
}
.detail-row code {
padding: 2px 6px;
background: white;
border-radius: 4px;
font-size: 12px;
color: #f56c6c;
}
.empty-violations {
padding: 60px 20px;
text-align: center;
color: #909399;
font-size: 14px;
}
/* 文档说明 */
.doc-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 16px;
}
.doc-card {
padding: 16px;
background: #f5f7fa;
border-radius: 8px;
border-left: 4px solid #409eff;
}
.doc-card h4 {
margin: 0 0 8px 0;
font-size: 14px;
color: #409eff;
font-family: 'Courier New', monospace;
}
.doc-card p {
margin: 0;
font-size: 13px;
color: #606266;
line-height: 1.6;
}
/* 最佳实践 */
.practices-list {
margin: 0;
padding-left: 24px;
}
.practices-list li {
font-size: 14px;
line-height: 2;
color: #606266;
}
</style>
三、SRI (子资源完整性)
3.1 SRI管理器
sri-manager.js
export class SRIManager {
constructor() {
this.algorithms = ['sha256', 'sha384', 'sha512']
}
// 生成SRI Hash (需要Web Crypto API)
async generateHash(content, algorithm = 'sha384') {
const encoder = new TextEncoder()
const data = encoder.encode(content)
const hashBuffer = await crypto.subtle.digest(algorithm.toUpperCase(), data)
const hashArray = Array.from(new Uint8Array(hashBuffer))
const hashBase64 = btoa(String.fromCharCode(...hashArray))
return `${algorithm}-${hashBase64}`
}
// 生成多个算法的Hash
async generateMultipleHashes(content, algorithms = ['sha384', 'sha512']) {
const hashes = await Promise.all(
algorithms.map(algo => this.generateHash(content, algo))
)
return hashes.join(' ')
}
// 为script标签添加integrity
addScriptIntegrity(src, integrity) {
const script = document.createElement('script')
script.src = src
script.integrity = integrity
script.crossOrigin = 'anonymous'
return script
}
// 为link标签添加integrity
addLinkIntegrity(href, integrity) {
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = href
link.integrity = integrity
link.crossOrigin = 'anonymous'
return link
}
// 验证资源完整性(客户端模拟)
async verifyIntegrity(content, expectedHash) {
const [algorithm] = expectedHash.split('-')
const actualHash = await this.generateHash(content, algorithm)
return actualHash === expectedHash
}
// 从URL获取资源并生成Hash
async generateHashFromUrl(url) {
try {
const response = await fetch(url)
const content = await response.text()
return await this.generateHash(content)
} catch (error) {
console.error('Failed to generate hash from URL:', error)
return null
}
}
// 批量生成CDN资源的SRI
async generateCDNHashes(urls) {
const results = []
for (const url of urls) {
const hash = await this.generateHashFromUrl(url)
results.push({
url,
integrity: hash,
crossorigin: 'anonymous'
})
}
return results
}
}
export default new SRIManager()
3.2 SRI演示组件
SRIDemo.vue
<script setup>
import { ref } from 'vue'
import sriManager from './sri-manager.js'
const resourceUrl = ref('https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js')
const resourceType = ref('script')
const generatedHash = ref('')
const isGenerating = ref(false)
const cdnResources = ref([
'https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js',
'https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js',
'https://cdn.jsdelivr.net/npm/element-plus/dist/index.css'
])
const batchResults = ref([])
// 生成单个资源的Hash
const generateHash = async () => {
if (!resourceUrl.value) return
isGenerating.value = true
try {
generatedHash.value = await sriManager.generateHashFromUrl(resourceUrl.value)
} catch (error) {
console.error('Generate hash failed:', error)
alert('生成Hash失败,请检查URL')
} finally {
isGenerating.value = false
}
}
// 批量生成
const batchGenerate = async () => {
isGenerating.value = true
try {
batchResults.value = await sriManager.generateCDNHashes(cdnResources.value)
} catch (error) {
console.error('Batch generate failed:', error)
} finally {
isGenerating.value = false
}
}
// 生成HTML代码
const generateHTMLCode = () => {
if (!generatedHash.value) return ''
if (resourceType.value === 'script') {
return `<script src="${resourceUrl.value}"
integrity="${generatedHash.value}"
crossorigin="anonymous"></script>`
} else {
return `<link rel="stylesheet"
href="${resourceUrl.value}"
integrity="${generatedHash.value}"
crossorigin="anonymous">`
}
}
</script>
<template>
<div class="sri-demo">
<h2>SRI (子资源完整性) 配置</h2>
<div class="generator-section">
<h3>生成SRI Hash</h3>
<div class="input-group">
<input
v-model="resourceUrl"
type="text"
placeholder="输入CDN资源URL"
class="url-input"
/>
<select v-model="resourceType" class="type-select">
<option value="script">Script</option>
<option value="stylesheet">Stylesheet</option>
</select>
<button @click="generateHash" :disabled="isGenerating" class="generate-btn">
{{ isGenerating ? '生成中...' : '生成Hash' }}
</button>
</div>
<div v-if="generatedHash" class="result-section">
<div class="result-label">生成的Integrity值:</div>
<code class="hash-code">{{ generatedHash }}</code>
<div class="result-label">HTML代码:</div>
<pre class="html-code">{{ generateHTMLCode() }}</pre>
</div>
</div>
<div class="batch-section">
<h3>批量生成</h3>
<div class="cdn-list">
<div v-for="(url, index) in cdnResources" :key="index" class="cdn-item">
<input v-model="cdnResources[index]" type="text" class="cdn-input" />
</div>
</div>
<button @click="batchGenerate" :disabled="isGenerating" class="batch-btn">
批量生成
</button>
<div v-if="batchResults.length" class="batch-results">
<div v-for="result in batchResults" :key="result.url" class="batch-result-item">
<div class="result-url">{{ result.url }}</div>
<div class="result-integrity">
<code>{{ result.integrity }}</code>
</div>
</div>
</div>
</div>
<div class="info-section">
<h3>SRI说明</h3>
<ul>
<li>SRI可以验证从CDN加载的资源是否被篡改</li>
<li>浏览器会计算资源的Hash并与integrity属性比对</li>
<li>如果不匹配,资源会被拒绝加载</li>
<li>必须同时设置crossorigin="anonymous"</li>
<li>推荐使用sha384或sha512算法</li>
</ul>
</div>
</div>
</template>
<style scoped>
.sri-demo {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.generator-section,
.batch-section,
.info-section {
margin-bottom: 30px;
padding: 24px;
background: white;
border-radius: 8px;
}
.input-group {
display: flex;
gap: 12px;
margin-bottom: 20px;
}
.url-input {
flex: 1;
padding: 10px;
border: 1px solid #dcdfe6;
border-radius: 4px;
}
.type-select {
padding: 10px;
border: 1px solid #dcdfe6;
border-radius: 4px;
}
.generate-btn,
.batch-btn {
padding: 10px 24px;
background: #409eff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.result-section {
margin-top: 20px;
}
.result-label {
font-weight: 600;
margin-bottom: 8px;
}
.hash-code,
.html-code {
display: block;
padding: 12px;
background: #f5f7fa;
border-radius: 4px;
margin-bottom: 16px;
overflow-x: auto;
}
</style>
[由于篇幅限制,继续其他部分...]
四、HTTPS部署
4.1 HTTPS配置检查器
https-checker.js
export class HTTPSChecker {
constructor() {
this.checks = []
}
// 检查是否使用HTTPS
checkHTTPS() {
const isHTTPS = window.location.protocol === 'https:'
this.checks.push({
name: 'HTTPS协议',
passed: isHTTPS,
message: isHTTPS ? '网站使用HTTPS协议' : '网站未使用HTTPS协议',
severity: 'high'
})
return isHTTPS
}
// 检查混合内容
checkMixedContent() {
const images = document.querySelectorAll('img[src^="http:"]')
const scripts = document.querySelectorAll('script[src^="http:"]')
const styles = document.querySelectorAll('link[href^="http:"]')
const hasMixedContent = images.length > 0 || scripts.length > 0 || styles.length > 0
this.checks.push({
name: '混合内容检查',
passed: !hasMixedContent,
message: hasMixedContent
? `检测到${images.length}个HTTP图片,${scripts.length}个HTTP脚本,${styles.length}个HTTP样式`
: '未检测到混合内容',
severity: 'medium'
})
return !hasMixedContent
}
// 检查HSTS
async checkHSTS() {
// 这个需要检查HTTP响应头,前端无法直接检查
// 这里仅作演示
this.checks.push({
name: 'HSTS策略',
passed: null,
message: 'HSTS需要在服务器响应头中设置,前端无法直接检查',
severity: 'medium'
})
}
// 获取检查结果
getResults() {
return this.checks
}
// 生成HTTPS配置建议
generateHTTPSConfig() {
return {
nginx: `server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
# 强制HTTPS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# 重定向HTTP到HTTPS
if ($scheme != "https") {
return 301 https://$server_name$request_uri;
}
}`,
apache: `<VirtualHost *:443>
ServerName example.com
SSLEngine on
SSLCertificateFile /path/to/cert.pem
SSLCertificateKeyFile /path/to/key.pem
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</VirtualHost>
<VirtualHost *:80>
ServerName example.com
Redirect permanent / https://example.com/
</VirtualHost>`
}
}
}
export default new HTTPSChecker()
五、Cookie安全
5.1 Cookie安全管理器
cookie-security.js
export class CookieSecurity {
// 设置安全Cookie
setSecureCookie(name, value, options = {}) {
const defaults = {
secure: true, // 只通过HTTPS传输
httpOnly: false, // 前端不能设置HttpOnly,需要服务端设置
sameSite: 'Strict', // 防止CSRF
maxAge: 86400, // 1天
path: '/',
domain: window.location.hostname
}
const config = { ...defaults, ...options }
let cookieString = `${name}=${encodeURIComponent(value)}`
if (config.maxAge) {
cookieString += `; Max-Age=${config.maxAge}`
}
if (config.expires) {
cookieString += `; Expires=${config.expires.toUTCString()}`
}
if (config.path) {
cookieString += `; Path=${config.path}`
}
if (config.domain) {
cookieString += `; Domain=${config.domain}`
}
if (config.secure) {
cookieString += '; Secure'
}
if (config.sameSite) {
cookieString += `; SameSite=${config.sameSite}`
}
document.cookie = cookieString
}
// 获取Cookie
getCookie(name) {
const matches = document.cookie.match(
new RegExp('(?:^|; )' + name.replace(/([.$?*|{}()[]\\\/\+^])/g, '\\$1') + '=([^;]*)')
)
return matches ? decodeURIComponent(matches[1]) : null
}
// 删除Cookie
deleteCookie(name, options = {}) {
this.setSecureCookie(name, '', {
...options,
maxAge: -1
})
}
// 检查Cookie安全性
checkCookieSecurity(cookieString) {
const checks = {
hasSecure: /Secure/i.test(cookieString),
hasHttpOnly: /HttpOnly/i.test(cookieString),
hasSameSite: /SameSite=/i.test(cookieString),
sameSiteValue: null
}
const sameSiteMatch = cookieString.match(/SameSite=(Strict|Lax|None)/i)
if (sameSiteMatch) {
checks.sameSiteValue = sameSiteMatch[1]
}
return checks
}
// 列出所有Cookie
listCookies() {
const cookies = document.cookie.split(';')
return cookies.map(cookie => {
const [name, value] = cookie.trim().split('=')
return {
name: name,
value: decodeURIComponent(value || '')
}
})
}
}
export default new CookieSecurity()
六、简历描述模板
内容安全策略实施 (2024.03 - 2024.08)
负责公司Web应用的内容安全策略部署,实现CSP配置、SRI完整性验证、HTTPS全站迁移和Cookie安全加固。
核心职责
- 配置Content Security Policy,限制资源加载来源,防御XSS攻击
- 实施SRI子资源完整性验证,保护CDN资源不被篡改
- 推动HTTPS全站部署,配置HSTS强制安全连接
- 加固Cookie安全,设置Secure、HttpOnly、SameSite属性
- 建立CSP违规监控系统,实时收集和分析安全事件
技术实现
- 使用严格的CSP策略,禁用unsafe-inline和unsafe-eval
- 为所有CDN资源生成SRI Hash值
- 配置Nginx反向代理,强制HTTP重定向到HTTPS
- 使用Let's Encrypt自动化证书管理
- 实现CSP report-uri接口收集违规报告
项目成果
- CSP部署后XSS攻击拦截率提升到100%
- 通过SRI验证,成功阻止3次CDN资源篡改
- HTTPS迁移完成,全站SSL Labs评级达到A+
- Cookie安全加固后,未发生会话劫持事件
七、SOP标准回答
面试问题: 什么是CSP?如何配置?
标准回答
"CSP是Content Security Policy的缩写,是一种浏览器安全机制,用于防御XSS等注入攻击。
CSP的核心思想是白名单。你明确告诉浏览器哪些资源是可信的,浏览器只加载这些来源的资源,其他的都拒绝。比如你设置script-src 'self',浏览器就只加载同源的JavaScript,外部域名的脚本都会被阻止。
配置CSP有两种方式。一种是HTTP响应头,服务器返回Content-Security-Policy头部。另一种是HTML的meta标签。我一般用响应头方式,因为meta标签无法设置report-uri等某些指令。
CSP有很多指令。default-src是默认策略,script-src控制JavaScript来源,style-src控制CSS来源,img-src控制图片来源,connect-src控制AJAX和WebSocket连接。每个指令可以设置多个源,比如'self'表示同源,'unsafe-inline'允许内联代码,但这不安全,应该避免。
我在项目中用的是严格模式。禁止所有内联脚本和eval,所有资源必须从白名单域名加载。对于确实需要的内联脚本,我用nonce机制。服务器生成随机nonce值,既加到CSP策略里script-src 'nonce-abc123',也加到script标签