OAuth2及sa-token框架实践

admin 2024-01-21 47次阅读

1、解决什么问题 1.1 故事

以上,打开尚未登录的哔哩哔哩,选择登录,会看到其他登录方式有微信、微博以及QQ。

tokenpocet官网

登录页

tokenall中文官网

举例微博来说,这里,就是想要使用作为用户的“你”在微博的账号信息,简单地,你可以把微博的账户名、密码告诉B站,B站登录微博,就可以获取到它想要的信息。

但是这种方式存在一些问题:

就是解决以上的问题,它能保证B站能够得到“账号信息”的权限,而又不能发微博,在你想要取消授权的时候又不用大动干戈修改密码。

1.2 下个定义

OAuth(Open ,开放授权)是一种发布和与受保护数据交互的简单方式。它是互联网中基于令牌的身份验证和授权的开放标准,它允许第三方服务(例如)使用用户的账户信息,而不用暴露用户密码。OAuth规范描述了用于获取访问令牌的5种授权方式:

简单来说,OAuth规范中,首先需要获取访问令牌,后续对用户资源的访问,都是需要将访问令牌带上的。

2、授权码授权模式

.0规范提供的5中获取访问令牌的方式中,授权码授权是最为安全、且广泛使用的。在正式介绍授权码授权之前imToken钱包,首先声明几个概念,如上的故事中:

2.1 故事续

回到的登录故事,选择“微博登录”,这时我们可以看到以下页面

登录授权

扫码登录微博,同意授权

同意授权

同意授权后自动跳转

跳转页

因为尚未登录微博,所以需要先登录微博,整个流程是

这是一个标准的.0 授权码授权流程:

授权流程

有必要对 做一下单独说明;授权之前, 需要到 关联的 注册一次, 会为 分配一个 id以及 (密码)。

2.2 授权码授权流程

通过授权码方式获取访问令牌,总体来说分为两步,一是获取授权码,而是授权码换取访问令牌。结合请求微博授权的实例说明。更多详细参考规范

2.2.1 授权请求

对应以上流程第2步。授权请求是客户端应用发送给授权服务器,获取授权码的过程。

// bilibili发给微博的授权请求是这样的
https://api.weibo.com/oauth2/authorize?
client_id=2841902482&
redirect_uri=https%3A%2F%2Fpassport.bilibili.com%2Flogin%2Fsnsback%3Fsns%3Dweibo%26state%3Ddaee0470162b11edbd071613e9886b23%26source%3Dnew_main_mini&
scope=email###
// OAuth2 rfc 规范中的授权请求
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
        &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
    Host: server.example.com

这些参数的意义是,

可以看到微博的授权方式,与标准的.0有一定有差异,但是大体上流程是按照流程来的。

2.2.2 授权响应(重定向)

对应以上流程第5步。怎么就直接从第2步到了第5步token 权限管理·(中国)官方网站,步骤3、4呢?其实3、4步骤产生的结果是资源拥有者同意授权,这个过程不涉及客户端应用,完全是授权服务器内部的逻辑,所以在.0规范中不涉及。

在的故事中,如果用户事先登录了微博,那么请求授权的时候就不会触发扫码登录,而是直接展示要求授权的页面,让用户同意;同样,身份认证方式也可以不是扫码,而是用户名密码登录。总之,这都是授权服务器的内部事宜,产出结果是资源拥有者同意/不同意授权。

规范中,如果资源拥有者同意了授权,授权服务器的响应是

HTTP/1.1 302 Found
     Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
               &state=xyz

注意到这是一个重定向的响应(http 302)。user-agent(通常是浏览器)获得该响应后,会重定向访问地址,规范中在重定向地址中附加了参数 code,这就是授权码,后续客户端应用可以通过code换取访问令牌。

规范中,若资源拥有者不同意授权,会进行一些说明,如下

HTTP/1.1 302 Found
   Location: https://client.example.com/cb?error=access_denied&state=xyz

具体参考rfc规范,实现上也不是必须的。

