博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Oauth2方式实现单点登录
阅读量:4292 次
发布时间:2019-05-27

本文共 9163 字,大约阅读时间需要 30 分钟。

下面先简单介绍一下Oauth2的原理

Oauth2是什么?

Oauth2是一种授权机制,用来授权给第三方应用,获取用户数据。

这是阮一峰老师的解释,因此解释的比较好了,就不在此重复了。

Oauth2 的原理

Oauth2 有四种授权模型 授权码,隐藏式,密码式,凭证式 目前主流的形式是授权码方式。 我们项目中使用的也是授权码方式。 这里就只介绍一下授权码方式 。

授权码方式

这块的内容完全是引用于 只是为了方便大家截阅读不用在来回跳转。 其他的几种方式可以去这篇文章中查看。

授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。

这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。

第一步,A 网站提供一个链接,用户点击后就会跳转到 B 网站,授权用户数据给 A 网站使用。下面就是 A 网站跳转 B 网站的一个示意链接。

https://b.com/oauth/authorize?  response_type=code&  client_id=CLIENT_ID&  redirect_uri=CALLBACK_URL&  scope=read

上面 URL 中,response_type参数表示要求返回授权码(code),client_id参数让 B 知道是谁在请求,redirect_uri参数是 B 接受或拒绝请求后的跳转网址,scope参数表示要求的授权范围(这里是只读)。

在这里插入图片描述

第二步,用户跳转后,B 网站会要求用户登录,然后询问是否同意给予 A 网站授权。用户表示同意,这时 B 网站就会跳回redirect_uri参数指定的网址。跳转时,会传回一个授权码,就像下面这样。

https://a.com/callback?code=AUTHORIZATION_CODE
上面 URL 中,code参数就是授权码。
在这里插入图片描述

第三步,A 网站拿到授权码以后,就可以在后端,向 B 网站请求令牌。

https://b.com/oauth/token? client_id=CLIENT_ID& client_secret=CLIENT_SECRET& grant_type=authorization_code& code=AUTHORIZATION_CODE& redirect_uri=CALLBACK_URL

上面 URL 中,client_id参数和client_secret参数用来让 B 确认 A 的身份(client_secret参数是保密的,因此只能在后端发请求),grant_type参数的值是AUTHORIZATION_CODE,表示采用的授权方式是授权码,code参数是上一步拿到的授权码,redirect_uri参数是令牌颁发后的回调网址。

在这里插入图片描述

第四步,B 网站收到请求以后,就会颁发令牌。具体做法是向redirect_uri指定的网址,发送一段 JSON 数据。

{
"access_token":"ACCESS_TOKEN", "token_type":"bearer", "expires_in":2592000, "refresh_token":"REFRESH_TOKEN", "scope":"read", "uid":100101, "info":{
...}}

上面 JSON 数据中,access_token字段就是令牌,A 网站在后端拿到了。

在这里插入图片描述

单点登录的实现

下面我们就要开始动手了。

第一步,配置哪些应用可以到我们服务器中来获取认证。

可以看下图里面的关键往上,clientId,redirectUri 就是我们在上面的原理介绍中包括的, 这里还有一个关键的信息 就是clientSecret 。 因此我们不能随便让人拿到clientId 就能来我们服务器中认证。 我们需要一点案例机制。就是生成一个密钥,告诉第三方的应用,让他请求授权的时候,把这个密钥带上,否则就是非法请求。 其他的参数只是为了管理方便使用。

在这里插入图片描述

第二步: 获取授权码

下面可以看看真实的请求路径

http://127.0.0.1:9999/tboot/oauth2/authorize?username=superman&password=123456&code=560p&client_id=1287990317873762304&redirect_uri=http:%2F%2F127.0.0.1:10001%2F&state=1234

下面看看java代码

@RequestMapping(value = "/authorize", method = RequestMethod.GET)    @ApiOperation(value = "认证获取code")    public Result authorize(@ApiParam("用户名") @RequestParam String username,                                    @ApiParam("密码") @RequestParam String password,                                    @ApiParam("客户端id") @RequestParam String client_id,                                    @ApiParam("成功授权后回调地址") @RequestParam String redirect_uri,                                    @ApiParam("授权类型为code") @RequestParam(required = false, defaultValue = "code") String response_type,                                    @ApiParam("客户端状态值") @RequestParam String state                                   ){
Client client = clientService.getById(client_id); if(client==null){
return ResultUtil.error("客户端client_id不存在"); } User user = userService.findByUsername(username); if(user==null){
return ResultUtil.error("用户名不存在"); } if(!new BCryptPasswordEncoder().matches(password, user.getPassword())){
return ResultUtil.error("用户密码不正确"); } // 判断回调地址 if(!client.getRedirectUri().equals(redirect_uri)){
return ResultUtil.error("回调地址redirect_uri不正确"); } // 生成code 5分钟内有效 String code = UUID.randomUUID().toString().replace("-", ""); // 存入用户及clientId信息 redisTemplate.opsForValue().set("oauthCode:"+code, new Gson().toJson(new Oauth2TokenInfo(client_id, username)), 5L, TimeUnit.MINUTES); Map
map = new HashMap<>(16); map.put("code", code); map.put("redirect_uri", redirect_uri); map.put("state", state); return ResultUtil.data(map); }

第三步: 获取token

这样就返回了授权码,然后夺通过 授权码获取accessToken就算完成了。

查看请求路径

http://127.0.0.1:10001/tboot/oauth2/token?code=3838482b50b1447e81cbf9e52403f629&response_type=code&grant_type=authorization_code&client_id=1287990317873762304&client_secret=f3ede985786a40c2b0d7c341c83af4f3&redirect_uri=http:%2F%2F127.0.0.1:10001%2F

查看java代码

