设计模式与安全:认证中的策略、授权中的代理、加密中的模板方法

说实话,很多做业务系统的朋友觉得设计模式跟安全八竿子打不着。但我在做支付系统的时候,被安全架构折腾得够呛,才真正体会到——设计模式在安全领域,简直就是救星。

今天咱们就聊三个最典型的场景:认证、授权、加密。这三个场景分别对应三种设计模式:策略模式、代理模式、模板方法模式。我一个个拆开讲。

认证中的策略模式

先说说认证。你想想看,一个系统里可能有多种认证方式:用户名密码、短信验证码、微信扫码、指纹识别……如果把这些逻辑全写在一个类里,那代码会变成什么样?

我曾经接手过一个老项目,认证逻辑全堆在一个叫 AuthService 的类里,光 if-else 就有十几层。每次加一种新的认证方式,就得改这个类,测试回归一遍,累得够呛。

策略模式就是干这个的。它把每种认证方式封装成一个独立的策略类,客户端只需要选择策略,不用关心具体实现。

核心思想: 定义一组算法,把它们封装起来,并且可以互相替换。

来看代码。先定义一个认证策略接口:

// Java 版本
public interface AuthenticationStrategy {
    boolean authenticate(String credentials);
}

// 用户名密码策略
public class UsernamePasswordStrategy implements AuthenticationStrategy {
    @Override
    public boolean authenticate(String credentials) {
        // 解析用户名和密码
        // 校验逻辑
        System.out.println("使用用户名密码认证");
        return true;
    }
}

// 短信验证码策略
public class SmsCodeStrategy implements AuthenticationStrategy {
    @Override
    public boolean authenticate(String credentials) {
        // 校验短信验证码
        System.out.println("使用短信验证码认证");
        return true;
    }
}

// 上下文类
public class AuthenticationContext {
    private AuthenticationStrategy strategy;
    
    public void setStrategy(AuthenticationStrategy strategy) {
        this.strategy = strategy;
    }
    
    public boolean executeAuthentication(String credentials) {
        return strategy.authenticate(credentials);
    }
}

C++ 版本也差不多,只是多了指针管理:

// C++ 版本
class AuthenticationStrategy {
public:
    virtual ~AuthenticationStrategy() = default;
    virtual bool authenticate(const std::string& credentials) = 0;
};

class UsernamePasswordStrategy : public AuthenticationStrategy {
public:
    bool authenticate(const std::string& credentials) override {
        std::cout << "使用用户名密码认证" << std::endl;
        return true;
    }
};

class AuthenticationContext {
private:
    std::unique_ptr<AuthenticationStrategy> strategy_;
public:
    void setStrategy(std::unique_ptr<AuthenticationStrategy> strategy) {
        strategy_ = std::move(strategy);
    }
    bool executeAuthentication(const std::string& credentials) {
        return strategy_->authenticate(credentials);
    }
};
我的经验: 策略模式在认证场景里最大的好处是——新增一种认证方式,你只需要加一个新类,不用改任何现有代码。这完全符合开闭原则。

授权中的代理模式

授权跟认证不一样。认证是「你是谁」,授权是「你能干什么」。在授权场景里,代理模式特别好用。

我记得有一次做权限系统,业务方要求:某些操作只有管理员能做,普通用户不能做。如果直接在业务代码里写权限判断,那每个方法都得加一段 if(checkPermission()),代码会变得很脏。

代理模式的做法是:创建一个代理对象,在代理对象里做权限检查,通过后才调用真实对象的方法。

// Java 版本
// 真实主题接口
public interface DocumentService {
    void viewDocument(String docId);
    void editDocument(String docId);
}

// 真实主题
public class DocumentServiceImpl implements DocumentService {
    @Override
    public void viewDocument(String docId) {
        System.out.println("查看文档: " + docId);
    }
    
    @Override
    public void editDocument(String docId) {
        System.out.println("编辑文档: " + docId);
    }
}

// 代理类
public class DocumentServiceProxy implements DocumentService {
    private DocumentService realService;
    private String userRole;
    
    public DocumentServiceProxy(String userRole) {
        this.realService = new DocumentServiceImpl();
        this.userRole = userRole;
    }
    
    @Override
    public void viewDocument(String docId) {
        // 查看权限:所有用户都可以
        realService.viewDocument(docId);
    }
    
    @Override
    public void editDocument(String docId) {
        // 编辑权限:只有管理员可以
        if (!"ADMIN".equals(userRole)) {
            throw new SecurityException("无编辑权限");
        }
        realService.editDocument(docId);
    }
}

C++ 版本:

// C++ 版本
class DocumentService {
public:
    virtual ~DocumentService() = default;
    virtual void viewDocument(const std::string& docId) = 0;
    virtual void editDocument(const std::string& docId) = 0;
};

class DocumentServiceProxy : public DocumentService {
private:
    std::unique_ptr<DocumentService> realService_;
    std::string userRole_;
public:
    DocumentServiceProxy(const std::string& role) 
        : realService_(std::make_unique<DocumentServiceImpl>()), userRole_(role) {}
    