微博的授权响应是,可以看到与规范差异比较大,关注,嗯?好像是用隐式授权,访问令牌直接暴露了,不怀好意的第三方可以通过这个访问令牌,访问用户在微博的一些资源了,哔哩哔哩的故事到此结束。

https://passport.bilibili.com/register/snsback.html?
sns=weibo&
snsUid=7780546299&
snsAccessToken=2.00PW4YUIoe11GD940dc91c8apEMysD&
snsAccessExpires=2644318&
csrf=21e50500160811eda02a22f04cb09b9e&
source=new_main_mini&
gourl=https%3A%2F%2Fwww.bilibili.com%2F#/

2.2.3 访问令牌请求

客户端应用使用授权码,请求授权服务器获取访问令牌。

POST /token HTTP/1.1
     Host: server.example.com
     Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
     Content-Type: application/x-www-form-urlencoded
     grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
     &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

可以看到,最重要的参数就是授权码code。成功的响应试是这样的:

HTTP/1.1 200 OK
     Content-Type: application/json;charset=UTF-8
     Cache-Control: no-store
     Pragma: no-cache
     {
       "access_token":"2YotnFZFEjr1zCsicMWpAA",
       "token_type":"example",
       "expires_in":3600,
       "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
       "example_parameter":"example_value"
     }

其中 token可以用于在acess token过期后,直接向授权服务器获取新的访问令牌,而不用资源拥有者参与。

3、sa-token实践.0授权码

sa-token是一个国产的轻量级Java权限认证框架,官方文档地址是:

根据官网的指引,搭建一个.0授权服务及其简单,直接参考官方指引即可:

最后可以下载官方的完整示例

image.png

但是目前的版本(v1.30.0)中并没有直接介绍资源服务器侧的相关信息,即资源服务器如何根据访问令牌来做对应的访问权限控制。

权限控制这一块sa-token已经提供了基础的信息以及API:

3.1 实践资源服务器

访问受保护资源时,规定通过请求头X--TOKEN传递授权获得访问令牌,通过以上分析,定义一个,在中校验请求携带的访问令牌,是否具备要求的scope权限。

部分代码如下:

private void accessTokenPermissionVerify(SaRequest request) {
    if (SaRouter.isMatchCurrURI("/info/**")) {
        // 获取请求头中的 access-token
        String accessToken = request.getHeader(AC_TOKEN_HEADER);
        if (StringUtils.isEmpty(accessToken)) {
            throw new NotPermissionException("未通过授权");
        }
        // 验证 access-token 是否有效
        AccessTokenModel acTokenEntity = SaOAuth2Util.getAccessToken(accessToken);
        if (null == acTokenEntity) {
            throw new NotPermissionException("未通过授权");
        }
        SaOAuth2Util.checkScope(accessToken, "userinfo");
    }
}

这里构建的权限模型是,访问url/info/**代表的资源,需要访问令牌,且具备权限。可以看到这里将url抽象为资源,匹配scope,并不能做到数据级别的权限控制,如用户A不能访问用户B的数据。数据权限是更加复杂的课题,已经不在的范围内了。

资源服务器的搭建完整示例:

重点关注类com....er对于权限模型的定义以及校验方法。

效果展示

胡乱填写的acess-token or 无权限的acess-token

GET http://localhost:8001/info/student?id=1
Accept: */*
X-ACCESS-TOKEN: ABG2ImmKfCKQXiIYlgtY9rotc2WDLojvfhzVY860aQsJk77j9GM9BKl6CtJm
###响应
{
  "code": 500,
  "msg": "无此权限:未通过授权",
  "data": null
}

有效&有权限的acess-token

GET http://localhost:8001/info/student?id=1
Accept: */*
X-ACCESS-TOKEN: ABG2ImmKfCKQXiIYlgtY9rotc2WDLojvfhzVY860aQsJk77j9GM9BKl6CtJm
###响应
{
  "id": "1",
  "name": "张三",
  "gender": "man",
  "age": 18,
  "mail": "[email protected]"
}