
1 概述
架构安全性应至少包含以下问题的具体解决方案:
认证(Authentication):如何正确分辨操作用户的真实身份?要确保是可信的用户,才能进行相应的操作。
授权(Authorization):系统如何控制一个用户该看到哪些数据?操作哪些功能?
凭证(Credential):系统如何保证它与用户之间的承诺是双方当时真实意图的体现,是准确、完整、不可抵赖的?需要根据凭证确定当时确实做了这样的承诺。
保密(Confidentiality):系统如何保证敏感数据无法被包括系统管理员在内的内外部人员所窃取、滥用?
传输(Transport Security):系统如何保证通过网络传输的信息无法被第三方窃听、篡改和冒充?
验证(Verification):系统如何确保提交到每项服务中的数据是合乎规则的,不会对系统稳定性、数据一致性、正确性产生风险?
这些问题都是与具体系统和业务无关的通用性问题,因此都存在业界通行的、已被验证过是行之有效的解决方案,甚至已经形成行业标准,不需要开发者自己从头去构思如何解决。
2 认证(Authentication)
认证并不简单地等同于“系统登录”功能,不是校验一下用户名、密码是否正确这么简单。
- 作用:账户和权限必须最大限度保障安全和隐私,同时又要兼顾各个系统模块甚至系统间共享访问。
- 问题:账户和权限的存储、管理与使用都面临一系列复杂的问题。
对于某些大规模的信息系统,账户和权限的管理往往要由专门的基础设施来负责,譬如微软的活动目录(ActiveDirectory,AD)或者轻量目录访问协议(Lightweight DirectoryAccess Protocol,LDAP),跨系统的共享使用甚至会用到区块链技术
- 误区:尽管“认证”是解决“你是谁”的问题,但这里的“你”并不一定是指人,也可能是指外部的代码,即第三方的类库或者服务。
早期Java当时的主要应用形式是Java Applets,类加载器从远端下载一段字节码,以Applets的形式在用户的浏览器中运行,由于Java操控计算机资源的能力要远远强于JavaScript,因此必须先确保这些代码不会损害用户的计算机。Java技术体系中的“安全管理器”(java.lang.SecurityManager)、“代码权限许可”(java.lang.RuntimePermission)等概念都是在那时产生的。到如今,代码认证的研究方向已经很固定,基本上都统一到证书签名上。
2.1 认证的标准
架构安全性的经验原则:以标准规范为指导、以标准接口去实现。安全涉及的问题很麻烦,但解决方案已相当成熟,对于99%的系统来说,在安全上不去做轮子,不去想发明创造,严格遵循标准,就是最恰当的安全设计。
在1999年,随J2EE 1.2[插图]发布的Servlet 2.2中添加了一系列用于认证的API,主要包括下列两部分内容:·标准方面,添加了四种内置的、不可扩展的认证方案,即Client-Cert、Basic、Digest和Form;·实现方面,添加了一套与认证和授权相关的程序接口,譬如HttpServletRequest::is-UserInRole()、HttpServletRequest::getUserPrincipal()等方法。
J2EE 1.2内置的Client-Cert、Basic、Digest和Form这四种认证方案都很有代表性,刚好分别覆盖了通信信道、协议和内容层面的认证。而这三种层面的认证恰好涵盖了主流的三种认证方式,具体含义和应用场景列举如下:
通信信道上的认证:你和我建立通信连接之前,要先证明你是谁。在网络传输(Network)场景中的典型应用是基于SSL/TLS传输安全层的认证。
通信协议上的认证:你请求获取我的资源之前,要先证明你是谁。在互联网(Internet)场景中的典型应用是基于HTTP协议的认证。
通信内容上的认证:你使用我提供的服务之前,要先证明你是谁。在万维网(World Wide Web)场景中的典型应用是基于Web内容的认证。
2.1.1 HTTP认证——通信协议上的认证
认证方案(AuthenticationScheme),它是指生成用户身份凭证的某种方法,这个概念最初源于HTTP协议的认证框架(Authentication Framework)。
IETF在RFC7235中定义了HTTP协议的通用认证框架,要求所有支持HTTP协议的服务器,在未授权的用户意图访问服务端保护区域的资源时,应返回401 Unauthorized的状态码,同时应在响应报文头里附带以下两个分别代表网页认证和代理认证的Header之一,告知客户端应该采取何种方式产生能代表访问者身份的凭证信息。
HTTP认证框架提出认证方案是希望能把认证“要产生身份凭证”的目的与“具体如何产生凭证”的实现分离开来,无论客户端通过生物信息(指纹、人脸)、用户密码、数字证书抑或其他方式来生成凭证,都属于如何生成凭证的具体实现,都可以包含在HTTP协议预设的框架之内。
我理解是虽然都要认证,但是返回的认证方案可以根据项目需要制定,从而提高灵活性。
- Basic认证:用作演示或者不要求安全性的场合。
- Digest:RFC 7616,HTTP摘要认证,可视为Basic认证的改良版本。针对Base64明文发送的风险,Digest认证把用户名和密码加盐(一个被称为Nonce的变化值作为盐值)后再通过MD5/SHA等哈希算法取摘要发送出去。但是这种认证方式依然是不安全的,无论客户端使用何种加密算法加密,无论是否采用了Nonce这样的动态盐值去抵御重放和冒认,遇到中间人攻击时依然存在显著的安全风险。关于加解密的问题,将在5.4节详细讨论。
- Bearer:RFC 6750,基于OAuth 2规范来完成认证。OAuth 2是一个同时涉及认证与授权的协议,具体将在5.2节详细介绍。
- **HOBA(HTTP Origin-Bound Authentication)**:RFC 7486,一种基于自签名证书的认证方案。基于数字证书的信任关系主要有两类模型:一类是采用CA(Certification Authority,认证机构)层次结构的模型,由CA中心签发证书;另一种是以IETF的Token Binding协议为基础的OBC(Origin Bound Certificate,原产地证书)自签名证书模型。5.5节将详细介绍数字证书。
HTTP认证框架中的认证方案是允许自行扩展的,并不要求一定由RFC规范来定义,只要用户代理(User Agent,通常是浏览器,泛指任何使用HTTP协议的程序)能够识别这种私有的认证方案即可。因此,很多厂商也扩展了自己的认证方案。
2.1.2 Web认证
IETF为HTTP认证框架设计了可插拔(Pluggable)的认证方案,原本是希望能涌现出各式各样的认证方案去支持不同的应用场景。尽管上节列举了一些还算常用的认证方案,但目前的信息系统,尤其是在系统对终端用户的认证场景中,直接采用HTTP认证框架的比例其实十分低。这不难理解,HTTP是“超文本传输协议”,传输协议的根本职责是把资源从服务端传输到客户端,至于资源具体是什么内容,只能由客户端自行解析驱动。以HTTP协议为基础的认证框架也只能面向传输协议而不是具体传输内容来设计,如果用户想要从服务器中下载文件,弹出一个HTTP服务器的对话框,让用户登录是可接受的;但如果用户想访问信息系统中的具体服务,肯定希望身份认证是由系统本身的功能去完成,而不是由HTTP服务器来负责认证。这种依靠内容而不是传输协议来实现的认证方式,在万维网里被称为“Web认证”,由于实现形式上登录表单占了绝对的主流,因此通常也被称为“表单认证”(Form Authentication)。
HTTP认证的局限性:
- 我的理解:HTTP认证是全局性的,每次访问资源都会弹出登录框,灵活性差。
补充说明:
HTTP认证(如Basic/Digest)是协议层认证,与具体业务无关。它的确会在浏览器弹出标准登录对话框(如浏览器自带的用户名/密码弹窗),但问题不仅在于“不灵活”,更在于:- 无法适配业务场景:比如支付服务需要多因素认证(指纹+密码),HTTP认证只能提供固定的用户名/密码验证。
- 用户体验割裂:弹出的HTTP登录框与系统UI风格不一致,破坏整体交互流程。
Web认证(表单认证)的优势:
- 我的理解:由系统判断服务类型,动态选择认证方式(如指纹或密码)。
补充说明:
Web认证(如表单登录)是应用层认证,核心优势在于:- 业务感知:系统能根据访问的具体功能(如支付、普通浏览)动态调整认证策略。
- UI可控:开发者可自定义登录页面(如嵌入验证码、指纹按钮等),与系统设计保持一致。
- 扩展性:支持OAuth、SSO、多因素认证等复杂场景,而HTTP认证仅支持简单凭据交换。
协议层 vs 应用层认证的对比
维度 | HTTP认证 | Web认证(表单) |
---|---|---|
层级 | 传输协议层(HTTP头) | 应用层(HTML表单+Session/Cookie) |
灵活性 | 低(固定认证方式) | 高(可自定义逻辑和UI) |
典型场景 | 静态文件下载、API基础认证 | 电商支付、银行系统、SaaS平台 |
用户交互 | 浏览器原生弹窗 | 系统自定义页面 |
技术实现 | WWW-Authenticate 响应头 |
后端Session + 前端表单提交 |
这么看来,现在项目中采用的认证方案基本都是Web认证。
“没有标准的约束”反倒成了表单认证的一大优点,它允许我们做出五花八门的页面,各种程序语言、框架或开发者本身都可以自行决定认证的全套交互细节。