OAuth2.0公钥私钥授权技术

传统的资源服务授权流程如下:

OAuth2.0公钥私钥授权技术_第1张图片

  1. 客户端先去授权服务器申请令牌,申请令牌后,携带令牌访问资源服务器;
  2. 资源服务器访问授权服务器校验令牌的合法性;
  3. 如果校验成功,授权服务器返回用户信息给资源服务器;
  4. 资源服务器接收到校验成功的结果后,返回客户端请求的资源。

传统授权方法的问题是用户每次请求资源服务,资源服务都需要携带令牌访问认证服务去校验令牌的合法性,并根据令牌获取用户的相关信息,性能低下。

为了避免每次资源服务器反复校验令牌的合法性,我们可以利用公钥私钥完成对令牌的加密和解密。公钥私钥授权流程如下:

OAuth2.0公钥私钥授权技术_第2张图片

  1. 客户端请求认证服务器申请令牌;
  2. 认证服务器采用非对称加密算法,使用私钥生成令牌;
  3. 客户端携带令牌访问资源服务客户端,即在Http header中添加Authorization:Bearer令牌;
  4. 资源服务器使用公钥校验令牌是否有效;
  5. 令牌有效,资源服务器向客户端响应资源信息。

实际上,认证服务器生成一对公钥和私钥,私钥留给自己,公钥发给其他资源服务器。在发放令牌时,先利用MD5之类的摘要算法生成令牌摘要,并通过私钥对改摘要进行签名(加密)。当客户端携带令牌访问资源时,取下签名,即利用公钥进行解密得到令牌的摘要。再通过采用MD5算法生成令牌摘要,最后,判断两个摘要是否相同,若相同,则令牌有效,否则,令牌无效。

1. 生成公钥私钥

生成密钥证书

使用JDK自带工具keytool生成ssl证书:

keytool -genkeypair -alias changgou -keypass changgou -keyalg RSA -keypass changgou -storepass changgou -keystore C:/Users/Administrator/Desktop/changgou.jks

导出公钥

这里参考大佬的实现

import sun.misc.BASE64Encoder;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.security.*;
import java.security.cert.Certificate;

/**
 * 从证书中导出公钥私钥
 */
public class ExportCert {
    //导出证书 base64格式
    public static void exportCert(KeyStore keystore, String alias, String exportFile) throws Exception {
        Certificate cert = keystore.getCertificate(alias);
        BASE64Encoder encoder = new BASE64Encoder();
        String encoded = encoder.encode(cert.getEncoded());
        FileWriter fw = new FileWriter(exportFile);
        fw.write("-----BEGIN CERTIFICATE-----");
        fw.write(encoded);
        fw.write("-----END CERTIFICATE-----");
        fw.close();
    }

    //得到KeyPair
    public static KeyPair getKeyPair(KeyStore keystore, String alias, char[] password) {
        try {
            Key key = keystore.getKey(alias, password);
            if (key instanceof PrivateKey) {
                Certificate cert = keystore.getCertificate(alias);
                PublicKey publicKey = cert.getPublicKey();
                return new KeyPair(publicKey, (PrivateKey) key);
            }
        } catch (UnrecoverableKeyException e) {
        } catch (NoSuchAlgorithmException e) {
        } catch (KeyStoreException e) {
        }
        return null;
    }

    //导出私钥
    public static void exportPrivateKey(PrivateKey privateKey,String exportFile) throws Exception {
        BASE64Encoder encoder = new BASE64Encoder();
        String encoded = encoder.encode(privateKey.getEncoded());
        FileWriter fw = new FileWriter(exportFile);
        fw.write("-----BEGIN PRIVATE KEY-----");
        fw.write(encoded);
        fw.write("-----END PRIVATE KEY-----");
        fw.close();
    }

    //导出公钥
    public static void exportPublicKey(PublicKey publicKey,String exportFile) throws Exception {
        BASE64Encoder encoder = new BASE64Encoder();
        String encoded = encoder.encode(publicKey.getEncoded());
        FileWriter fw = new FileWriter(exportFile);
        fw.write("-----BEGIN PUBLIC KEY-----"); 
        fw.write(encoded);
        fw.write("-----END PUBLIC KEY-----");
        fw.close();
    }