@RequestMapping(value = "/token", method = RequestMethod.GET)    @ApiOperation(value = "获取accessToken令牌")    public Result token(@ApiParam("授权类型") @RequestParam String grant_type,                                @ApiParam("客户端id") @RequestParam String client_id,                                @ApiParam("客户端秘钥") @RequestParam String client_secret,                                @ApiParam("认证返回的code") @RequestParam(required = false) String code,                                @ApiParam("刷新token") @RequestParam(required = false) String refresh_token,                                @ApiParam("成功授权后回调地址") @RequestParam(required = false) String redirect_uri){
Client client = clientService.getById(client_id); if(client==null){
return ResultUtil.error("客户端client_id不存在"); } // 判断clientSecret if(!client.getClientSecret().equals(client_secret)){
return ResultUtil.error("client_secret不正确"); } Oauth2TokenInfo tokenInfo = null; if("authorization_code".equals(grant_type)){
// 判断回调地址 if(!client.getRedirectUri().equals(redirect_uri)){
return ResultUtil.error("回调地址redirect_uri不正确"); } // 判断code 获取用户信息 String codeValue = redisTemplate.opsForValue().get("oauthCode:"+code); if(StrUtil.isBlank(codeValue)){
return ResultUtil.error("code已过期"); } tokenInfo = new Gson().fromJson(codeValue, Oauth2TokenInfo.class); if(!tokenInfo.getClientId().equals(client_id)){
return ResultUtil.error("code不正确"); } } else if ("refresh_token".equals(grant_type)){
// 从refreshToken中获取用户信息 String refreshTokenValue = redisTemplate.opsForValue().get("oauthTokenInfo:"+refresh_token); if(StrUtil.isBlank(refreshTokenValue)){
return ResultUtil.error("refresh_token已过期"); } tokenInfo = new Gson().fromJson(refreshTokenValue, Oauth2TokenInfo.class); if(!tokenInfo.getClientId().equals(client_id)){
return ResultUtil.error("refresh_token不正确"); } } else {
return ResultUtil.error("授权类型grant_type不正确"); } String token = null, refreshToken = null; Long expiresIn = null; String tokenKey = "oauthToken:"+client_id+":"+tokenInfo.getUsername(), refreshKey = "oauthRefreshToken:"+client_id+":"+tokenInfo.getUsername(); if("authorization_code".equals(grant_type)){
// 生成token模式 String oldToken = redisTemplate.opsForValue().get(tokenKey); String oldRefreshToken = redisTemplate.opsForValue().get(refreshKey); if(StrUtil.isNotBlank(oldToken)&&StrUtil.isNotBlank(oldRefreshToken)){
// 旧token token = oldToken; refreshToken = oldRefreshToken; expiresIn = redisTemplate.getExpire("oauthTokenInfo:"+token, TimeUnit.SECONDS); } else {
// 新生成 30天过期 String newToken = UUID.randomUUID().toString().replace("-", ""); String newRefreshToken = UUID.randomUUID().toString().replace("-", ""); redisTemplate.opsForValue().set(tokenKey, newToken, 30L, TimeUnit.DAYS); redisTemplate.opsForValue().set(refreshKey, newRefreshToken, 30L, TimeUnit.DAYS); // 新token中存入用户信息 redisTemplate.opsForValue().set("oauthTokenInfo:"+newToken, new Gson().toJson(tokenInfo),30L, TimeUnit.DAYS); redisTemplate.opsForValue().set("oauthTokenInfo:"+newRefreshToken, new Gson().toJson(tokenInfo),30L, TimeUnit.DAYS); token = newToken; refreshToken = newRefreshToken; expiresIn = redisTemplate.getExpire(token, TimeUnit.SECONDS); } } else if("refresh_token".equals(grant_type)) {
// 刷新token模式 生成新token 30天过期 String newToken = UUID.randomUUID().toString().replace("-", ""); String newRefreshToken = UUID.randomUUID().toString().replace("-", ""); redisTemplate.opsForValue().set(tokenKey, newToken, 30L, TimeUnit.DAYS); redisTemplate.opsForValue().set(refreshKey, newRefreshToken, 30L, TimeUnit.DAYS); // 新token中存入用户信息 redisTemplate.opsForValue().set("oauthTokenInfo:"+newToken, new Gson().toJson(tokenInfo),30L, TimeUnit.DAYS); redisTemplate.opsForValue().set("oauthTokenInfo:"+newRefreshToken, new Gson().toJson(tokenInfo),30L, TimeUnit.DAYS); token = newToken; refreshToken = newRefreshToken; expiresIn = redisTemplate.getExpire("oauthTokenInfo:"+token, TimeUnit.SECONDS); // 旧refreshToken过期 redisTemplate.delete("oauthTokenInfo:"+refresh_token); } Map
map = new HashMap<>(16); map.put("access_token", token); map.put("expires_in", expiresIn); map.put("refresh_token", refreshToken); return ResultUtil.data(map); }

就可以获取相当的信息在这里插入图片描述

在通过 accessTonken 就可去资源服务器去获取相应的用户信息了。

重要

交个朋友吧

在这里插入图片描述

转载地址:http://ickws.baihongyu.com/

你可能感兴趣的文章
docker安装 rabbitMq
查看>>
git 常用命令 入门
查看>>
linux安装docker
查看>>
关闭selinx nginx无法使用代理
查看>>
shell 脚本部署项目
查看>>
spring cloud zuul网关上传大文件
查看>>
springboot+mybatis日志显示SQL
查看>>
工作流中文乱码问题解决
查看>>
maven打包本地依赖包
查看>>
spring boot jpa 实现拦截器
查看>>
jenkins + maven+ gitlab 自动化部署
查看>>
Pull Request流程
查看>>
Lambda 表达式
查看>>
函数式数据处理(一)--流
查看>>
java 流使用
查看>>
java 用流收集数据
查看>>
java并行流
查看>>
CompletableFuture 组合式异步编程
查看>>
mysql查询某一个字段是否包含中文字符
查看>>
Java中equals和==的区别
查看>>