网站首页 > 技术文章 正文
大家好,我是 杰哥
本篇,我们来认识一个你一定陌生又熟悉的授权协议 :OAuth2.0(Open Authorization)协议
你可能也发现了,不知不觉中,在登录一些 APP 或者网站(应用 B)时,为了方便,我们总会直接使用他们所提供的第三方登录方式,如微信或者 QQ 登录,进行扫码登录。它们实际上采用的就是 OAuth2.0 协议
它是一个关于授权(authorization)的开放网络标准,是目前应用最广泛的标准之一。指的是我们通过微信、github等帐号的方式登录一些网站或应用,比如 gitee、有道云笔记、简书等。目前包括 github、facebook、twitter、微信、微博、QQ 等社交工具都集成了 OAuth2.0 协议以供第三方应用程序使用,实现一个社交帐号打通所有应用的效果
实例
比如说,我们采用微信账号的方式来登录码云(gitee)
1、选择使用微信账号登录
登录码云,默认采用用户名密码的方式登录,我们选择使用微信账号登录的方式
2、进入授权页面,生成授权链接
点击微信图标之后,进入如下页面
我们可以看到,该页面实际上是生成了一个请求授权的链接:
https://open.weixin.qq.com/connect/qrconnect?appid=wx63d402790645b7e6&redirect_uri=https%3A%2F%2Fgitee.com%2Fauth%2Fwechat%2Fcallback&response_type=code&scope=snsapi_login&state=7d4c668ff6b13870f6894f7153480d31ac16ce955a4a4669#wechat_redirect
这个链接中包含如下几个字段:
- app_id:表示客户端的 ID,必选项
- redirect_uri:表示重定向 URI,可选项。当点击确认授权时,会重定向跳转到 redirect_url ,并带上 code 的参数
- scope:表示申请的权限范围,可选项
- state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值
- response_type:表示授权类型,必选项,此处的值固定为"code",即授权码当我们使用微信扫描了二维码之后,微信便进行对用户的身份验证
3、用户完成认证并决定是否同意该第三方的申请
验证成功之后,微信弹出如下页面,询问我们是否同意向这个应用开放我们的昵称、头像
点击"允许"之后,即表示通过了用户的同意
此时就成功完成了 gitee 的登录动作,接下来便可以正常使用 gitee 的功能了
这个例子中,用户想要登录 gitee, 但不想在这个网站上面进行用户名的注册,选择使用已有的微信账号进行快捷登录。而微信只需要验证一下用户的身份,并请求用户进行授权的确认即可实现登录效果
角色
通过这个例子,我们发现,OAuth2.0 授权协议包含了以下几种角色
1、Third-party application:第三方应用程序,即这里的码云 gitee
2、Resource Owner:资源所有者,也就是持有微信帐号的用户
3、User Agent:用户代理,比如浏览器这样的工具
4、Authorization server:授权服务器,也就是像微信、github 配置的专门用来处理授权的服务器
5、Resource server:资源服务器,即微信、github 存放用户生成的资源如帐号、头像、昵称等的服务器。需要注意的是它与认证服务器,可以是同一台服务器,也可以是不同的服务器
流程
整体流程如下:
即:
1、用户通过浏览器访问第三方应用。比如一个网站地址,即上述例子中的码云
2、重定向至授权页面,用户选择是否同意授权。用户允许授权之后,第三方应用则采用授权服务器(也叫认证服务器)进行登录。这里的微信就是授权服务器,采用 appid、appSecret 获取 授权码 code
3、授权服务器返回 code
4、第三方应用再根据 code 向授权服务器申请令牌 access_token
5、授权服务器询问用户是否同意授权该资源的访问
6、用户同意授权
7、用户同意授权之后,授权服务器则生成并返回 access_token
8、第三方应用再携带 access_token 请求相应的资源,如昵称、头像等
9、资源服务器检验其 access_token 的正确性。若 access_token 正确,那么返回相应资源即可
10、浏览器显示用户登录成功!
二、使用场景
OAuth2.0 协议的使用场景主要为:第三方登录和开放接口的调用
第三方登录就是我们上面所展示的,使用微信等第三方的方式来登录一个应用或者网站
而开发接口的调用,则比如说微信、美团等平台向外提供一些自己的接口,这些接口会由很多商家调用来进行开发,对于这些接口的调用则同样可以使用 OAuth2.0 协议
上面为大家展示了第三方登录的例子,接下来我们一起来看下采用 OAuth2.0 协议进行开放接口的开发实战
三、Spring Boot 集成 Security oauth2(实战)
(一)实战说明
包含两个项目:
- 授权服务器:springSecurityOathu2
- 资源服务器:resource_server
实现用户在访问该资源服务器上的资源时,必须首先经过授权服务器的授权才可以正常访问
两者的关系如下:
也就是说,用户在调用资源服务上的接口时,需要
1)根据 appid、appSecret 请求授权服务器获取 code
2)授权服务器需要检验用户的认证信息(若用户未登录,则需要登录),若认证信息校验通过,则返回授权码 code
3)用户再根据 code 获取 令牌 access_token
4)授权服务器询问用户是否允许授权
5)用户同意授权
6)用户同意授权之后,授权服务器生成并返回 令牌 access_token
7)用户再带上 access_token 请求资源
8)资源服务器会首先访问授权服务器,对该 access_token 的验证
9)授权服务器返回 access_token 的验证结果,若验证成功,则返回成功
10)资源服务器返回其请求的资源
(二)实战步骤
授权服务器配置
在 springSecurity (Spring Boot Security 实现认证授权功能的示例项目)项目的基础上,添加如下步骤:
1、引入依赖
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.5.2.RELEASE</version>
</dependency>
2、权限配置类 AuthorityConfig
首先采用注解 @EnableAuthorizationServer 声明该项目为授权服务器,然后继承 AuthorizationServerConfigurerAdapter 类来进行具体的权限配置
注意:在 Spring 2.7 中,类 AuthorizationServerConfigurerAdapter 已被标记为 deprecated ,也就是说我们这里的实现方式并不是最新的。最新的方案可以参考链接
https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide
但是无论是最新的还是旧的实现方案,思想都是一样的,我们先通过该方案了解其思想即可。最新的方案,我也会将代码更新至 github 上
1)通过 configure(AuthorizationServerSecurityConfigurer security) 方法配置允许表单模式的授权
/**
* 允许表单模式的认证
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients()
.checkTokenAccess("permitAll()");
}
2)配置允许通过的所有客户端的信息
- 写入内存的方式
- 通过覆盖 configure(ClientDetailsServiceConfigurer clients) 方法配置所授权的所有客户端信息
- 这里,我们先采用在代码中直接写入内存的方式进行配置,以便清晰地了解其配置方式
/**
* 配置所有客户端的信息(client_id、secret、授权模式、scope、资源id 以及重定向地址)
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
/**
* 方法一:内存配置
*/
clients.inMemory()
.withClient("user-client")
.secret(passwordEncoder.encode("123456"))
.authorizedGrantTypes("authorization_code")
.scopes("all")
.resourceIds("wj_resource")
.redirectUris("http://localhost:8082/user/hello");
}
配置了一个 client_id 为 user-client,密码为 123456,采用授权码模式进行认证、范围为 all,即所有,resourceId 为 wj_resource,redirectUris 为 http://localhost:8082/user/hello
即作为授权服务器,允许 client_id 为 user-client,密码为 123456,采用授权码模式进行认证,对于 资源标识为 wj_resource 的客户端通过授权,当用户确认授权之后的重定向地址为 http://localhost:8082/user/hello
如果还有有其他客户端同样需要使用授权服务器进行授权操作,那么可以继续配置
当然也可以直接将这些客户端管理在 db 中,我们来看看使用 db 来配置客户端的步骤
- db 的方式
先创建表 oauth_client_details ,包含字段 client_id、resource_ids、client_secret、scope、authorized_grant_types、web_server_redirect_uri 等关键字段。然后插入需要配置的所有客户端信息
建表语句:
create table oauth_client_details ( client_id VARCHAR(256) PRIMARY KEY, resource_ids VARCHAR(256), client_secret VARCHAR(256), scope VARCHAR(256), authorized_grant_types VARCHAR(256), web_server_redirect_uri VARCHAR(256), authorities VARCHAR(256), access_token_validity INTEGER, refresh_token_validity INTEGER, additional_information VARCHAR(4096), autoapprove VARCHAR(256));
INSERT INTO oauth_client_details (client_id, client_secret, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove)VALUES ('user-client', '$2a$10$4zd/aj2BNJhuM5PIs5BupO8tiN2yikzP7JMzNaq1fXhcXUefWCOF2', 'all', 'authorization_code,refresh_token,password', null, null, 3600, 36000, null, true);
INSERT INTO oauth_client_details (client_id, client_secret, scope, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove)VALUES ('order-client', '$2a$10$4zd/aj2BNJhuM5PIs5BupO8tiN2yikzP7JMzNaq1fXhcXUefWCOF2', 'all', 'authorization_code,refresh_token,password', null, null, 3600, 36000, null, true);
这里我们配置了 user-client 和 order-client 两个客户端 代码中直接通过配置数据库信息,代码中只需要通过 clients.jdbc(dataSource) 指定数据源信息,框架便会自动加载数据库中 oauth_client_details 表的信息
/**
* 配置所有客户端的信息(client_id、secret、授权模式、scope、资源id 以及重定向地址)
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
/**
* 方法二:db配置
*/
JdbcClientDetailsServiceBuilder jcsb = clients.jdbc(dataSource);
jcsb.passwordEncoder(passwordEncoder);
}
整体类配置如下:
/**
* 授权的 server 端
*/
@Component
@EnableAuthorizationServer
public class AuthorityConfig extends AuthorizationServerConfigurerAdapter {
@Resource
private PasswordEncoder passwordEncoder;
@Resource
private DataSource dataSource;
/**
* 允许表单模式的认证
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients()
.checkTokenAccess("permitAll()");
}
/**
* 配置所有客户端的信息(client_id、secret、授权模式、scope、资源id 以及重定向地址)
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
/**
* 方法一:内存配置
*/
// clients.inMemory()
// .withClient("user-client")
// .secret(passwordEncoder.encode("123456"))
// .authorizedGrantTypes("authorization_code")
// .scopes("all")
// .resourceIds("wj_resource")
// .redirectUris("http://localhost:8082/user/hello");
/**
* 方法二:db配置
*/
JdbcClientDetailsServiceBuilder jcsb = clients.jdbc(dataSource);
jcsb.passwordEncoder(passwordEncoder);
}
}
资源服务器配置
1、引入同样的依赖
<!--security 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.5.2.RELEASE</version>
</dependency>
2、建立资源 API
@RestController
@RequestMapping("/rs")
public class ResourceController {
@GetMapping("/get_resource")
public String hello() {
return "我是被保护的资源接口";
}
}
3、资源配置类
@Configuration
@EnableResourceServer
public class ResourceConfig extends ResourceServerConfigurerAdapter {
@Value("${wj.appid}")
private String wjAppId;
@Value("${wj.appsecret}")
private String wjAppSecret;
/**
* 密码解密类
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 校验 token 的正确性
* @return
*/
@Bean
@Primary
public RemoteTokenServices remoteTokenServices(){
final RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
remoteTokenServices.setClientId(wjAppId);
remoteTokenServices.setClientSecret(wjAppSecret);
remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:8082/oauth/check_token");
return remoteTokenServices;
}
/**
* 定义资源id,以及无状态声明
* @param resources
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId("wj_resource").stateless(true);
}
/**
* 设置 所有请求必须先授权之后再进行请求
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
//设置创建session策略
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
//@formatter:off
//所有请求必须授权
http.authorizeRequests()
.anyRequest().authenticated();
//@formatter:on
}
}
由于资源服务器与授权服务器不在同一个服务,那么需要
1)通过 remoteTokenServices() 类来配置校验 token 正确性的信息
2)通过 configure(ResourceServerSecurityConfigurer resources) 方法来指定资源 id 等信息
3)再通过 configure(HttpSecurity http) 设置所有请求必须先授权之后再进行请求
4、配置文件
配置该资源服务器的 appid 和 appsecret
wj:
appid: user-client
appsecret: 123456
server:
port: 8085
完成配置之后,我们预期实现的效果是,在访问资源 id 时需要带上令牌才能正常访问
而在此之前:
1)授权服务器会首先校验用户的信息
2)然后根据用户所提供的 appId、appSecret 等信息返回授权码 code 的值
3)再根据 code 的值返回 access_token 的值
让我们访问测试一下试试~
(三)访问测试
1、认证接口
请求如下认证接口的链接,询问用户是否同意授权
http://localhost:8082/oauth/authorize?client_id=user-client&response_type=code
会进入登录页面
输入用户名密码,点击登录以后,进入授权认证页面。浏览器提示我们,你是否允许 user-client 访问你的受保护的资源?
选择允许之后,点击授权
会跳转到我们提前配置好的重定向地址 http://localhost:8082/user/hello,同时带上授权码 code 的值
2、根据授权码 code 获取 token
拿到了授权码,我们再根据 grant_type、redirect_url 以及 scope 来使用如下链接,申请令牌 access_token(该链接为 post 请求方式)
http://localhost:8082/oauth/token?code=eQVgni&grant_type=authorization_code&redirect_uri=http://localhost:8082/user/hello&scope=all
可以看到,我们得到的 token 的值为 :Rt-oo359d5dVQviNeMlyr4PDskU
3、校验 token 的正确性
采用 链接 http://localhost:8082/oauth/check_token?token=Rt-oo359d5dVQviNeMlyr4PDskU,进行 token 正确性的检验
根据返回结果,发现该 access_token 的值,被检验通过
4、资源服务器的访问
1)资源服务端直接访问首先我们直接来访问资源 /rs/get_resource
由于在代码中配置了所有请求必须先授权之后再进行请求的规则,所以这样直接访问,会返回一个 401-未授权的错误
2)指定 token 进行访问
带上 token 进行访问,可以看到成功访问了到我们的接口
符合预期,那么我们的开放接口就开发好了
总结
本篇文章,我们主要介绍了OAuth2.0 授权协议及其两个主要的使用场景:第三方登录和开放接口的调用。分别通过使用微信账号登录 gitee 的实例与 Spring Boot 实现开放接口调用的实战,使大家进一步认识了 OAuth2.0 授权协议的机制
那么,你一定总结出来了,OAuth2.0 授权协议是用来干什么的呢?
一句话, OAuth2.0 授权协议其实就是将用户与应用之间的认证与授权动作,交给可信的第三方,如微信等授权服务器,而不用再次进行用户信息注册的一种协议
文章演示代码地址:
https://github.com/helemile/Spring-Boot-Notes/tree/master/spring_OAthu2
嗯,就这样。每天学习一点,时间会见证你的强大~
欢迎大家关注我们的公众号,一起持续性学习吧~
往期精彩回顾
总结复盘
架构设计读书笔记与感悟总结
带领新人团队的沉淀总结
复盘篇:问题解决经验总结复盘
网络篇
网络篇(四):《图解 TCP/IP》读书笔记
网络篇(一):《趣谈网络协议》读书笔记(一)
事务篇章
事务篇(四):Spring事务并发问题解决
事务篇(三):分享一个隐性事务失效场景
事务篇(一):毕业三年,你真的学会事务了吗?
Docker篇章
Docker篇(六):Docker Compose如何管理多个容器?
Docker篇(二):Docker实战,命令解析
Docker篇(一):为什么要用Docker?
..........
SpringCloud篇章
Spring Cloud(十三):Feign居然这么强大?
Spring Cloud(十):消息中心篇-Kafka经典面试题,你都会吗?
Spring Cloud(九):注册中心选型篇-四种注册中心特点超全总结
Spring Cloud(四):公司内部,关于Eureka和zookeeper的一场辩论赛
..........
Spring Boot篇章
Spring Boot(七):你不能不知道的Mybatis缓存机制!
Spring Boot(六):那些好用的数据库连接池们
Spring Boot(四):让人又爱又恨的JPA
SpringBoot(一):特性概览
..........
翻译
[译]用 Mint 这门强大的语言来创建一个 Web 应用
【译】基于 50 万个浏览器指纹的新发现
使用 CSS 提升页面渲染速度
WebTransport 会在不久的将来取代 WebRTC 吗?
.........
职业、生活感悟
你有没有想过,旅行的意义是什么?
程序员的职业规划
灵魂拷问:人生最重要的是什么?
如何高效学习一个新技术?
如何让自己更坦然地度过一天?
..........
猜你喜欢
- 2024-09-25 SpringBoot集成Spring Security入门体验
- 2024-09-25 Spring Boot Actuator的端点都怎么用?咱用事实说话
- 2024-09-25 Spring Security-2-表单认证 spring security关闭表单登陆
- 2024-09-25 自营性电商项目④ 自营式电商平台的主要优势在于
- 2024-09-25 微服务架构系列之–前后端分离 JWT认证机制
- 2024-09-25 SpringBoot整合SpringSecurity和JWT实现mymes认证和授权(一)
- 2024-09-25 跟我学spring security系列文章第一章 实现一个基本的登入
- 2024-09-25 SpringBoot集成Spring Security springboot集成Elasticsearch
- 2024-09-25 Spring Security身份验证详细介绍
- 2024-09-25 如何使用JWT和Spring Security保护REST API,你会多少?
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)