    public static void main(String args[]) throws Exception {

        String keyStoreType = "JKS";
        String keystoreFile = "C:/Users/Administrator/Desktop/changgou.jks";
        String password = "changgou";
        String alias = "changgou";

        KeyStore keystore = KeyStore.getInstance(keyStoreType);
        keystore.load(new FileInputStream(new File(keystoreFile)), password.toCharArray());

        String exportCertFile = "C:/Users/Administrator/Desktop/cms.cer";
        String exportPrivateFile = "C:/Users/Administrator/Desktop/cmsPrivateKey.txt";
        String exportPublicFile = "C:/Users/Administrator/Desktop/cmsPublicKey.txt";

        ExportCert.exportCert(keystore, alias, exportCertFile);
        KeyPair keyPair = ExportCert.getKeyPair(keystore, alias, password.toCharArray());
        ExportCert.exportPrivateKey(keyPair.getPrivate(), exportPrivateFile);
        ExportCert.exportPublicKey(keyPair.getPublic(), exportPublicFile);

        System.out.println("OK");
    }
}

2. 生成令牌

JWT签名算法中,常采用的两种加密算法:

  • HS256;
  • RS256。

签名实际上是一个加密的过程,生成一段标识(也是JWT的一部分)作为接收方验证信息是否被篡改的依据。

RS256 (采用SHA-256 的 RSA 签名) 是一种非对称算法, 它使用公共/私钥对: 标识提供方采用私钥生成签名, JWT 的使用方获取公钥以验证签名。由于公钥 (与私钥相比) 不需要保护, 因此大多数标识提供方使其易于使用方获取和使用 (通常通过一个元数据URL)。

另一方面, HS256 (带有 SHA-256 的 HMAC 是一种对称算法, 双方之间仅共享一个 密钥。由于使用相同的密钥生成签名和验证签名, 因此必须注意确保密钥不被泄密。

在开发应用的时候启用JWT,使用RS256更加安全,你可以控制谁能使用什么类型的密钥。生成令牌的实现:

    /**
     * 利用私钥创建jwt令牌
     */
    @Test
    public void testGenerateRSAJwt() {
        // 加载证书
        ClassPathResource resource = new ClassPathResource("changgou.jks");

        // 读取证书
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resource, "changgou".toCharArray());

        // 获取私钥
        KeyPair keyPair = keyStoreKeyFactory.getKeyPair("changgou", "changgou".toCharArray());
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();

        // 创建令牌,使用RSA算法进行加盐加密
        Map<String, Object> payload = new HashMap<>();
        payload.put("name", "jack");
        payload.put("age", 24);
        Jwt jwt = JwtHelper.encode(JSON.toJSONString(payload), new RsaSigner(privateKey));

        System.out.println(jwt.getEncoded());
    }

3. 解析令牌

利用公钥解析Jwt令牌。

    /**
     * 利用公钥解析jwt令牌
     */
    @Test
    public void testCheckRSAJwt(){
        String jwtStr = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiamFjayIsImFnZSI6MjR9.UTt8SEfzL3T7Xr_zj2ipWdt_ROiw2QOahJVAc5qSEb1keNAGcnKOu2Z-_Oe1kD1fZFIY3Y17QVGIjqONFa54BkXmeb2d6Hfq6pW2lD3ht_1iyVdZ2FPkDHVzyYMvUMgH1V85hJagUsGK0NyWBVIaODUj83Kpdky2DsXqUmkCrNQu03g-kKV3LLQ514EPpkcjAo2CrxbFHhQjOpPjQAd2C_FdJllVnQBgXD8z-h1VBOkhe1N3QH8AZx9PnKZMLEtKsN6U5cSbbj0vf5DKaYiyf5P_5DJ_OdkRcd5XvTZjGmFZA8FL5IYOtax4-ILPbdaMsYh2CYDct2n02crxpThkjw";
        String publicKey = "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvFsEiaLvij9C1Mz+oyAmt47whAaRkRu/8kePM+X8760UGU0RMwGti6Z9y3LQ0RvK6I0brXmbGB/RsN38PVnhcP8ZfxGUH26kX0RK+tlrxcrG+HkPYOH4XPAL8Q1lu1n9x3tLcIPxq8ZZtuIyKYEmoLKyMsvTviG5flTpDprT25unWgE4md1kthRWXOnfWHATVY7Y/r4obiOL1mS5bEa/iNKotQNnvIAKtjBM4RlIDWMa6dmz+lHtLtqDD2LF1qwoiSIHI75LQZ/CNYaHCfZSxtOydpNKq8eb1/PGiLNolD4La2zf0/1dlcr5mkesV570NxRmU1tFm8Zd3MZlZmyv9QIDAQAB-----END PUBLIC KEY-----";

        Jwt jwt = JwtHelper.decodeAndVerify(jwtStr, new RsaVerifier(publicKey));
        System.out.println(jwt.getClaims());
    }

你可能感兴趣的:(Spring,Cloud)