
SpringCloud系列—Spring Cloud 开放认证Oauth2.0应用
作者 | 宇木木兮
来源 |今日头条
学习目标
统一认证与授权——Oauth2.0开放授权平台
第1章 Oauth2.0简介
OAuth2.0是OAuth协议的延续版本,但不向前兼容OAuth 1.0(即完全废止了OAuth1.0)。 OAuth 2.0关注客户端开发者的简易性。要么通过组织在资源拥有者和HTTP服务商之间的被批准的交互动作代表用户,要么允许第三方应用代表用户获得访问的权限。同时为Web应用,桌面应用和手机,和起居室设备提供专门的认证流程。
上面这段话是百度百科上面给的解释,其实Oauth2.0说白了就是一种协议,而按照这种协议的话,我们能实现第三方授权操作;第三方授权其实我们也都用到过,就比如说现在我要去爱奇艺看电视,而有些资源是需要VIP的,那我首先得登录吧,然后登录之后再充个会员就可以看了。那登录的时候你会发现,可以选择微信等账号登录,这其实就是Oauth2.0的应用。
1.1 Oauth2.0中的角色
在正式讲解Oauth2.0的实现流程与实战之前,我们先来明确一下这个玩意中的几种角色。网上也有一大堆资料,但是可能很多同学可能分不太清这些角色到底是什么,OK,那我这里给大家做个解释。
还是按照上面那个登录爱奇艺的例子来看,我们要通过微信这种方式去登录爱奇艺,那这个时候实际上就是我们在点击微信登录按钮的时候,首先第三方客户端(也就是爱奇艺)要先重定向请求到微信的某个接口,请求认证,如果通过认证了,微信就会返回一个token给爱奇艺,然后爱奇艺再根据这个token去微信的另一个接口获取我的微信名啊、头像啊等等资源。这就是大体流程,后面会给出具体流程图。接下来我们根据这个流程来分析一下有哪些角色。
- 资源所有者(Resource Owner):就是我,资源只的就是我在微信上面的头像啊,昵称啊等资源。
- 第三方应用:或者称为第三方客户端(Clinet),希望使用资源服务器提供的资源,这里就是爱奇艺,它想要使用我的微信名,微信头像等资源。
- 认证服务器(Authorization Server):专门用于对资源所有者的身份进行认证,对要访问的资源进行授权、产生令牌的服务器。访问资源,需要通过认证服务器由资源所有者授权才可以访问。这里其实就是微信的授权系统。
- 资源服务器(Resource Server):存储用户的资源,验证令牌有效性。这里指的就是微信中保存我微信信息的服务器。
- 服务提供商(Service Provider):认证服务和资源服务归属于一个机构,该机构就是服务提供商。就是腾讯嘛。
OK,明确了角色的概念之后,再来看看这个登录的具体流程。
1.2 Oauth2.0模式
OAuth2.0在第三方应用和服务提供商之间,设置一个授权层(authorization layer)。这样做的目的就是因为第三方应用它不能直接登录服务提供商的,就像爱奇艺不能直接登录微信一样,那这个时候如果爱奇艺想要用微信的资源的话,怎么办?就是在他们之间增加一个授权层,让用户给爱奇艺授权,这样,爱奇艺就能使用该用户的某一部分资源了。而Oauth2.0又分为四种授权方式,接下来每种模式都介绍一下。
1.2.1 授权码模式
Authorization Code
授权码模式(Authorization Code):功能是最完整的,流程也是最严密的,国内各大服务提供商(微信、微博、淘宝、百度)都是使用此授权模式进行授权。该授权模式可以确定是用户进行授权的,并且令牌是认证服务器发放到第三方应用服务器上,而不是浏览器上。该模式的认证流程如下:
- 用户进入爱奇艺应用,想看特殊电影,那得登录啊。
- 点击微信登录,就会跳转到微信的登录页面,这是爱奇艺重定向到微信的认证服务端
- 返回一个授权页面给用户
- 用户微信扫码或者直接输入账号密码进行授权
- 微信认证服务器产生一个授权码返回给爱奇艺
- 爱奇艺拿到这个授权码然后再发送请求到微信认证服务器请求一个token
- 微信认证服务器认证成功返回给爱奇艺一个token
- 爱奇艺拿着这个token请求微信的资源服务器获取微信名和头像
- 微信资源服务器校验token成功后响应这些信息给爱奇艺
- 爱奇艺登录成功
- 返回特殊电影给用户
1.2.2 简化模式
简化模式(Implicit):和授权码模式不同的是,令牌发放给浏览器,OAuth2客户端运行在浏览器中。而不是发放该第三方应用的服务器。1.2.3 密码模式
密码模式(resource owner password credentials):将用户和密码传过去,直接获取accesstokne,用户同意授权动作是在第三方应用上完成,而不是在认证服务器。第三方应用申请令牌时,直接带用户名和密码去向认证服务器申请令牌。这种方式认证服务器无法断定用户是否真的授权,用户和密码可能是第三方应用盗取过来的。
流程如下:
- 用户向客户端直接提供认证服务器想要的用户名和密码。
- 客户端将用户名和密码发给认证服务器,向认证服务器请求令牌
- 认证服务器确认后,向客户端提供访问令牌
- 后面的流程跟上面的类似
1.2.4 客户端模式
客户端模式(client credentials):这种模式实际上不是让用户授权去登录微信了,而是让客户端也就是爱奇艺自己去登录微信,拿相应的资源。这种模式使用较少,当一个第三方应用自己本身需要获取资源,而不是获取用户资源时,客户端模式十分有用。
流程如下:
- 客户端向认证服务器进行身份认证,并要求一个访问令牌
- 认证服务器确认后,向客户端提供访问令牌
第2章 授权码模式实战
在明白了Oauth2.0到底是个什么玩意儿,以及它的四种模式之后,接下来我们通过SpringSecurity来实现一下授权码模式,这种模式是市面上用的最广的。项目框架依然是以mall这个项目为主。但是会创建一个单独的认证服务器模块(mall-oauth2)和资源服务器模块(mall-resource)。
2.1 认证服务器
认证服务器是服务提供者专门用来处理认证授权的服务器,主要负责获取用户授权并颁发token,以及完成后续的token认证工作。认证部分功能主要由spring security 负责,授权则由oauth2负责。
2.1.1 项目搭建
创建一个springboot项目 mall-oauth2项目,配置pom
2.1.2 表结构设计
1.在oauth2库中增加表结构
- oauth_access_token:存储生成的access_token,由类JdbcTokenStore操作
- oauth_client_details:存储客户端的配置信息,由类JdbcClientDetailsService操作
- oauth_code:存储服务端系统生成的code的值,由类JdbcAuthorizationCodeServices操作
- oauth_refresh_token:存储刷新令牌的refresh_token,如果客户端的grant_type不支持refresh_token,那么不会用到这张表,同样由类JdbcTokenStore操作
2.然后创建用户和角色表
2.1.3 配置
1.配置文件
2.开启Spring Security 安全配置
创建WebSecurityConfig 继承WebSecurityConfigurerAdapter
通过@EnableWebSecurity 开启Spring Security配置,继承WebSecurityConfigurerAdapter的方法,实现个性化配置。如果使用内存配置用户,可以重写其中的configure方法进行配置,由于我们使用数据库中的用户信息,所以不需要在这里进行配置。并且采用认证服务器和资源服务器分离,也不需要在这里对服务资源进行权限的配置。
在类中创建了两个Bean,分别是用于处理认证请求的认证管理器AuthenticationManager,以及配置全局统一使用的密码加密方式BCryptPasswordEncoder,它们会在认证服务中被使用。
3.创建认证服务配置类
在类中,通过@EnableAuthorizationServer 注解开启认证服务,通过继承父类AuthorizationServerConfigurerAdapter,对以下信息进行了配置:
ClientDetailsServiceConfigurer:配置客户端服务,这里我们通过JdbcClientDetailsService从数据库读取相应的客户端配置信息,进入源码可以看到客户端信息是从表oauth_client_details中拉取。
AuthorizationServerEndpointsConfigurer:用来配置授权(authorization)以及令牌(token)的访问端点,以及令牌服务的配置信息。该类作为一个装载类,装载了Endpoints所有的相关配置。
AuthorizationServerSecurityConfigurer:配置令牌端点(endpoint)的安全约束,OAuth2开放了端点用于检查令牌,/oauth/check_token和/oauth/token_key这些端点默认受到保护,在这里配置可被外部调用。
4.采用从数据库中获取用户信息的方式进行身份验证
创建UserDetailServiceImpl 实现UserDetailsService接口,并实现loadUserByUsername方法,根据用户名从数据库查询用户信息及权限。
5.创建MyUser和Permission等实体类,具体参考代码
2.1.4 启动服务
首先发起请求获取授权码(code),直接访问下面的url:
http://localhost:7070/oauth/authorize?client_id=iqiyi&response_type=code
- client_id:因为认证服务器要知道是哪一个应用在请求授权,所以client_id就是认证服务器给每个应用分配的id
- redirect_uri:重定向地址,会在这个重定向地址后面附加授权码,让第三方应用获取code
- response_type:code表明采用授权码认证模式
- scope:需要获得哪些授权,这个参数的值是由服务提供商定义的,不能随意填写
首先会重定向到登录验证页面,因为之前的url中只明确了第三方应用的身份,这里要确定第三方应用要请求哪一个用户的授权。输入数据库表user中配置的用户信息 admin/123456:
注意url中请求的参数必须和在数据库中的表oauth_client中配置的相同,如果不存在或信息不一致都会报错,在参数填写错误时会产生如下报错信息:
如果参数完全匹配,会请求用户向请求资源的客户端client授权,因为我数据库的autoapprove字段设置为true了,所以直接会进行授权。并且页面会跳转到redirect_uri定义的地址,并在uri后面带上code授权码。
这样,用户的登录和授权的操作都在浏览器中完成了,接下来我们需要获取令牌,发送post请求到/oauth/token接口,使用授权码获取access_token。在发送请求时,需要在请求头中包含clientId和clientSecret,并且携带参数 grant_type、code、redirect_uri,这里会对redirect_uri做二次验证:
这样,就通过/oauth/token端点获取到了access_token,并一同拿到了它的令牌类型、过期时间、授权范围信息,这个令牌将在请求资源服务器的资源时被使用。
2.1.5 相关参数说明
配置说明:
- 可以配置:"authorization_code", "password", "implicit","client_credentials","refresh_token"
- scopes:授权范围标识,比如指定微服务名称,则只可以访问指定的微服务
- autoApprove: false跳转到授权页面手动点击授权,true不需要手动授权,直接响应授权码
- redirectUris:当获取授权码后,认证服务器会重定向到指定的这个URL,并且带着一个授权码code响应。
- withClient:允许访问此认证服务器的客户端ID
- secret:客户端密码,加密存储
- authorizedGrantTypes:授权类型,支持同时多种授权类型
令牌访问端点 Spring Security对OAuth2提供了默认可访问端点,即URL - /oauth/authorize:申请授权码code,涉及类AuthorizationEndpoint
- /oauth/token:获取令牌token,涉及类TokenEndpoint
- /oauth/check_token:用于资源服务器请求端点来检查令牌是否有效,涉及类CheckTokenEndpoint
- /oauth/confirm_access:用于确认授权提交,涉及类WhitelabelApprovalEndpoint
- /oauth/error:授权错误信息,涉及WhitelabelErrorEndpoint
- /oauth/token_key:提供公有密匙的端点,使用JWT令牌时会使用,涉及类TokenKeyEndpoint
2.2 资源服务器
上文介绍了认证服务器的搭建,也通过例子从认证服务器中获取到了token值,接下来,我们就搭建一下资源服务器,当爱奇艺发送token过来之后,资源服务器通过验证返回资源。资源服务器其实就理解成一个springboot的web工程就完事了,可以提供一些controller接口,它和认证服务器可以部署在一起,也可以分离部署,我们这里采用分开部署的形式。
2.2.1 项目搭建
创建一个springboot的web项目 mall-resource项目,配置pom
2.2.2 配置
1.增加配置类
在类中主要实现了以下功能:
- @EnableResourceServer注解表明开启OAuth2资源服务器,在请求资源服务器的请求前,需要通过认证服务器获取access_token令牌,然后在访问资源服务器中的资源时需要携带令牌才能正常进行请求
- 通过RemoteTokenServices实现自定义认证服务器,这里配置了我们之前创建的认证服务器
- 重写configure(HttpSecurity http)方法,开启所有请求需要授权才可以访问
- 配置资源相关设置configure(ResourceServerSecurityConfigurer resources),这里只设置resourceId,作为该服务资源的唯一标识
- @EnableGlobalMethodSecurity(prePostEnabled = true):开启方法级权限控制
- 创建 RemoteTokenServices 远程校验令牌服务,去校验令牌有效性,因为当前认证和资源服务器不是在同一工程中,所以要通过远程调用认证服务器校验令牌是否有效,如果认证和资源服务器在同一工程中,可以使用 DefaultTokenServices 配置校验令牌。
2.controller
2.2.3 测试
1.不带access_token访问2.带上access_token之后
使用Postman,在Authorization中配置使用Bearer Token,并填入从认证服务器获取的access_token(或在Headers中的Authorization字段直接填写Bearer 'access_token'),再次访问接口,可以正常访问接口资源:
