JustAuth升级到v1.8.1版本,新增AuthState工具类,可自动生成state

本文主要是介绍JustAuth升级到v1.8.1版本,新增AuthState工具类,可自动生成state,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

JustAuth(gitee | github),如你所见,它仅仅是一个第三方授权登录的工具类库,它可以让我们脱离繁琐的第三方登录SDK,让登录变得So easy!

JustAuth的功能

史上最全的整合第三方登录的工具,目前已支持Github、Gitee、微博、钉钉、百度、Coding、腾讯云开发者平台、OSChina、支付宝、QQ、微信、淘宝、Google、Facebook、抖音、领英、小米、微软和今日头条等第三方平台的授权登录。 Login, so easy!

JustAuth特点

废话不多说,就俩字:

  1. :已集成十多家第三方平台(国内外常用的基本都已包含),后续依然还有扩展计划!
  2. :API就是奔着最简单去设计的(见后面快速开始),尽量让您用起来没有障碍感!

JustAuth(gitee | github)今日升级到1.8.1版本,新增了AuthState工具类,支持根据source自动生成state

AuthState.java

package me.zhyd.oauth.utils;import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.exception.AuthException;
import me.zhyd.oauth.request.ResponseStatus;import java.nio.charset.Charset;
import java.util.concurrent.ConcurrentHashMap;/*** state工具,负责创建、获取和删除state** @author yadong.zhang (yadong.zhang0415(a)gmail.com)* @version 1.0* @since 1.8*/
@Slf4j
public class AuthState {/*** 空字符串*/private static final String EMPTY_STR = "";/*** state存储器*/private static ConcurrentHashMap<String, String> stateBucket = new ConcurrentHashMap<>();/*** 生成随机的state** @param source oauth平台* @return state*/public static String create(String source) {return create(source, RandomUtil.randomString(4));}/*** 创建state** @param source oauth平台* @param body   希望加密到state的消息体* @return state*/public static String create(String source, Object body) {return create(source, JSON.toJSONString(body));}/*** 创建state** @param source oauth平台* @param body   希望加密到state的消息体* @return state*/public static String create(String source, String body) {String currentIp = getCurrentIp();String simpleKey = ((source + currentIp));String key = Base64.encode(simpleKey.getBytes(Charset.forName("UTF-8")));log.debug("Create the state: ip={}, platform={}, simpleKey={}, key={}, body={}", currentIp, source, simpleKey, key, body);if (stateBucket.containsKey(key)) {log.debug("Get from bucket: {}", stateBucket.get(key));return stateBucket.get(key);}String simpleState = source + "_" + currentIp + "_" + body;String state = Base64.encode(simpleState.getBytes(Charset.forName("UTF-8")));log.debug("Create a new state: {}", state, simpleState);stateBucket.put(key, state);return state;}/*** 获取state** @param source oauth平台* @return state*/public static String get(String source) {String currentIp = getCurrentIp();String simpleKey = ((source + currentIp));String key = Base64.encode(simpleKey.getBytes(Charset.forName("UTF-8")));log.debug("Get state by the key[{}], current ip[{}]", key, currentIp);return stateBucket.get(key);}/*** 获取state中保存的body内容** @param source oauth平台* @param state  加密后的state* @param clazz  body的实际类型* @param <T>    需要转换的具体的class类型* @return state*/public static <T> T getBody(String source, String state, Class<T> clazz) {if (StringUtils.isEmpty(state) || null == clazz) {return null;}log.debug("Get body from the state[{}] of the {} and convert it to {}", state, source, clazz.toString());String currentIp = getCurrentIp();String decodedState = Base64.decodeStr(state);log.debug("The decoded state is [{}]", decodedState);if (!decodedState.startsWith(source)) {return null;}String noneSourceState = decodedState.substring(source.length() + 1);if (!noneSourceState.startsWith(currentIp)) {// ip不相同,可能为非法的请求throw new AuthException(ResponseStatus.ILLEGAL_REQUEST);}String body = noneSourceState.substring(currentIp.length() + 1);log.debug("body is [{}]", body);if (clazz == String.class) {return (T) body;}if (clazz == Integer.class) {return (T) Integer.valueOf(Integer.parseInt(body));}if (clazz == Long.class) {return (T) Long.valueOf(Long.parseLong(body));}if (clazz == Short.class) {return (T) Short.valueOf(Short.parseShort(body));}if (clazz == Double.class) {return (T) Double.valueOf(Double.parseDouble(body));}if (clazz == Float.class) {return (T) Float.valueOf(Float.parseFloat(body));}if (clazz == Boolean.class) {return (T) Boolean.valueOf(Boolean.parseBoolean(body));}if (clazz == Byte.class) {return (T) Byte.valueOf(Byte.parseByte(body));}return JSON.parseObject(body, clazz);}/*** 登录成功后,清除state** @param source oauth平台*/public static void delete(String source) {String currentIp = getCurrentIp();String simpleKey = ((source + currentIp));String key = Base64.encode(simpleKey.getBytes(Charset.forName("UTF-8")));log.debug("Delete used state[{}] by the key[{}], current ip[{}]", stateBucket.get(key), key, currentIp);stateBucket.remove(key);}private static String getCurrentIp() {String currentIp = IpUtils.getIp();return StringUtils.isEmpty(currentIp) ? EMPTY_STR : currentIp;}
}

AuthState基本用法

工具类的基本用法如下:

@Test
public void usage() {String source = "github";System.out.println("\nstep1 生成state: 预期创建一个新的state...");String state = AuthState.create(source);System.out.println(state);System.out.println("\nstep2 重复生成state: 预期从bucket中返回一个可用的state...");String recreateState = AuthState.create(source);System.out.println(recreateState);Assert.assertEquals(state, recreateState);System.out.println("\nstep3 获取state: 预期获取上面生成的state...");String stateByBucket = AuthState.get(source);System.out.println(stateByBucket);Assert.assertEquals(state, stateByBucket);System.out.println("\nstep4 删除state: 预期删除掉上面创建的state...");AuthState.delete(source);System.out.println("\nstep5 重新获取state: 预期返回null...");String deletedState = AuthState.get(source);System.out.println(deletedState);Assert.assertNull(deletedState);
}

输出结果:

step1 生成state: 预期创建一个新的state...
Z2l0aHViXzE5Mi4xNjguMTkuMV9lemV5step2 重复生成state: 预期从bucket中返回一个可用的state...
Z2l0aHViXzE5Mi4xNjguMTkuMV9lemV5step3 获取state: 预期获取上面生成的state...
Z2l0aHViXzE5Mi4xNjguMTkuMV9lemV5step4 删除state: 预期删除掉上面创建的state...step5 重新获取state: 预期返回null...
null

基础版的创建state的方法,适用于对state无复杂要求,只做简单验证时使用,内部通过source_ip_randomstr的方式生成state

AuthState扩展用法

到这一步,可能有朋友会觉得,这功能太鸡肋了,限制太大了,只能生成固定格式的随机字符串,没法利用state做一些复杂的业务操作。我只想说,“莫慌~~”,这才哪到哪。
如果需要对state做特殊处理,比如要在state中保存当前用户信息(id)、当前页面的标识(bind-oauth)等更多的特殊参数时,可以使用以下方法:

