后端系列 - 一、设计一个安全的HTTP接口
背景
前提:客户端无法被逆向,但是可以被网络代理。
目标:在上述前提下,设计一个安全的接口服务。
基础访问安全:
ip白名单
ip黑名单机制 / 配置黑名单
幂等
限流
防止重放攻击 时间戳验证
方案选型:
如果是普通接口,仅仅是为了验证,采用BA Auth或者代码硬编码校验一个key即可,采用方案一。
如果是权限接口,请求和响应中没有敏感数据,一般在云服务厂商场景,采用AK SK方案即可。
安全性要求很高,采用AES + RSA + AK + SK方案。
极端场景:
采用双向SSL/TLS认证
常见字段含义
方案一、BA Auth
BA Auth 通常指的是 Basic Authentication,即基本认证,这是一种简单的HTTP认证机制,用户通过用户名和密码进行认证。以下是使用Java实现基本认证的示例代码:
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class BasicAuthFilter extends javax.servlet.Filter {
private static final String REALM_NAME = "MyRealm";
@Override
public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws java.io.IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Basic ")) {
try {
String base64Credentials = authHeader.substring("Basic ".length());
String credentials = java.util.Base64.getDecoder().decode(base64Credentials).toString();
int p = credentials.indexOf(":");
if (p != -1) {
String username = credentials.substring(0, p).trim();
String password = credentials.substring(p + 1).trim();
// 这里可以添加你的用户名和密码验证逻辑
if ("yourUsername".equals(username) && "yourPassword".equals(password)) {
chain.doFilter(request, response);
return;
}
}
} catch (IllegalArgumentException e) {
// 解码失败
}
}
response.setHeader("WWW-Authenticate", "Basic realm=\"" + REALM_NAME + "\"");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
@Override
public void init(javax.servlet.FilterConfig filterConfig) {
// 初始化代码,如果有的话
}
@Override
public void destroy() {
// 清理代码,如果有的话
}
}
在这个示例中,BasicAuthFilter
类扩展了 javax.servlet.Filter
类,覆盖了 doFilter
方法来实现基本认证。当用户尝试访问受保护的资源时,如果请求中没有包含有效的用户名和密码,服务器将返回401状态码和相应的WWW-Authenticate头,提示客户端需要进行基本认证。
你需要将这个过滤器配置到你的Web应用程序中,通常在`web.xml`文件中进行配置,或者使用注解或编程式的方式来注册这个过滤器。
请根据你的实际需求调整用户名和密码验证逻辑以及realm名称。
方案二、AK + SK
AK/SK(Access Key ID/Secret Access Key)是一种常见的认证方式,通常用于API调用,特别是在云服务提供商的API中,例如AWS、腾讯云、阿里云等。AK/SK认证机制通常包括以下步骤:
1. 用户注册并获取一对密钥,即Access Key ID(AK)和Secret Access Key(SK)。
2. 用户在API请求中包含AK和签名(使用SK生成)。
3. 服务端接收到请求后,验证签名的正确性,如果签名正确,则处理请求。
以下是一个使用Java实现AK/SK认证的简单示例。这个示例假设你已经有一个服务端,它接收API请求并验证签名。这里我们只实现客户端的签名生成部分。
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.TreeMap;
public class AKSKAuth {
private static final String ACCESS_KEY_ID = "your-access-key-id";
private static final String SECRET_ACCESS_KEY = "your-secret-access-key";
public static void main(String[] args) throws Exception {
String httpMethod = "GET"; // HTTP请求方法
String host = "api.example.com"; // 服务端域名
String uri = "/path"; // 资源路径
String queryParams = "param1=value1¶m2=value2"; // 查询参数
String now = System.currentTimeMillis() / 1000; // 当前时间戳
// 构造待签名字符串
StringBuilder canonicalRequest = new StringBuilder();
canonicalRequest.append(httpMethod).append("\n");
canonicalRequest.append(uri).append("\n");
canonicalRequest.append(queryParams).append("\n");
canonicalRequest.append("host:").append(host).append("\n");
canonicalRequest.append("x-sdk-date:").append(now);
// 生成签名
String signature = sign(canonicalRequest.toString(), SECRET_ACCESS_KEY);
// 构造请求头
String authorizationHeader = "hmac id=\"" + ACCESS_KEY_ID + "\","
+ " algorithm=\"hmac-sha1\","
+ " headers=\"host x-sdk-date request-line-method request-uri\","
+ " signature=\"" + signature + "\"";
// 发送API请求(示例,实际中使用HTTP客户端发送请求)
System.out.println("Authorization: " + authorizationHeader);
}
// 使用HMAC-SHA1算法生成签名
public static String sign(String data, String key) throws NoSuchAlgorithmException {
// 使用给定的密钥创建签名
byte[] keyBytes = key.getBytes();
byte[] dataBytes = data.getBytes();
MessageDigest md = MessageDigest.getInstance("HmacSHA1");
md.update(keyBytes);
byte[] hash = md.digest(dataBytes);
// 将字节数组转换为Base64字符串
return Base64.getEncoder().encodeToString(hash);
}
}
这个示例代码展示了如何生成一个简单的签名。在实际应用中,签名的生成可能会更复杂,包括对请求的多个部分进行签名,并且可能需要遵循特定的签名规范。请根据你所使用的服务提供商的API文档来调整签名生成逻辑。
请注意,这个示例仅用于演示目的,实际应用中需要考虑安全性、错误处理和性能优化等因素。
方案三、 AES + RSA
在上述基础上每一个appId / AK / SK 具备自身的RSA/AES 秘钥,单独提供。
在请求时对敏感数据进行AES加密,然后通过RSA类似AK/SK的方式进行签名。
服务端收到数据后先根据入参查询RSA/AES秘钥,然后进行验签,验签完毕后进行解密,完成交互。
这里如果返回也涉及敏感数据,一般这个动作是对称的,即互相提供公钥,各自保存私钥。
/**
* AES加密方式生成密钥
*/
public static String geneKey() throws Exception {
//获取一个密钥生成器实例
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128);
SecretKey secretKey = keyGenerator.generateKey();
String key = Base64.getEncoder().encodeToString(secretKey.getEncoded());
return key;
}
/**
* 生成pk/sk
*/
public static Map<String, String> genKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
String privateKeyString = Base64.getEncoder().encodeToString(privateKey.getEncoded());
String publicKeyString = Base64.getEncoder().encodeToString(publicKey.getEncoded());
Map<String, String> map = new HashMap<>();
map.put("pk", publicKeyString);
map.put("sk", privateKeyString);
return map;
}
扩展场景:如何在一个没有鉴权的系统上快速增加鉴权
Spring项目可以使用Spring Security ①username password 固定形式,集成简单。
nginx 拦截 :WEB页面没有认证界面使用Nginx认证的两种方式