授权机制(OAuth2、JWT、Security)
授权机制(OAuth2、JWT、Security)
1、前言背景
- 目前主流的用户认证方法有:
- 基于session
- 基于token
JWT
是用在前后端分离, 需要简单的对后台API进行保护时使用.(前后端分离无session, 频繁传用户密码不安全)
这里有具体的详情:session和jwt区别
- 第三方网站登录:
OAuth2
用在使用第三方账号登录的情况(比如使用weibo, qq, github登录某个app)
参考文章:http://www.rfcreader.com/#rfc6750
2、OAuth2
参考文章:前言技术之Oauth2全方面介绍
2.1、OAuth2的四种角色,它们的关系
Oauth一共定义了四种角色:
-
1.
资源所有者
(Resource Owner) :即代表用户本身 -
2.
资源服务器
(Resource Server) :存储受保护的账号信息 -
3.
授权服务器
(Authorization Server) :在成功验证用户身份,并获得授权后,给客户端派发访问资源令牌 -
4.
客户端
(Client) :即代表你访问的第三方应用
A 客户端向资源拥有者发送授权申请;
B 资源拥有者同意客户端的授权,返回授权码;
C 客户端使用授权码向认证服务器申请令牌token;
D 认证服务器对客户端进行身份校验,认证通过后发放令牌;
E 客户端拿着认证服务器颁发的令牌去资源服务器请求资源;
F 资源服务器校验令牌的有效性,返回给客户端资源信息;
例子:
2.2、Oauth2四种认证方式/授权方式
授权码
:通过授权码,服务端通过 Ajax 把 access token 返回给客户端隐式
:用于移动 APP 或 web 应用(应用运行在用户的设备上)用户密码凭证
:同一个公司不同系统之间内部账户互联互通,比如国内某社区的代码托管系统通过社区的账户也可以登录。客户端凭证
:第三方应用自身服务访问提供 OAuth2 服务提供的平台资源
2.2.1、流程图
2.2.1.1、授权码模式
2.2.1.2、隐式授权模式
2.2.1.3、密码模式
2.2.1.4、客户端模式
2.3、oauth2 模式是否需要前后端
2.4、oauth2 选型
参考文章:https://blog.youkuaiyun.com/fcclzydouble/article/details/124021146
2.5、请求参数说明
2.5.1、授权码模式
2.5.1.1、获取授权码
请求URL
https://authorization-server.com/auth
?response_type=code
&client_id=29352910282374239857
&redirect_uri=https%3A%2F%2Fexample-app.com%2Fcallback
&scope=create+delete
&state=xcoiv98y3md22vwsuye3kch
请求参数说明:
参数 | 是否必须 | 说明 |
---|---|---|
response_type | 是 | 值为code表示指定服务端返回授权码 |
client_id | 是 | 平台分配给第三方应用的 ID |
redirect_uri | 是 | 回调 URL ,处理完后会直接重定向回调URL,并在回调URL后面加上?code=,例如:https://dropletbook.com/callback?code=AUTHORIZATION_CODE |
scope | 是 | 请求权限类型,一个或多个空格分隔的字符串,指示应用程序恳求的权限。 |
state | 否 | 应用程序生成一个随机字符串并将其包括在恳求中。然后它应该查看在用户授权应用程序后是否回来相同的值。这用于避免 CSRF 攻击。 |
2.5.1.2、获取access_token
请求URL
https://cloud.digitalocean.com/v1/oauth/token?client_id=CLIENT_ID&client_secret=CLIENT_SECRET&grant_type=authorization_code&code=AUTHORIZATION_CODE&redirect_uri=CALLBACK_URL
请求参数说明:
参数 | 是否必须 | 说明 |
---|---|---|
client_id | 是 | 平台分配给第三方应用的 ID |
client_secret | 是 | 平台分配给第三方应用的 密钥 |
grant_type | 是 | 授权类型,填写authorization_code,表示授权码模式 |
code | 是 | 授权码 |
redirect_uri | 是 | 一定和申请授权码时用的redirect_uri一致 |
2.5.2、隐式模式
2.5.2.1、获取access_token
请求URL
https://authorization-server.com/auth
?response_type=token
&client_id=29352910282374239857
&redirect_uri=https%3A%2F%2Fexample-app.com%2Fcallback
&scope=create+delete
&state=xcoiv98y3md22vwsuye3kch
请求参数说明:
参数 | 是否必须 | 说明 |
---|---|---|
response_type | 是 | 值为token表示指定服务端返回token |
client_id | 是 | 平台分配给第三方应用的 ID |
redirect_uri | 是 | 回调 URL ,处理完后会直接重定向回调URL,并在回调URL后面加上?code=,例如:https://dropletbook.com/callback?code=AUTHORIZATION_CODE |
scope | 是 | 请求权限类型,一个或多个空格分隔的字符串,指示应用程序恳求的权限。 |
state | 否 | 应用程序生成一个随机字符串并将其包括在恳求中。然后它应该查看在用户授权应用程序后是否回来相同的值。这用于避免 CSRF 攻击。 |
2.5.3、密码模式
2.5.3.1、获取access_token
请求URL
POST /oauth/token HTTP/1.1
Host: authorization-server.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic Y2xpZW50OnNlY3JldA==
grant_type=password&username=johndoe&password=A3ddj3w
在上面的请求中,Authorization头部包含了客户端的ID和Secret,"grant_type=password"表示使用密码模式进行授权,"username"和"password"分别是用户的用户名和密码。
请求参数说明:
参数 | 是否必须 | 说明 |
---|---|---|
grant_type | 是 | 值为password表示使用密码模式 |
username | 是 | 用户名 |
password | 是 | 密码 |
2.5.4、客户端模式
2.5.4.1、获取access_token
请求URL
POST /oauth/token HTTP/1.1
Host: authorization-server.com
Authorization: Basic Base64Encode(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
在上面的请求中,Authorization头部包含了客户端的ID和Secret,
请求参数说明:
参数 | 是否必须 | 说明 |
---|---|---|
grant_type | 是 | 值为client_credentials表示客户端模式 |
2.5.5、token
2.5.5.1、token使用
token_type:有MAC Token
与Bearer Token
两种类型,两种的校验算法不同,RFC 6750建议Oauth2采用 Bearer Token(http://www.rfcreader.com/#rfc6750)。
Bearer Token
bearer token 是一个加密字符串,通常由服务端根据密钥生成。
客户端在请求服务端时,必须在请求头中包含 Authorization: Bearer <token>
。
服务端收到请求后,解析出 <token>
,并校验 <token>
的合法性。
如果校验通过,则认证通过。
例如:
GET /api/userinfo HTTP/1.1
Host: resource-server.com
Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA
MAC Token(略)
(我也没找到资料)
2.5.5.2、刷新token
请求URL
https://cloud.digitalocean.com/v1/oauth/token?grant_type=refresh_token&client_id=CLIENT_ID&client_secret=CLIENT_SECRET&refresh_token=REFRESH_TOKEN
请求参数说明:
参数 | 是否必须 | 说明 |
---|---|---|
grant_type | 是 | 授权类型,填写refresh_token,表示授权码模式刷新token |
client_id | 是 | 平台分配给第三方应用的 ID |
client_secret | 是 | 平台分配给第三方应用的 密钥 |
refresh_token | 是 | 刷新token,这个和access_token不一样 ,在获取access_token的时候,会返回refresh_token |
3、spring security
3.1、OAuth2 整合
参考文章:https://docs.spring.io/spring-security/reference/servlet/oauth2/login/index.html
英文文档比较难读,而且暂时不需要用,所以就不深入去解读了。
3.1.1、配置自定义提供程序属性
例如:
spring:
security:
oauth2:
client:
registration:
okta:
client-id: okta-client-id
client-secret: okta-client-secret
provider:
okta:
authorization-uri: https://your-subdomain.oktapreview.com/oauth2/v1/authorize
token-uri: https://your-subdomain.oktapreview.com/oauth2/v1/token
user-info-uri: https://your-subdomain.oktapreview.com/oauth2/v1/userinfo
user-name-attribute: sub
jwk-set-uri: https://your-subdomain.oktapreview.com/oauth2/v1/keys
4、JWT
4.1、jwt概念
参考文章:https://blog.youkuaiyun.com/sssssooo/article/details/128509302
JWT是一种认证协议,提供了一种用于发布接入令牌(Access Token),并对发布的签名接入令牌进行验证的方法。SSO私钥加密token。应用端公钥解密token,
4.2、jwt格式
参考文章:https://www.jianshu.com/p/455a537854a9
参考文章:https://blog.youkuaiyun.com/Solo95/article/details/123456965
一个JWT包含3个部分:
头部Header
:可存放签名的类型
数据Payload
:可存放用户数据和有效期,Payload的内容在接收者端是通过签名(Signature)来校验的。
签名Signature
:使用密钥对Header和Payload数据进行加密生成签名。
4.2.1、header
{
"alg": "HS256",
"typ": "JWT"
}
JWS | 算法名称 | 描述 |
---|---|---|
HS256 | HMAC256 | HMAC with SHA-256 |
HS384 | HMAC384 | HMAC with SHA-384 |
HS512 | HMAC512 | HMAC with SHA-512 |
RS256 | RSA256 | RSASSA-PKCS1-v1_5 with SHA-256 |
RS384 | RSA384 | RSASSA-PKCS1-v1_5 with SHA-384 |
RS512 | RSA512 | RSASSA-PKCS1-v1_5 with SHA-512 |
ES256 | ECDSA256 | ECDSA with curve P-256 and SHA-256 |
ES384 | ECDSA384 | ECDSA with curve P-384 and SHA-384 |
ES512 | ECDSA512 | ECDSA with curve P-521 and SHA-512 |
PS256 | RSAPSSSA256 | RSAPSS with SHA-256 |
PS384 | RSAPSSSA384 | RSAPSS with SHA-384 |
4.2.2、Payload
JWT token的第二部分是包含了声明的payload,声明是一个实体的表述加上额外信息,一共有三种形式的声明:注册、共有和私有。
注册声明(建议但不强制):
- iss: jwt签发者
- sub: jwt使用者
- aud: jwt接收者
- exp: jwt过期时间,该时间必须大于签发时间
- nbf: 定义在某个时间之前,该jwt都是不可用的
- iat: jwt签发时间
- jti: jwt唯一身份标注,主要用于一次性token,从而避免重放攻击
(注:为了保持紧凑,注册声明都是三个字母)
公有声明:
公有声明可以加入任何信息,一般会添加用户相关信息或者业务需要的信息,但不建议添加敏感信息,因为该部分会在客户端解密。
私有声明:
私有声明是提供者和使用者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密,基本等同于明文信息。
例如:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
4.2.3、Signature(签名)
4.2.4、公式
String header = "";
String payload = "";
String secret = "";
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
4.3、java代码
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
public final static String SECRET = "abcdefghijklmnopqrstuvwxyz";
public String getToken(){
// Jwt存储信息
Map<String, Object> claimsMap = new HashMap<String, Object>();
claimsMap.put("user_key", token);//随机id
claimsMap.put("user_id", userId);//用户id
claimsMap.put("username", userName);//用户名
return createToken(claimsMap);
}
public static String createToken(Map<String, Object> claims)
{
String token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();
return token;
}