  • create(String source, String body)
public static String create(String source, String body) {return xx;
}

这个方法接收一个string类型的变量, 可以传入复杂的字符串类型, 也可以自己封装数据后转成字符串,然后调用这个接口。当然,针对非字符串的特殊结构参数,可以使用下面的方法。

  • create(String source, Object body)
public static String create(String source, Object body) {return create(source, JSON.toJSONString(body));
}

这个方法接收一个Object类型的变量,支持任意一种数据类型,可以传入map、list、实体类等复杂结构的参数。

接下来演示一下通过AuthState生成随机的、指定字符串的或者指定任意内容的state字符串。用法如下:

@Test
public void create() {String source = "github";System.out.println("\n通过随机字符串生成state...");String state = AuthState.create(source);System.out.println(state);AuthState.delete(source);System.out.println("\n通过传入自定义的字符串生成state...");String stringBody = "这是一个字符串";String stringState = AuthState.create(source, stringBody);System.out.println(stringState);AuthState.delete(source);System.out.println("\n通过传入数字生成state...");Integer numberBody = 111;String numberState = AuthState.create(source, numberBody);System.out.println(numberState);AuthState.delete(source);System.out.println("\n通过传入日期生成state...");Date dateBody = DateUtil.parse("2019-01-01 12:12:12", DatePattern.NORM_DATETIME_PATTERN);String dateState = AuthState.create(source, dateBody);System.out.println(dateState);AuthState.delete(source);System.out.println("\n通过传入map生成state...");Map<String, Object> mapBody = new HashMap<>();mapBody.put("userId", 1);mapBody.put("userToken", "xxxxx");String mapState = AuthState.create(source, mapBody);System.out.println(mapState);AuthState.delete(source);System.out.println("\n通过传入List生成state...");List<String> listBody = new ArrayList<>();listBody.add("xxxx");listBody.add("xxxxxxxx");String listState = AuthState.create(source, listBody);System.out.println(listState);AuthState.delete(source);System.out.println("\n通过传入实体类生成state...");AuthConfig entityBody = AuthConfig.builder().clientId("xxxxx").clientSecret("xxxxx").build();String entityState = AuthState.create(source, entityBody);System.out.println(entityState);AuthState.delete(source);
}

输出结果:

通过随机字符串生成state...
Z2l0aHViXzE5Mi4xNjguMTkuMV81Y3pz通过传入自定义的字符串生成state...
Z2l0aHViXzE5Mi4xNjguMTkuMV/ov5nmmK/kuIDkuKrlrZfnrKbkuLI=通过传入数字生成state...
Z2l0aHViXzE5Mi4xNjguMTkuMV8xMTE=通过传入日期生成state...
Z2l0aHViXzE5Mi4xNjguMTkuMV8xNTQ2MzE1OTMyMDAw通过传入map生成state...
Z2l0aHViXzE5Mi4xNjguMTkuMV97InVzZXJUb2tlbiI6Inh4eHh4IiwidXNlcklkIjoxfQ==通过传入List生成state...
Z2l0aHViXzE5Mi4xNjguMTkuMV9bInh4eHgiLCJ4eHh4eHh4eCJd通过传入实体类生成state...
Z2l0aHViXzE5Mi4xNjguMTkuMV97ImNsaWVudElkIjoieHh4eHgiLCJjbGllbnRTZWNyZXQiOiJ4eHh4eCIsInVuaW9uSWQiOmZhbHNlfQ==

怎么样? 是不是很爽?想往state中插入任何数据都可以。但是有些朋友可能又会说了:你这只生成了state,但是我怎么获取到我传入的具体的body内容?我就算生成的,但是我用不到这个数据,一样是“脱了裤子放屁 ——多此一举”。

咳~~ 还是那俩字:“莫慌~~”

获取自定的body

前面介绍到了, 可以通过create(String source, String body)或者create(String source, Object body)生成复杂类型的state,那么如何从encode之后的state中获取到定制的body内容呢?

AuthState提供了获取body内容的方法:

/*** 获取state中保存的body内容** @param source oauth平台* @param state  加密后的state* @param clazz  body的实际类型* @param <T>    需要转换的具体的class类型* @return state*/
public static <T> T getBody(String source, String state, Class<T> clazz) {...
}

方法接收三个参数,参数的作用如上注释,只要是按照规定规则({source}_{ip}_{body})生成的state,都可以通过getBody方法解析出body内容。

示例代码:

@Test
public void getBody() {String source = "github";System.out.println("\n通过随机字符串生成state...");String state = AuthState.create(source);System.out.println(state);String body = AuthState.getBody(source, state, String.class);System.out.println(body);AuthState.delete(source);System.out.println("\n通过传入自定义的字符串生成state...");String stringBody = "这是一个字符串";String stringState = AuthState.create(source, stringBody);System.out.println(stringState);stringBody = AuthState.getBody(source, stringState, String.class);System.out.println(stringBody);AuthState.delete(source);System.out.println("\n通过传入数字生成state...");Integer numberBody = 111;String numberState = AuthState.create(source, numberBody);System.out.println(numberState);numberBody = AuthState.getBody(source, numberState, Integer.class);System.out.println(numberBody);AuthState.delete(source);System.out.println("\n通过传入日期生成state...");Date dateBody = DateUtil.parse("2019-01-01 12:12:12", DatePattern.NORM_DATETIME_PATTERN);String dateState = AuthState.create(source, dateBody);System.out.println(dateState);dateBody = AuthState.getBody(source, dateState, Date.class);System.out.println(dateBody);AuthState.delete(source);System.out.println("\n通过传入map生成state...");Map<String, Object> mapBody = new HashMap<>();mapBody.put("userId", 1);mapBody.put("userToken", "xxxxx");String mapState = AuthState.create(source, mapBody);System.out.println(mapState);mapBody = AuthState.getBody(source, mapState, Map.class);System.out.println(mapBody);AuthState.delete(source);System.out.println("\n通过传入List生成state...");List<String> listBody = new ArrayList<>();listBody.add("xxxx");listBody.add("xxxxxxxx");String listState = AuthState.create(source, listBody);System.out.println(listState);listBody = AuthState.getBody(source, listState, List.class);System.out.println(listBody);AuthState.delete(source);System.out.println("\n通过传入实体类生成state...");AuthConfig entityBody = AuthConfig.builder().clientId("xxxxx").clientSecret("xxxxx").build();String entityState = AuthState.create(source, entityBody);System.out.println(entityState);entityBody = AuthState.getBody(source, entityState, AuthConfig.class);System.out.println(entityBody);AuthState.delete(source);
}

运行结果:

通过随机字符串生成state...
Z2l0aHViXzE5Mi4xNjguMTkuMV9maXBo
fiph通过传入自定义的字符串生成state...
Z2l0aHViXzE5Mi4xNjguMTkuMV/ov5nmmK/kuIDkuKrlrZfnrKbkuLI=
这是一个字符串通过传入数字生成state...
Z2l0aHViXzE5Mi4xNjguMTkuMV8xMTE=
111通过传入日期生成state...
Z2l0aHViXzE5Mi4xNjguMTkuMV8xNTQ2MzE1OTMyMDAw
Tue Jan 01 12:12:12 CST 2019通过传入map生成state...
Z2l0aHViXzE5Mi4xNjguMTkuMV97InVzZXJUb2tlbiI6Inh4eHh4IiwidXNlcklkIjoxfQ==
{userToken=xxxxx, userId=1}通过传入List生成state...
Z2l0aHViXzE5Mi4xNjguMTkuMV9bInh4eHgiLCJ4eHh4eHh4eCJd
[xxxx, xxxxxxxx]通过传入实体类生成state...
Z2l0aHViXzE5Mi4xNjguMTkuMV97ImNsaWVudElkIjoieHh4eHgiLCJjbGllbnRTZWNyZXQiOiJ4eHh4eCIsInVuaW9uSWQiOmZhbHNlfQ==
me.zhyd.oauth.config.AuthConfig@725bef66

如上,通过getBody获取到具体的body数据后,调用方可以根据自己的规则去校验state或者做其他逻辑操作。

作者:咋样? 这回没有问题了吧?
刺头:切,你有能耐让代码按照我的意念生成任意格式的state?
作者:噗~~(一口老痰喷他一脸)

How to use?

前面主要通过源码+示例解释了AuthState的用法,那么我们在实际使用JustAuth时,只需要将state参数改为以下方式即可:

new AuthGithubRequest(AuthConfig.builder().clientId("xx").clientSecret("xx").redirectUri("https://www.zhyd.me/oauth/callback/github").state(AuthState.create(source)) // 1.8.1版本提供的生成state的方法 .build());

注意:授权登录后,需要手动清除本次请求流程中生成的state

AuthState.delete(source);

通过JustAuth-demo运行测试后的输出内容:

进入render:github
IP:192.168.19.1
2019-07-15 19:10:59 [me.zhyd.oauth.utils.AuthState:65] DEBUG - Create the state: ip=192.168.19.1, platform=github, simpleKey=github192.168.19.1, key=Z2l0aHViMTkyLjE2OC4xOS4x, body=n8gn
2019-07-15 19:10:59 [me.zhyd.oauth.utils.AuthState:74] DEBUG - Create a new state: Z2l0aHViXzE5Mi4xNjguMTkuMV9uOGdu
https://github.com/login/oauth/authorize?client_id=xxx&amp;redirect_uri=http://dblog-web.zhyd.me/oauth/callback/github&amp;state=Z2l0aHViXzE5Mi4xNjguMTkuMV9uOGdu
进入callback:github callback params:{"code":"609c1df58e66da9eb5c6","state":"Z2l0aHViXzE5Mi4xNjguMTkuMV9uOGdu"}
2019-07-15 19:11:00 [me.zhyd.oauth.utils.AuthState:106] DEBUG - Get body from the state[Z2l0aHViXzE5Mi4xNjguMTkuMV9uOGdu] of the github and convert it to class java.lang.String
2019-07-15 19:11:00 [me.zhyd.oauth.utils.AuthState:109] DEBUG - The decoded state is [github_192.168.19.1_n8gn]
2019-07-15 19:11:00 [me.zhyd.oauth.utils.AuthState:119] DEBUG - body is [n8gn]
获取state中的body信息:n8gn
IP:192.168.19.1
2019-07-15 19:11:00 [me.zhyd.oauth.utils.AuthState:65] DEBUG - Create the state: ip=192.168.19.1, platform=github, simpleKey=github192.168.19.1, key=Z2l0aHViMTkyLjE2OC4xOS4x, body=2s6v
2019-07-15 19:11:00 [me.zhyd.oauth.utils.AuthState:68] DEBUG - Get from bucket: Z2l0aHViXzE5Mi4xNjguMTkuMV9uOGdu
{"code":2000,"data":{"avatar":"https://avatars3.githubusercontent.com/u/12689082?v=4","blog":"https://www.zhyd.me","company":"innodev","email":"yadong.zhang0415@gmail.com","gender":"UNKNOW","location":"Beijing","nickname":"yadong.zhang","remark":"心之所向,无所不能","source":"GITHUB","token":{"accessToken":"xx","expireIn":0},"username":"zhangyd-c","uuid":"xx"}}
2019-07-15 19:11:08 [me.zhyd.oauth.utils.AuthState:157] DEBUG - Delete used state[Z2l0aHViXzE5Mi4xNjguMTkuMV9uOGdu] by the key[Z2l0aHViMTkyLjE2OC4xOS4x], current ip[192.168.19.1]

项目源码

  • https://gitee.com/yadong.zhang/JustAuth
  • https://github.com/zhangyd-c/JustAuth

其他开源作品

  • blog-hunter,一款简单好用并且支持多个平台的博客爬取工具
  • OneBlog,一个简洁美观、功能强大并且自适应的Java博客
  • JustAuth,史上最全的整合第三方登录的工具,目前已支持Github、Gitee、微博、钉钉、百度、Coding、腾讯云开发者平台、OSChina、支付宝、QQ、微信、淘宝、Google、Facebook、抖音、领英、小米、微软和今日头条等第三方平台的授权登录。Login, so easy!
  • spingboot-shiro,Springboot + shiro权限管理。这或许是流程最详细、代码最干净、配置最简单的shiro上手项目了。
  • braum-spring-boot-starter,Braum可以很方便的帮助开发人员过滤、识别恶意请求

这篇关于JustAuth升级到v1.8.1版本,新增AuthState工具类,可自动生成state的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1020132

相关文章

IDEA如何让控制台自动换行

《IDEA如何让控制台自动换行》本文介绍了如何在IDEA中设置控制台自动换行,具体步骤为:File-Settings-Editor-General-Console,然后勾选Usesoftwrapsin... 目录IDEA如何让控制台自http://www.chinasem.cn动换行操作流http://www

vscode保存代码时自动eslint格式化图文教程

《vscode保存代码时自动eslint格式化图文教程》:本文主要介绍vscode保存代码时自动eslint格式化的相关资料,包括打开设置文件并复制特定内容,文中通过代码介绍的非常详细,需要的朋友... 目录1、点击设置2、选择远程--->点击右上角打开设置3、会弹出settings.json文件,将以下内

基于Go语言实现一个压测工具

《基于Go语言实现一个压测工具》这篇文章主要为大家详细介绍了基于Go语言实现一个简单的压测工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录整体架构通用数据处理模块Http请求响应数据处理Curl参数解析处理客户端模块Http客户端处理Grpc客户端处理Websocket客户端

golang1.23版本之前 Timer Reset方法无法正确使用

《golang1.23版本之前TimerReset方法无法正确使用》在Go1.23之前,使用`time.Reset`函数时需要先调用`Stop`并明确从timer的channel中抽取出东西,以避... 目录golang1.23 之前 Reset ​到底有什么问题golang1.23 之前到底应该如何正确的

Python脚本实现自动删除C盘临时文件夹

《Python脚本实现自动删除C盘临时文件夹》在日常使用电脑的过程中,临时文件夹往往会积累大量的无用数据,占用宝贵的磁盘空间,下面我们就来看看Python如何通过脚本实现自动删除C盘临时文件夹吧... 目录一、准备工作二、python脚本编写三、脚本解析四、运行脚本五、案例演示六、注意事项七、总结在日常使用

java图像识别工具类(ImageRecognitionUtils)使用实例详解

《java图像识别工具类(ImageRecognitionUtils)使用实例详解》:本文主要介绍如何在Java中使用OpenCV进行图像识别,包括图像加载、预处理、分类、人脸检测和特征提取等步骤... 目录前言1. 图像识别的背景与作用2. 设计目标3. 项目依赖4. 设计与实现 ImageRecogni

MybatisGenerator文件生成不出对应文件的问题

《MybatisGenerator文件生成不出对应文件的问题》本文介绍了使用MybatisGenerator生成文件时遇到的问题及解决方法,主要步骤包括检查目标表是否存在、是否能连接到数据库、配置生成... 目录MyBATisGenerator 文件生成不出对应文件先在项目结构里引入“targetProje

IDEA如何切换数据库版本mysql5或mysql8

《IDEA如何切换数据库版本mysql5或mysql8》本文介绍了如何将IntelliJIDEA从MySQL5切换到MySQL8的详细步骤,包括下载MySQL8、安装、配置、停止旧服务、启动新服务以及... 目录问题描述解决方案第一步第二步第三步第四步第五步总结问题描述最近想开发一个新应用,想使用mysq

基于Python开发电脑定时关机工具

《基于Python开发电脑定时关机工具》这篇文章主要为大家详细介绍了如何基于Python开发一个电脑定时关机工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 简介2. 运行效果3. 相关源码1. 简介这个程序就像一个“忠实的管家”,帮你按时关掉电脑,而且全程不需要你多做

java脚本使用不同版本jdk的说明介绍

《java脚本使用不同版本jdk的说明介绍》本文介绍了在Java中执行JavaScript脚本的几种方式,包括使用ScriptEngine、Nashorn和GraalVM,ScriptEngine适用... 目录Java脚本使用不同版本jdk的说明1.使用ScriptEngine执行javascript2.