    void viewDocument(const std::string& docId) override {
        realService_->viewDocument(docId);
    }
    
    void editDocument(const std::string& docId) override {
        if (userRole_ != "ADMIN") {
            throw std::runtime_error("无编辑权限");
        }
        realService_->editDocument(docId);
    }
};
避坑指南: 我曾经犯过一个错误——在代理类里直接写死了权限规则。后来权限规则变了,得改代理类。正确的做法是:代理类只做权限检查的「开关」,具体的权限规则应该从外部配置或数据库读取。

加密中的模板方法模式

加密算法有很多种:AES、DES、RSA、SM4……但它们的流程其实差不多:初始化密钥、加密、解密。不同的只是具体算法实现。

模板方法模式正好适合这种场景。它把不变的流程写在父类里,把可变的算法实现留给子类去完成。

// Java 版本
public abstract class EncryptionTemplate {
    
    // 模板方法,定义加密流程
    public final byte[] encrypt(byte[] data, String key) {
        byte[] processedKey = processKey(key);
        byte[] encryptedData = doEncrypt(data, processedKey);
        return postProcess(encryptedData);
    }
    
    // 模板方法,定义解密流程
    public final byte[] decrypt(byte[] data, String key) {
        byte[] processedKey = processKey(key);
        byte[] decryptedData = doDecrypt(data, processedKey);
        return postProcess(decryptedData);
    }
    
    // 子类必须实现的步骤
    protected abstract byte[] processKey(String key);
    protected abstract byte[] doEncrypt(byte[] data, byte[] key);
    protected abstract byte[] doDecrypt(byte[] data, byte[] key);
    
    // 可选钩子方法
    protected byte[] postProcess(byte[] data) {
        return data; // 默认不做后处理
    }
}

// AES 加密实现
public class AESEncryption extends EncryptionTemplate {
    @Override
    protected byte[] processKey(String key) {
        // AES 密钥处理逻辑
        System.out.println("处理 AES 密钥");
        return key.getBytes();
    }
    
    @Override
    protected byte[] doEncrypt(byte[] data, byte[] key) {
        System.out.println("执行 AES 加密");
        return data; // 实际加密逻辑
    }
    
    @Override
    protected byte[] doDecrypt(byte[] data, byte[] key) {
        System.out.println("执行 AES 解密");
        return data; // 实际解密逻辑
    }
}

C++ 版本:

// C++ 版本
class EncryptionTemplate {
public:
    virtual ~EncryptionTemplate() = default;
    
    // 模板方法
    std::vector<uint8_t> encrypt(const std::vector<uint8_t>& data, const std::string& key) {
        auto processedKey = processKey(key);
        auto encryptedData = doEncrypt(data, processedKey);
        return postProcess(encryptedData);
    }
    
    std::vector<uint8_t> decrypt(const std::vector<uint8_t>& data, const std::string& key) {
        auto processedKey = processKey(key);
        auto decryptedData = doDecrypt(data, processedKey);
        return postProcess(decryptedData);
    }
    
protected:
    virtual std::vector<uint8_t> processKey(const std::string& key) = 0;
    virtual std::vector<uint8_t> doEncrypt(const std::vector<uint8_t>& data, 
                                           const std::vector<uint8_t>& key) = 0;
    virtual std::vector<uint8_t> doDecrypt(const std::vector<uint8_t>& data, 
                                           const std::vector<uint8_t>& key) = 0;
    virtual std::vector<uint8_t> postProcess(const std::vector<uint8_t>& data) {
        return data;
    }
};
关键点: 模板方法模式把「不变」和「可变」分开了。加密流程是固定的,算法实现是可变的。这样你加一种新加密算法,只需要继承父类,实现那几个抽象方法就行。

三种模式的对比

设计模式 安全场景 核心作用 我的建议
策略模式 认证 多种认证方式灵活切换 适合认证方式经常变化的系统
代理模式 授权 控制访问权限 适合需要细粒度权限控制的场景
模板方法模式 加密 固定加密流程,可变算法 适合需要支持多种加密算法的系统

知识体系结构图

设计模式与安全 认证 策略模式 授权 代理模式 加密 模板方法模式 策略模式 • 多种认证方式 • 可动态切换 • 符合开闭原则 场景: 用户名密码、短信、 微信扫码、指纹等 代理模式 • 权限控制 • 访问拦截 • 职责分离 场景: 管理员/普通用户 不同权限的操作 模板方法模式 • 固定流程 • 可变算法 • 代码复用 场景: AES、DES、RSA SM4等加密算法
个人心得: 这三个模式在安全领域用好了,代码会变得特别干净。我后来做安全框架的时候,基本就是这三个模式打底。你想想看,认证策略可插拔、授权代理可拦截、加密流程可扩展——这不就是安全系统的理想状态吗?

最后说一句:设计模式不是银弹,但用对地方,它就是最好的工具。安全领域尤其如此,因为安全需求变化快、规则复杂,设计模式正好能帮你应对这种变化。

公众号:蓝海资料掘金营,微信deep3321