Spring Security中的加解密和签名验签

1. 密码加密

  • 密码加密:在 Spring Security 中,密码加密是单向的,即加密后的密码无法解密回原始密码。这是为了确保即使数据库被泄露,攻击者也无法轻易获取用户的明文密码。
  • 存储加密密码:Spring Security 使用 PasswordEncoder 对密码进行加密,并将加密后的密码存储在数据库中。(内置了很多种加密方法,可按需使用)
为什么密码加密一般是单向的?

SpringSecurity中的密码只能加密不能解密(不可逆),由于大部分加密算法是加密之后的串随机盐生成最终的密钥,相同的密码加密之后也会变成不一样的密钥,除了用户自己没人知道密码,这样保证了密码的安全性。

为什么相同的密码,注册时生成的是不同的密钥,后台又是怎么知道用户输入的密码是正确的?

因为用户登录时,后台根据用户账号去数据库查找当前用户存在数据库里的密钥串,并解析这个串,获取其中的盐值,并和用户当前输入的密码加密之后的串重新加密。(密码加密后的串+盐值--->最终要和数据库里的密钥值比对的值)

2. 信息(用户、密码、验证码等)传输加解密

(前后端信息传输需要用到)

  • 传输加解密:为了确保密码在传输过程中的安全性,前端可以使用公钥加密密码,后端使用私钥解密(此方式为RSA非对称加密,也可使用对称加密)。这是一种常见的做法,可以防止中间人攻击。
    • 加密:想象你有一封重要的信件,你不想让别人看到信的内容。你可以把信放在一个保险箱里,只有你有钥匙(密钥)可以打开保险箱。这个过程就是加密。
    • 解密:当你收到这封放在保险箱里的信件时,你可以用钥匙(密钥)打开保险箱,取出信件并读取内容。这个过程就是解密。
  • 公钥和私钥:公钥可以公开,私钥必须保密。公钥通常存储在前端的公共 JavaScript 文件中(也可将公钥存储在服务器上,并通过 API 动态获取。这样可以避免公钥被篡改或泄露。),而私钥存储在后端的配置文件中(如 .properties 或 .yml 文件),也可将私钥存储在环境变量或安全的密钥管理服务中,这样更安全。

3.签名和验签

(防止信息被篡改,一般用户登录之后用token值去调后台接口时需要验证是否被篡改,没有被篡改即可调用后台接口,如果被人为篡改过就不支持后续的操作)

  • 签名:想象你写了一封信,并在信的末尾签上你的名字。这个签名可以证明这封信是你写的,并且信的内容没有被篡改。这个过程就是签名。
  • 验签:当别人收到这封信时,可以通过比对签名和信的内容,确认这封信确实是你写的,并且信的内容没有被篡改,别人才会按照信的指示给你一定的响应。这个过程就是验签。

为什么这样设计?

  • 密码加密

实现PasswordEncoder类的内置类/自定义加密方法:这样设计是为了保护密码的机密性,只有用户自己才知道登录密码。

  • 信息传输加解密

公钥加密,私钥解密:这样设计是为了保护数据的机密性,只有拥有私钥的人才能解密数据。

  • 签名和验签

私钥签名,公钥验签:这样设计是为了确保数据的完整性和来源的可靠性。只有拥有私钥的人才能生成签名,其他人可以用公钥验证签名是否有效,确保数据确实来自签名者,并且没有被篡改。

RSA加密案例

前端加密

import JSEncrypt from 'jsencrypt/bin/jsencrypt.min' 
// 公钥,密钥对生成地址 http://web.chacuo.net/netrsakeypair 
const publicKey = '你的RSA加密公钥' 
// 账号密码传输加密 
export function encrypt(txt) { 
    const encryptor = new JSEncrypt() 
    encryptor.setPublicKey(publicKey) // 设置公钥 
    return encryptor.encrypt(txt) // 对数据进行加密 
}

后端解密

/** 
 * RSA加解密工具类 
 */ 
public class RSAUtils { 
    /** 
     * 私钥解密
     * SecretConstants.DATA_RSA_PRIVATE_KEY 私钥 
     */ 
    public static String privateDecrypt(String data) throws NoSuchAlgorithmException, InvalidKeySpecException { 
        // 通过PKCS#8编码的Key指令获得私钥对象 
        KeyFactory keyFactory = KeyFactory.getInstance("RSA"); 
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(SecretConstants.DATA_RSA_PRIVATE_KEY))); 
        RSAPrivateKey key = (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec); 
        try { 
            Cipher cipher = Cipher.getInstance("RSA"); 
            cipher.init(Cipher.DECRYPT_MODE, key); 
            return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), key.getModulus().bitLength()), "UTF-8"); 
        } catch (Exception e) { 
            throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e); 
        } 
    } 
} 
// 调用解密传输信息方法 
RSAUtils.privateDecrypt(String.valueOf(credentials));

对于安全系数要求高的登录系统

通常会采用以下措施:

注意:jwt值可以看作一个token,下面会有两对公钥私钥,分别为public_key,private_keypublic_sign_key,private_sign_key

  1. 前端的账号密码验证码传输时,使用后端提供的公钥加密(public_key)也可以使用对称加密,即前后台使用一个密钥),并生成一个uuid(uuid需存在前端缓存sessionStorage里)方便后续使用
  2. 后端接收uuid后,将uuid作为key的一部分,jwt的值私钥签名(private_sign_key)之后作为value值,后续后端存入redis中。
  3. 先进行私钥解密(private_key)就能获取前端的账号密码验证码信息,后端做登录密码验证时,需要将私钥解密的数据(即前端传过来的密码)再次加密(密码加密,一般可自行MD5+盐加密或者使用PasswordEncoder内置的实现方法),再和数据库里的密码(存的加密后的数据)比对,一致则登录成功,否则登录失败。
  4. 前端调用后台接口前都需要验签(白名单接口除外),此时需要将sessionStorage中的uuid和参数的其他的参数一并传到后台接口,后台接口获取请求参数中的uuid,并去查找存在redis中的jwt值签名值,并用公钥解签(public_sign_key)这个值(验证jwt是否是系统颁发的)
  5. 验签通过之后即可调用接口的信息

加密算法

  • 加密算法:常见的非对称加密算法有 RSA、ECC 等。确保选择的算法足够安全,并且符合你的应用需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值