一、OAuth2.0 介绍
1.1 OAuth 认证标准
OAuth(开放授权) 是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容,OAuth2.0 是 OAuth 协议的延续版本。
1.2 OAuth 和 OAuth2.0的区别
引用:
https://blog.csdn.net/u013436121/article/details/23631885
1.3 OAuth2.0 认证流程
OAuth2.0 的模块分为几部分:
- 客户端:本身不存储资源,需要通过用户的授权去请求资源服务器的资源。客户端如:商城安卓端、商城web端等
- 资源拥有者:通常为用户,即该资源信息的拥有者
- 认证服务器:用例对资源拥有者的身份进行认证,对访问资源授权。客户端想要访问资源,必须使用资源拥有者给予的授权码来认证服务器获取令牌
- 资源服务器:存储资源的服务器,如用户信息等。客户端最终使用令牌从资源服务器获取信息
例如:使用微信登录商城
1.4 OAuth2.0 授权模式
- 授权码模式(Authorization Code) => 常用
- 密码模式(Resource Owner Password Credentials) => 常用
- 隐式授权模式(Implicit) => 不常用
- 客户端模式(Client Credentials) => 不常用
1.5 授权码模式流程
- 客户端请求第三方授权
- 用户同意给客户端授权
- 客户端获取到授权码,请求认证服务器申请令牌
- 认证服务器向客户端响应令牌
- 客户端请求资源服务器的资源,资源服务校验令牌合法性,完成授权
- 资源服务器返回受保护资源
二、SSO 实现(SpringSecurity + Oauth2.0)
使用 Spring security + Oauth2.0 实现用户认证和用户授权。 Spring security 是一个强大的和高度可定制的身份验证和访问控制框架,其继承了 Oauth2.0 协议。
2.1 流程模型图
2.2 非对称加密算法实现本地令牌验证
由于传统的授权模式性能低下,每次都需要请求授权服务器校验令牌合法性,则会造成多于的请求,且拖慢响应速度。故使用非对称算法,利用公钥私钥完成对令牌的加密。
若加密成功,则表示令牌合法。若校验失败,则令牌无效不合法。
旧框架:
新框架: 本地令牌验证
2.2.1 RSA 非对称算法介绍
特点:公钥和私钥是成对的,它们互相解密
加解密: 公钥加密,私钥解密
签名:私钥签名,公钥验签
转载:RSA公钥,私钥和数字签名简单理解
2.2.2 生成公私钥
生成秘钥证书
秘钥证书中包含公钥和私钥
创建一个文件夹,并执行如下命令
keytool -genkeypair -alias tom -keyalg RSA -keypass tomtom -keystore tom.jks -storepass tomtom
Keytool 是一个 java 的证书管理工具
-alias:密钥的别名
-keyalg:使用的 hash 算法
-keypass:密钥的访问密码,能够读出证书
-keystore:密钥库文名
-storepass:密钥库的访问密码,能够解密数据
相关操作命令
# 查看证书信息
keytool -list -keystore tom.jks# 删除别名
keytool -delete -alias tom -keystore tom.jks
2.2.3 导出公钥
可以使用 openssl 来导出公钥信息
下载地址:
http://slproweb.com/products/Win32OpenSSL.html
网盘下载:
链接:https://pan.baidu.com/s/1Ov3gUtpGPHKFSc9iqC-sug 提取码:ir26
配置环境变量:
OPENSSL_CONF
D:\Common\OpenSSL-Win64\bin\openssl.cfg
在 Path 中增加:
D:\Common\OpenSSL-Win64\bin
导出公钥
keytool -list -rfc --keystore tomtom.jks | openssl x509 -inform pem -pubkey
保存公钥到文件中,公钥必须整理成同一行
修改配置文件:
2.2.4 测试
import com.alibaba.fastjson.JSON;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaSigner;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.interfaces.RSAPrivateKey;
import java.util.HashMap;
import java.util.Map;public class CreateJWTTestDemo {@Testpublic void testCreateToken() {// 加载证书ClassPathResource resource = new ClassPathResource("tomtom.jks");// 读取证书数据KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resource, "tomtom".toCharArray());// 获取证书中的一对秘钥KeyPair keyPair = keyStoreKeyFactory.getKeyPair("tomtom", "tomtom".toCharArray());// 获取私钥RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();// 创建令牌,使用私钥加盐[RSA]Map<String, Object> payload = new HashMap<String, Object>();payload.put("nickname", "tomcat");payload.put("address", "xiamen");payload.put("role", "admin, user");Jwt jwt = JwtHelper.encode(JSON.toJSONString(payload), new RsaSigner(privateKey));// 获取令牌数据String token = jwt.getEncoded();System.out.println(token);}@Testpublic void testParseToken() {String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZGRyZXNzIjoieGlhbWVuIiwicm9sZSI6ImFkbWluLCB1c2VyIiwibmlja25hbWUiOiJ0b21jYXQifQ.A0QrW0_Kk66G4fMdbFKPwJdsWmkwXJ4-8pymoJe28KGfuWtjOJKLZxdzjPuw9wItjoxTNHMWDCrLOxLCVuKbShIxpJDsmWecrmSjR6a8ZqBXqtw3-V_C-AYqJlyxHy7rfzCZzZDQ8GvyY7u4cFcx-iv42g9yVOLDc2DpnzvQ9uoLNYrqQuhagmEUtqrgHMX5OBYM1OP11VEjURVRt2tCOHX2Q285rkN-bgFgXtyscBkjjf5IGbiDXi6sofLTi_Ei2lEftvbhLNAi1LEQitCaZlqkh_ZsQ4d2RtJmP_vkRKCBHRhe0PNChIg0KI5TJ8fDeHgMfsEahcKbNfDdujfRzA";String publicKey = "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmI1RoiNIK6nP+GrlCTPSSR1ResUCDLHQ5o6kyA9wyqYj/8S8Xgjyp9AKf8e0AB+Yid0mjtVHQyt3PSXPPSAvypeKvGWyVaCuLEmUtE1FX2VcvPqdmEiGBhtF1cwGh+9DAGxp0tNUjbsVaS3A3QxR5MLWTbMJZFGlD2xhKjGc8qju/3UHKgbH1SDZKBJRk+Wdv7AWO5nf9sH2KDBo4Vm2HMwhfoPrrslIEBNfcaRqD317n5szvtDGd6zMSYA9ENOQ8dx1Yf2bve/B2jTQVdtO+v3uhKn2AAOChvVmjpdvVxXFWDmEBy+nUpMswMjheizlo0AWs8BqXdns9geY1E6icQIDAQAB-----END PUBLIC KEY-----";Jwt jwt = JwtHelper.decodeAndVerify(token,new RsaVerifier(publicKey));String claims = jwt.getClaims();System.out.println(claims);}
}
2.3 流程分析
流程分析:
- 用户登录,请求认证服务
- 认证服务认证通过,生成 jwt 令牌,并写入 cookie 中
- 用户访问资源时,带着 cookie 到网关
- 网关从 cookie 中获取 token,如果存在 token,则验证其合法性,若不合法则拒绝访问,合法则放行
- 用户退出时,请求认证服务,删除 cookie 中的 token