OAuth2及sa-token框架实践

1、OAuth2解决什么问题

1.1 bilibili故事

https://www.bilibili.com/

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

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

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

  • 信任问题;通过账户名+密码,B站可以拿到你微博账号的所有权限,万一给你发了条不可描述的微博咋办
  • 取消授权;如果想要不让B站继续访问你的微博账号信息,唯一可以做的就是修改密码了

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

1.2 下个定义

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

  • 授权码授权
  • 隐式授权
  • 资源所有者凭据授权;用户的用户名+密码,就是一种资源所有者凭据
  • 客户端凭据授权
  • 刷新令牌授权

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

2、授权码授权模式

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

  • Resource Owner 资源所有者;“你”,想要登录bilibili的你,拥有微博账号信息所有权的你
  • Client Application 客户端应用;bilibili,它想要使用resource owner的某些资源
  • Resource Server 资源服务器;微博,承载resource owner资源的网络服务器
  • Authorization Server 授权服务器;微博,负责验证用户身份,然后向客户端应用颁发访问令牌的网络服务器

2.1 bilibili故事续

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

登录授权

扫码登录微博,同意授权

同意授权

同意授权后自动跳转

跳转页

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

  • 微博进行用户身份验证,确认资源所有者的身份
  • 微博进行授权,让资源所有者选择授予的权限,这里的权限有个人信息、分享、联系邮箱
  • 通过微博授权服务器的重定向,bilibili得到了授权码
  • bilibili通过授权码请求微博,得到访问令牌(acess token),这一步是在后台进行的,前端页面不可见

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

授权流程

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

2.2 授权码授权流程

通过授权码方式获取访问令牌,总体来说分为两步,一是获取授权码,而是授权码换取访问令牌。结合bilibili请求微博授权的实例说明。更多详细参考规范 https://www.tech-invite.com/y65/tinv-ietf-rfc-6749-2.html#e-4-1

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

这些参数的意义是,

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

2.2.2 授权响应(重定向)

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

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

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

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

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

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

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

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

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

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"
     }

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

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

sa-token是一个国产的轻量级Java权限认证框架,官方文档地址是: https://sa-token.dev33.cn/doc/index.html#/

根据官网的指引,搭建一个OAuth2.0授权服务及其简单,直接参考官方指引即可: https://sa-token.dev33.cn/doc/index.html#/oauth2/oauth2-server

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

image.png

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

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

  • acess-token对应的scope,即权限,可以用一些逗号分隔的字符串来抽象表示一些权限,比如示例中的userinfo,可以抽象为用户信息访问权限
  • 权限验证的API, 如 cn.dev33.satoken.oauth2.logic.SaOAuth2Util#checkScope(accessToken, scpes),接受acess-token和需要校验的权限列表,检查acess-token是否具备这些权限
  • 构建url<->scope权限模型,访问哪个url,必须具备什么scope;可以通过servlet filter技术校验这个模型。

3.1 实践资源服务器

访问受保护资源时,规定通过请求头X-ACCESS-TOKEN传递授权获得访问令牌,通过以上分析,定义一个Filter,在filter中校验请求携带的访问令牌,是否具备要求的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/**代表的资源,需要访问令牌,且具备scopeuserinfo权限。可以看到这里将url抽象为资源,匹配scope,并不能做到数据级别的权限控制,如用户A不能访问用户B的userinfo数据。数据权限是更加复杂的课题,已经不在OAuth2的范围内了。

资源服务器的搭建完整示例: https://gitee.com/Z-A/sa-token-all

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

效果展示

胡乱填写的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": "zhangsan@qq.com"
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值