oAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 oAuth 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 oAuth 是安全的。
假设有一个“云笔记”产品,并提供了“云笔记服务”和“云相册服务”,此时用户需要在不同的设备(PC、Android、iPhone、TV、Watch)上去访问这些“资源”(笔记,图片)
那么用户如何才能访问属于自己的那部分资源呢?此时传统的做法就是提供自己的账号和密码给我们的“云笔记”,登录成功后就可以获取资源了。但这样的做法会有以下几个问题:
为了解决如上问题,oAuth 应用而生。
第三方应用程序(Third-party application): 又称之为客户端(client),比如上节中提到的设备(PC、Android、iPhone、TV、Watch),我们会在这些设备中安装我们自己研发的 APP。又比如我们的产品想要使用 QQ、微信等第三方登录。对我们的产品来说,QQ、微信登录是第三方登录系统。我们又需要第三方登录系统的资源(头像、昵称等)。对于 QQ、微信等系统我们又是第三方应用程序。
HTTP 服务提供商(HTTP service): 我们的云笔记产品以及 QQ、微信等都可以称之为“服务提供商”。
资源所有者(Resource Owner): 又称之为用户(user)。
用户代理(User Agent): 比如浏览器,代替用户去访问这些资源。
认证服务器(Authorization server): 即服务提供商专门用来处理认证的服务器,简单点说就是登录功能(验证用户的账号密码是否正确以及分配相应的权限)
资源服务器(Resource server): 即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。简单点说就是资源的访问入口,比如上节中提到的“云笔记服务”和“云相册服务”都可以称之为资源服务器
oAuth 在 “客户端” 与 “服务提供商” 之间,设置了一个授权层(authorization layer)。“客户端” 不能直接登录 “服务提供商”,只能登录授权层,以此将用户与客户端区分开来。“客户端” 登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。“客户端” 登录授权层以后,“服务提供商” 根据令牌的权限范围和有效期,向 “客户端” 开放用户储存的资料。
交互模型涉及三方:
认证服务器负责对用户进行认证,并授权给客户端权限。认证很容易实现(验证账号密码即可),问题在于如何授权。比如我们使用第三方登录 “云笔记”,你可以看到如使用 QQ 登录的授权页面上有 “云笔记将获得以下权限” 的字样以及权限信息
认证服务器需要知道请求授权的客户端的身份以及该客户端请求的权限。我们可以为每一个客户端预先分配一个 id,并给每个 id 对应一个名称以及权限信息。这些信息可以写在认证服务器上的配置文件里。然后,客户端每次打开授权页面的时候,把属于自己的 id 传过来
开放平台是由 oAuth2.0 协议衍生出来的一个产品。它的作用是让客户端自己去这上面进行注册、申请,通过之后系统自动分配 client_id
,并完成配置的自动更新(通常是写进数据库)。
客户端要完成申请,通常需要填写客户端程序的类型(Web、App 等)、企业介绍、执照、想要获取的权限等等信息。这些信息在得到服务提供方的人工审核通过后,开发平台就会自动分配一个 client_id
给客户端了。
到这里,已经实现了登录认证、授权页的信息展示。那么接下来,当用户成功进行授权之后,认证服务器需要把产生的 access_token
发送给客户端,方案如下:
让客户端在开放平台申请的时候,填写一个 URL,例如:http://www.qq.com
每次当有用户授权成功之后,认证服务器将页面重定向到这个 URL(回调),并带上 access_token
,例如:http://www.qq.com?access_token=123456789
客户端接收到了这个 access_token
,而且认证服务器的授权动作已经完成,刚好可以把程序的控制权转交回客户端,由客户端决定接下来向用户展示什么内容
Access Token 是客户端访问资源服务器的令牌。拥有这个令牌代表着得到用户的授权。然而,这个授权应该是 临时 的,有一定有效期。这是因为,Access Token 在使用的过程中 可能会泄露。给 Access Token 限定一个 较短的有效期 可以降低因 Access Token 泄露而带来的风险。
然而引入了有效期之后,客户端使用起来就不那么方便了。每当 Access Token 过期,客户端就必须重新向用户索要授权。这样用户可能每隔几天,甚至每天都需要进行授权操作。这是一件非常影响用户体验的事情。希望有一种方法,可以避免这种情况。
于是 oAuth2.0 引入了 Refresh Token 机制
Refresh Token 的作用是用来刷新 Access Token。认证服务器提供一个刷新接口,例如:
http://www.qq.com/refresh?refresh_token=&client_id=
传入 refresh_token
和 client_id
,认证服务器验证通过后,返回一个新的 Access Token。为了安全,oAuth2.0 引入了两个措施:
oAuth2.0 要求,Refresh Token 一定是保存在客户端的服务器上 ,而绝不能存放在狭义的客户端(例如 App、PC 端软件)上。调用 refresh
接口的时候,一定是从服务器到服务器的访问。
oAuth2.0 引入了 client_secret
机制。即每一个 client_id
都对应一个 client_secret
。这个 client_secret
会在客户端申请 client_id
时,随 client_id
一起分配给客户端。客户端必须把 client_secret
妥善保管在服务器上,决不能泄露。刷新 Access Token 时,需要验证这个 client_secret
。
实际上的刷新接口类似于:
http://www.qq.com/refresh?refresh_token=&client_id=&client_secret=
以上就是 Refresh Token 机制。Refresh Token 的有效期非常长,会在用户授权时,随 Access Token 一起重定向到回调 URL,传递给客户端
客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。 oAuth 2.0 定义了四种授权方式。
简化模式适用于纯静态页面应用。所谓纯静态页面应用,也就是应用没有在服务器上执行代码的权限(通常是把代码托管在别人的服务器上),只有前端 JS 代码的控制权。
这种场景下,应用是没有持久化存储的能力的。因此,按照 oAuth2.0 的规定,这种应用是拿不到 Refresh Token 的。其整个授权流程如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SLaDd1lr-1590645270277)(
该模式下,access_token
容易泄露且不可刷新
授权码模式适用于有自己的服务器的应用,它是一个一次性的临时凭证,用来换取 access_token
和 refresh_token
。认证服务器提供了一个类似这样的接口:
https://www.qq.com/exchange?code=&client_id=&client_secret=
需要传入 code``client_id``client_secret``access_token``refresh_token``code
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
这个 code 的作用是保护 token 的安全性。上一节说到,简单模式下,token 是不安全的。这是因为在第 4 步当中直接把 token 返回给应用。而这一步容易被拦截、窃听。引入了 code 之后,即使攻击者能够窃取到 code,但是由于他无法获得应用保存在服务器的 client_secret
,因此也无法通过 code 换取 token。而第 5 步,为什么不容易被拦截、窃听呢?这是因为,首先,这是一个从服务器到服务器的访问,黑客比较难捕捉到;其次,这个请求通常要求是 https 的实现。即使能窃听到数据包也无法解析出内容。
有了这个 code,token 的安全性大大提高。
密码模式中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向 “服务商提供商” 索要授权。在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分。
一个典型的例子是同一个企业内部的不同产品要使用本企业的 oAuth2.0 体系。在有些情况下,产品希望能够定制化授权页面。由于是同个企业,不需要向用户展示“xxx将获取以下权限”等字样并询问用户的授权意向,而只需进行用户的身份认证即可。这个时候,由具体的产品团队开发定制化的授权界面,接收用户输入账号密码,并直接传递给鉴权服务器进行授权即可。
有一点需要特别注意的是,在第 2 步中,认证服务器需要对客户端的身份进行验证,确保是受信任的客户端
如果信任关系再进一步,或者调用者是一个后端的模块,没有用户界面的时候,可以使用客户端模式。鉴权服务器直接对客户端进行身份验证,验证通过后,返回 token。
Spring Security 是一个安全框架,前身是 Acegi Security,oAuth2 实现方式,能够为 Spring 企业应用系统提供声明式的安全访问控制。Spring Security 基于 Servlet 过滤器、IoC 和 AOP,为 Web 请求和方法调用提供身份确认和授权处理,避免了代码耦合,减少了大量重复代码工作
spring security 的核心功能主要包括:
springsecurity是一个典型的责任链模式, 其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式。
springsecurity 执行过程的过滤器链
WebAsyncManagerIntegrationFilter
-> SecurityContextPersistenceFilter
-> HeaderWriterFilter
-> CsrfFilter
-> LogoutFilter
-> DefaultLoginPageGeneratingFilter
1、WebAsyncManagerIntegrationFilter
将Security上下文与Spring Web中用于处理异步请求映射的 WebAsyncManager 进行集成。
2、SecurityContextPersistenceFilter
在每次请求处理之前将该请求相关的安全上下文信息加载到SecurityContextHolder中,然后在该次请求处理完成之后,将SecurityContextHolder中关于这次请求的信息存储到一个“仓储”中,然后将 SecurityContextHolder中的信息清除
例如在Session中维护一个用户的安全信息就是这个过滤器处理的。
3、HeaderWriterFilter
用于将头信息加入响应中
4、CsrfFilter
用于处理跨站请求伪造
5、LogoutFilter
用于处理退出登录
6、UsernamePasswordAuthenticationFilter
用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自“/login”的请求。
从表单中获取用户名和密码时,默认使用的表单name值为“username”和“password”,这两个值可以通过设置这个过滤器的usernameParameter 和 passwordParameter 两个参数的值进行修改。
7、DefaultLoginPageGeneratingFilter
如果没有配置登录页面,那系统初始化时就会配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面。
8、BasicAuthenticationFilter
处理请求头信息,DigestAuthenticationFilter
9、RequestCacheAwareFilter
用来处理请求的缓存
10、SecurityContextHolderAwareRequestFilter
11、AnonymousAuthenticationFilter
12、SessionManagementFilter
13、ExceptionTranslationFilter
处理 AccessDeniedException 和 AuthenticationException 异常
14、FilterSecurityInterceptor
AbstractInterceptUrlConfigurer.createFilterSecurityInterceptor
15、RememberMeAuthenticationFilter的作用是, 当用户没有登录而直接访问资源时, 从cookie里找出用户的信息, 如果Spring Security能够识别出用户提供的remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入系统.
其代码顺序是:
WebAsyncManagerIntegrationFilter
-> SecurityContextPersistenceFilter
-> HeaderWriterFilter
-> CsrfFilter
-> LogoutFilter
-> DefaultLoginPageGeneratingFilter
在发送一次登入请求的时候:
请求首先进入WebAsyncManagerIntegrationFilter
这个过滤器,它下一个过滤器SecurityContextPersistenceFilter
通过request的attribute来上了一次请求锁,并且对SecurityContextHolder进行了管理;而SecurityContextHolder是对用户信息进行管理的一套系统,分为三大类
默认的SecurityContextHolderStrategy 实现类是ThreadLocalSecurityContextHolderStrategy 它使用了ThreadLocal来存储了用户信息。
下一个过滤器HeaderWriterFilter
请求和响应封装了一下,用于CsrfFilter
鉴别csrf攻击 LogoutFilter
判断是不是登出操作,如果是则不执行下面的过滤器,而执行登出的相关操作,DefaultLoginPageGeneratingFilter生成登录页
认证的工作是交给AuthenticationManager去做,AuthenticationManager下有多个认证器 AuthenticationProvider
只要其中一个AuthenticationProvider通过认证就算登陆成功,而且在认证器中抛出异常,无法终止认证流程只是算该认证器未通过。
当过滤器链走到尽头(FilterSecurityInterceptor)下一步就是鉴权了,鉴权功能会交给AccessDecisionManager去处理,而AccessDecisionManager下又有多个投票器,其中WebExpressionVoter是security的一个默认投票器
返回int类型,1表示赞成,0表示弃权,-1表示反对。当所有投票器的vote执行结束,如果最终结果小于0表示不通过,
方法的参数说明:
1.FilterInvocation 可获得请求的相关信息,比如请求方式(get post)url 等
2.authentication 是从securitycontext中拿出来的用户信息
3.Collection 是可以访问该路径的权限集合。也就是前面 SecurityContextHolderAwareRequestFilter 查找出来的权限
操作流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0k1bICVR-1590645270284)(E:\securityAndToken\Lusifer_201904030001.png)]
操作流程
配置认证服务器
配置数据源:DataSource
配置令牌存储方式:TokenStore
-> JdbcTokenStore
配置客户端读取方式:ClientDetailsService
-> JdbcClientDetailsService
配置服务端点信息:
AuthorizationServerEndpointsConfigurer
tokenStore
:设置令牌存储方式配置客户端信息:
ClientDetailsServiceConfigurer
withClientDetails
:设置客户端配置读取方式配置 Web 安全
BCryptPasswordEncoder
AuthenticationManagerBuilder
RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联。简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般是多对多的关系
在我们的 oAuth2 系统中,我们需要对系统的所有资源进行权限控制,系统中的资源包括:
系统的目的就是对应用系统的所有对象资源和数据资源进行权限控制,比如:功能菜单、界面按钮、数据显示的列、各种行级数据进行权限的操控
系统的具体操作者,可以归属于一个或多个角色,它与角色的关系是多对多的关系
为了对许多拥有相似权限的用户进行分类管理,定义了角色的概念,例如系统管理员、管理员、用户、访客等角色。角色具有上下级关系,可以形成树状视图,父级角色的权限是自身及它的所有子角色的权限的综合。父级角色的用户、父级角色的组同理可推
系统的所有资源进行权限控制,系统中的资源包括:
系统的目的就是对应用系统的所有对象资源和数据资源进行权限控制,比如:功能菜单、界面按钮、数据显示的列、各种行级数据进行权限的操控
系统的具体操作者,可以归属于一个或多个角色,它与角色的关系是多对多的关系
为了对许多拥有相似权限的用户进行分类管理,定义了角色的概念,例如系统管理员、管理员、用户、访客等角色。角色具有上下级关系,可以形成树状视图,父级角色的权限是自身及它的所有子角色的权限的综合。父级角色的用户、父级角色的组同理可推
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dOEOxBBU-1590645270288)(E:\securityAndToken\模块图.png)]参考资料:https://www.funtl.com/zh/spring-security-oauth2/PasswordEncoder.html#%E6%B5%8B%E8%AF%95%E8%AE%BF%E9%97%AE