面试官:能说一说微信授权的原理吗?(Spring Cloud OAuth2 授权码模式)
我是风筝,公众号「古时的风筝」,一个简单的程序员鼓励师。
文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面。
上一篇文章Spring Cloud OAuth2 实现单点登录介绍了使用 password 模式进行身份认证和单点登录。本篇介绍 Spring Cloud OAuth2 的另外一种授权模式-授权码模式。
授权码模式认证过程
授权码模式的认证过程是这样的:
1、用户客户端请求认证服务器的认证接口,并附上回调地址;
2、认证服务接口接收到认证请求后调整到自身的登录界面;
3、用户输入用户名和密码,点击确认,跳转到授权、拒绝提示页面(也可省略);
4、用户点击授权或者默认授权后,跳转到微服务客户端的回调地址,并传入参数 code;
5、回调地址一般是一个 RESTful 接口,此接口拿到 code 参数后,再次请求认证服务器的 token 获取接口,用来换取 access_token 等信息;
6、获取到 access_token 后,拿着 token 去请求各个微服务客户端的接口。
注意上面所说的用户客户端可以理解为浏览器、app 端,微服务客户端就是我们系统中的例如订单服务、用户服务等微服务,认证服务端就是用来做认证授权的服务,相对于认证服务端来说,各个业务微服务也可以称作是它的客户端。
认证服务端配置
认证服务端继续用上一篇文章的配置,代码不需要任何改变,只需要在数据库里加一条记录,来支持新加的微服务客户端的认证
我们要创建的客户端的 client-id 为 code-client,client-secret 为 code-secret-8888,但是同样需要加密,可以用如下代码获取:
System.out.println(new BCryptPasswordEncoder().encode("code-secret-8888"));
除了以上这两个参数,要将 authorized_grant_types 设置为 authorization_code,refresh_token,web_server_redirect_uri 设置为回调地址,稍后微服务客户端会创建这个接口。
然后将这条记录组织好插入数据库中。
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('code-client', '$2a$10$jENDQZRtqqdr6sXGQK.L0OBADGIpyhtaRfaRDTeLKI76I/Ir1FDn6', 'all','authorization_code,refresh_token', 'http://localhost:6102/client-authcode/login', null, 3600, 36000, null, true);
创建授权模式的微服务
引入 maven 包
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>
<dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>3.14.2</version>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
引入 okhttp 和 thymeleaf 是因为要做一个简单的页面并模拟正常的认证过程。
配置文件 application.yml
spring:application:name: client-authcode
server:port: 6102servlet:context-path: /client-authcodesecurity:oauth2:client:client-id: code-clientclient-secret: code-secret-8888user-authorization-uri: http://localhost:6001/oauth/authorizeaccess-token-uri: http://localhost:6001/oauth/tokenresource:jwt:key-uri: http://localhost:6001/oauth/token_keykey-value: devauthorization:check-token-access: http://localhost:6001/oauth/check_token
**创建 resourceConfig **
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {@Beanpublic TokenStore jwtTokenStore() {return new JwtTokenStore(jwtAccessTokenConverter());}@Beanpublic JwtAccessTokenConverter jwtAccessTokenConverter() {JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();accessTokenConverter.setSigningKey("dev");accessTokenConverter.setVerifierKey("dev");return accessTokenConverter;}@Autowiredprivate TokenStore jwtTokenStore;@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {resources.tokenStore(jwtTokenStore);}@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/login").permitAll();}
}
使用 jwt 作为 token 的存储,注意允许 /login
接口无授权访问,这个地址是认证的回调地址,会返回 code 参数。
创建 application.java启动类
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}
到这步可以先停一下了。我们把认证服务端和刚刚创建的认证客户端启动起来,就可以手工测试一下了。回调接口不是还没创建呢吗,没关系,我们权当那个地址现在就是为了接收 code 参数的。
1、在浏览器访问 /oauth/authorize 授权接口,接口地址为:
http://localhost:6001/oauth/authorize?client_id=code-client&response_type=code&redirect_uri=http://localhost:6102/client-authcode/login
注意 response_type 参数设置为 code,redirect_uri 设置为数据库中插入的回调地址。
2、输入上面地址后,会自动跳转到认证服务端的登录页面,输入用户名、密码,这里用户名是 admin,密码是 123456
3、点击确定后,来到授权确认页面,页面上有 Authorize 和 Deny (授权和拒绝)两个按钮。可通过将 autoapprove 字段设置为 0 来取消此页面的展示,默认直接同意授权。
4、点击同意授权后,跳转到了回调地址,虽然是 404 ,但是我们只是为了拿到 code 参数,注意地址后面的 code 参数。
5、拿到这个 code 参数是为了向认证服务器 /oauth/token 接口请求 access_token ,继续用 REST Client 发送请求,同样的,你也可以用 postman 等工具测试。
注意 grant_type 参数设置为 authorization_code,code 就是上一步回调地址中加上的,redirect_uri 仍然要带上,回作为验证条件,如果不带或者与前面设置的不一致,会出现错误。
请求头 Authorization ,仍然是 Basic + 空格 + base64(client_id:client_secret),可以通过 https://www.sojson.com/base64.html 网站在线做 base64 编码。
code-client:code-secret-8888 通过 base64 编码后结果为 Y29kZS1jbGllbnQ6Y29kZS1zZWNyZXQtODg4OA==
POST http://localhost:6001/oauth/token?grant_type=authorization_code&client=code-client&code=BbCE34&redirect_uri=http://localhost:6102/client-authcode/login
Accept: */*
Cache-Control: no-cache
Authorization: Basic Y29kZS1jbGllbnQ6Y29kZS1zZWNyZXQtODg4OA==
发送请求后,返回的 json 内容如下:
{"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTU3MjYwMTMzMiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiI2OWRmY2M4Yy1iZmZiLTRiNDItYTZhZi1hN2IzZWUyZjI1ZTMiLCJjbGllbnRfaWQiOiJjb2RlLWNsaWVudCJ9.WlgGnBkNdg2PwKqjbZWo6QmUmq0QluZLgIWJXaZahSU","token_type": "bearer","refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsImp3dC1leHQiOiJKV1Qg5omp5bGV5L-h5oGvIiwic2NvcGUiOlsiYWxsIl0sImF0aSI6IjY5ZGZjYzhjLWJmZmItNGI0Mi1hNmFmLWE3YjNlZTJmMjVlMyIsImV4cCI6MTU3MjYzMzczMiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiJdLCJqdGkiOiJkNzk2OWRhMS04NTg4LTQ2YzMtYjdlNS1jMGM5NzcxNTM5Y2YiLCJjbGllbnRfaWQiOiJjb2RlLWNsaWVudCJ9.TEz0pQOhST9-ozdoJWm6cf1SoWvPC6W-5JW9yjZJXek","expires_in": 3599,"scope": "all","jwt-ext": "JWT 扩展信息","jti": "69dfcc8c-bffb-4b42-a6af-a7b3ee2f25e3"
}
和上一篇文章 password 模式拿到的 token 内容是一致的,接下来的请求都需要带上 access_token 。
6、把获取到的 access_token 代入到下面的请求中 ${access_token} 的位置,就可以请求微服务中的需要授权访问的接口了。
GET http://localhost:6102/client-authcode/get
Accept: */*
Cache-Control: no-cache
Authorization: bearer ${access_token}
接口内容如下:
@org.springframework.web.bind.annotation.ResponseBody
@GetMapping(value = "get")
@PreAuthorize("hasAnyRole('ROLE_ADMIN')")
public Object get(Authentication authentication)
{//Authentication authentication = SecurityContextHolder.getContext().getAuthentication();authentication.getCredentials();OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();String token = details.getTokenValue();return token;
}
经过以上的手工测试,证明此过程是通的,但是还没有达到自动化。如果你集成过微信登录,那你一定知道我们在回调地址中做了什么,拿到返回的 code 参数去 token 接口换取 access_token 对不对,没错,思路都是一样的,我们的回调接口中同样要拿 code 去换取 access_token。
为此,我做了一个简单的页面,并且在回调接口中请求获取 token 的接口。
创建简单的登录页面
在 resources 目录下创建 templates 目录,用来存放 thymeleaf 的模板,不做样式,只做最简单的演示,创建 index.html 模板,内容如下:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>古时的风筝-OAuth2 Client</title>
</head>
<body>
<div><a href="http://localhost:6001/oauth/authorize?client_id=code-client&response_type=code&redirect_uri=http://localhost:6102/client-authcode/login">登录</a><span th:text="'当前认证用户:' + ${username}"></span><span th:text="${accessToken}"></span>
</div>
</body>
</html>
回调接口及其他接口
@Slf4j
@Controller
public class CodeClientController {/*** 用来展示index.html 模板* @return*/ @GetMapping(value = "index")public String index(){return "index";}@GetMapping(value = "login")public Object login(String code,Model model) {String tokenUrl = "http://localhost:6001/oauth/token";OkHttpClient httpClient = new OkHttpClient();RequestBody body = new FormBody.Builder().add("grant_type", "authorization_code").add("client", "code-client").add("redirect_uri","http://localhost:6102/client-authcode/login").add("code", code).build();Request request = new Request.Builder().url(tokenUrl).post(body).addHeader("Authorization", "Basic Y29kZS1jbGllbnQ6Y29kZS1zZWNyZXQtODg4OA==").build();try {Response response = httpClient.newCall(request).execute();String result = response.body().string();ObjectMapper objectMapper = new ObjectMapper();Map tokenMap = objectMapper.readValue(result,Map.class);String accessToken = tokenMap.get("access_token").toString();Claims claims = Jwts.parser().setSigningKey("dev".getBytes(StandardCharsets.UTF_8)).parseClaimsJws(accessToken).getBody();String userName = claims.get("user_name").toString();model.addAttribute("username", userName);model.addAttribute("accessToken", result);return "index";} catch (Exception e) {e.printStackTrace();}return null;}@org.springframework.web.bind.annotation.ResponseBody@GetMapping(value = "get")@PreAuthorize("hasAnyRole('ROLE_ADMIN')")public Object get(Authentication authentication) {//Authentication authentication = SecurityContextHolder.getContext().getAuthentication();authentication.getCredentials();OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();String token = details.getTokenValue();return token;}
}
其中 index() 方法是为了展示 thymeleaf 模板,login 方法就是回调接口,这里用了 okhttp3 用作接口请求,请求认证服务端的 /oauth/token 接口来换取 access_token,只是把我们手工测试的步骤自动化了。
访问 index.html 页面
我们假设这个页面就是一个网站的首页,未登录的用户会在网站上看到登录按钮,我们访问这个页面:http://localhost:6102/client-authcode/index,看到的页面是这样的
接下来,点击登录按钮,通过上面的模板代码看出,点击后其实就是跳转到了我们手工测试第一步访问的那个地址,之后的操作和上面手工测试的是一致的,输入用户名密码、点击同意授权。
接下来,页面跳转回回调地址<http://localhost:6102/client-authcode/login?code=xxx 的时候,login 方法拿到 code 参数,开始构造 post 请求体,并把 Authorization 加入请求头,然后请求 oauth/token 接口,最后将拿到的 token 和 通过 token 解析后的 username 返回给前端,最后呈现的效果如下:
最后,拿到 token 后的客户端,就可以将 token 加入到请求头后,去访问需要授权的接口了。
结合上一篇文章,我们就实现了 password 和 授权码两种模式的 oauth2 认证。
本篇源码微服务客户端对应的源码地址为: 点击查看 github 源码
面试官:能说一说微信授权的原理吗?(Spring Cloud OAuth2 授权码模式)相关推荐
- java调用授权接口oauth2_微信授权就是这个原理,Spring Cloud OAuth2 授权码模式
上一篇文章Spring Cloud OAuth2 实现单点登录介绍了使用 password 模式进行身份认证和单点登录.本篇介绍 Spring Cloud OAuth2 的另外一种授权模式-授权码模式 ...
- 原创 | 我被面试官给虐懵了,竟然是因为我不懂Spring中的@Configuration
这是Hollis的第 216 篇原创分享 作者 l cxuan 来源 l Hollis(ID:hollischuang) 现在大部分的Spring项目都采用了基于注解的配置,采用了@Configura ...
- 我被面试官给虐懵了,竟然是因为我不懂Spring中的@Configuration
作者 l cxuan 来源 l Hollis(ID:hollischuang) 现在大部分的Spring项目都采用了基于注解的配置,采用了@Configuration 替换标签的做法.一行简单的注解就 ...
- 简述ip地址的abc类如何划分_面试官问:讲讲IP地址的分配原理
网络模型介绍 在计算机网络中有著名的OSI七层协议体系结构,概念清楚,理论完整,但是它既复杂又不实用.TCP/IP体系结构则不同,得到了广泛的应用.最终结合OSI和TCP/IP的优点,采用了一种只有五 ...
- Spring Security Oauth2 授权码模式下 自定义登录、授权页面
主要说明:基于若依springcloud微服务框架的2.1版本 嫌弃缩进不舒服的,直接访问我的博客站点: http://binarydance.top//aticle_view.html?aticle ...
- spring cloud oauth2系列篇(二)深入authorization_code授权码模式完整实现
项目的源码地址:https://github.com/daxian-zhu/online_edu 项目是最新的,文章可能不是最新的 这里先简单的介绍项目,不然有的地方可能我表达不清楚容易造成误解 on ...
- 将Spring Security OAuth2授权服务JWK与Consul 配置中心结合使用
将Spring Security OAuth2授权服务JWK与Consul 配置中心结合使用 概述 在前文中介绍了OAuth2授权服务简单的实现密钥轮换,与其不同,本文将通过Consul实现我们的目的 ...
- Spring Security OAuth2 授权码模式 (Authorization Code)
前言 Spring Security OAuth2 授权码模式 (Authorization Code) 应该是授权登录的一个行业标准 整体流程 首先在平台注册获取CLIENT_ID和CLIENT_S ...
- 面试官:你能说说MyBatis拦截器原理吗?
点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:Format cnblogs.com/fangjian042 ...
最新文章
- return false
- 如何在AngularJS的ng-options中设置value属性?
- ks检验正态分布结果_统计学里的数据正态性检验
- Android C2DM学习 - 云端推送
- 08_clickhouse主键/索引的工作机制(MergeTree的稀疏索引、索引的生成过程、索引的查询),数据标记的工作机制(数据存储、数据标记、数据查询、数据查询示例)(学习笔记)
- 高考完?入门级的开源项目带你开启编程之旅
- Nginx特性验证-反向代理/负载均衡/页面缓存/URL重定向
- cocos怎么把res文件夹放服务器上,cocos2d 三合一跑胡子房卡+服务器组件+后台控制+安装教程+棋牌完整源码...
- 登录页面(通过数据库查询密码是否正确)
- [原]sencha touch之carousel
- lua与python结合_从Python到Lua
- 分布式事务控制解决方案
- 虚拟机中部署ISA Server 2006 防火墙 网络设置(上)
- pulseaudio数据流框图
- 正态分布的前世今生:最小二乘法
- 团队项目—第二阶段第五天
- ORB-SLAM 解读(五) 地图点投影进行特征匹配
- 经典的Java算法面试题
- 编译原理用C语言编写递归下降分析程序,编译原理递归下降分析器(C语言).pdf
- pycharm搭建yolo3-pytorch-master的pytorch环境
热门文章
- ubuntu下安装telnet服务
- 机器学习可视化技术(Towards Data Science)
- 盘点!一篇看完2018年中国各地编程教育政策
- 【DP】CF940E Cashback
- 伊利诺理工大学计算机专业,美国伊利诺伊理工大学世界排名_专业_申请条件-PSONE品思...
- value.charAt(i)和value.slice(i)的理解
- (SVN+SSH)搭建SVN并使用SSH进行免密拉取推送代码
- MATLAB学习之数列极限(一)
- 一种效果很好的自动白平衡技术(WhiteBalance)
- C++ - 多态(2) | 虚表的打印、单继承与多继承的虚表