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

相关文章

深入浅出Spring中的@Autowired自动注入的工作原理及实践应用

《深入浅出Spring中的@Autowired自动注入的工作原理及实践应用》在Spring框架的学习旅程中,@Autowired无疑是一个高频出现却又让初学者头疼的注解,它看似简单,却蕴含着Sprin... 目录深入浅出Spring中的@Autowired:自动注入的奥秘什么是依赖注入?@Autowired

Debian 13升级后网络转发等功能异常怎么办? 并非错误而是管理机制变更

《Debian13升级后网络转发等功能异常怎么办?并非错误而是管理机制变更》很多朋友反馈,更新到Debian13后网络转发等功能异常,这并非BUG而是Debian13Trixie调整... 日前 Debian 13 Trixie 发布后已经有众多网友升级到新版本,只不过升级后发现某些功能存在异常,例如网络转

Ubuntu如何升级Python版本

《Ubuntu如何升级Python版本》Ubuntu22.04Docker中,安装Python3.11后,使用update-alternatives设置为默认版本,最后用python3-V验证... 目China编程录问题描述前提环境解决方法总结问题描述Ubuntu22.04系统自带python3.10,想升级

解决升级JDK报错:module java.base does not“opens java.lang.reflect“to unnamed module问题

《解决升级JDK报错:modulejava.basedoesnot“opensjava.lang.reflect“tounnamedmodule问题》SpringBoot启动错误源于Jav... 目录问题描述原因分析解决方案总结问题描述启动sprintboot时报以下错误原因分析编程异js常是由Ja

Python从Word文档中提取图片并生成PPT的操作代码

《Python从Word文档中提取图片并生成PPT的操作代码》在日常办公场景中,我们经常需要从Word文档中提取图片,并将这些图片整理到PowerPoint幻灯片中,手动完成这一任务既耗时又容易出错,... 目录引言背景与需求解决方案概述代码解析代码核心逻辑说明总结引言在日常办公场景中,我们经常需要从 W

Python实战之SEO优化自动化工具开发指南

《Python实战之SEO优化自动化工具开发指南》在数字化营销时代,搜索引擎优化(SEO)已成为网站获取流量的重要手段,本文将带您使用Python开发一套完整的SEO自动化工具,需要的可以了解下... 目录前言项目概述技术栈选择核心模块实现1. 关键词研究模块2. 网站技术seo检测模块3. 内容优化分析模

基于Redis自动过期的流处理暂停机制

《基于Redis自动过期的流处理暂停机制》基于Redis自动过期的流处理暂停机制是一种高效、可靠且易于实现的解决方案,防止延时过大的数据影响实时处理自动恢复处理,以避免积压的数据影响实时性,下面就来详... 目录核心思路代码实现1. 初始化Redis连接和键前缀2. 接收数据时检查暂停状态3. 检测到延时过

C#使用Spire.XLS快速生成多表格Excel文件

《C#使用Spire.XLS快速生成多表格Excel文件》在日常开发中,我们经常需要将业务数据导出为结构清晰的Excel文件,本文将手把手教你使用Spire.XLS这个强大的.NET组件,只需几行C#... 目录一、Spire.XLS核心优势清单1.1 性能碾压:从3秒到0.5秒的质变1.2 批量操作的优雅

Python使用python-pptx自动化操作和生成PPT

《Python使用python-pptx自动化操作和生成PPT》这篇文章主要为大家详细介绍了如何使用python-pptx库实现PPT自动化,并提供实用的代码示例和应用场景,感兴趣的小伙伴可以跟随小编... 目录使用python-pptx操作PPT文档安装python-pptx基础概念创建新的PPT文档查看

在ASP.NET项目中如何使用C#生成二维码

《在ASP.NET项目中如何使用C#生成二维码》二维码(QRCode)已广泛应用于网址分享,支付链接等场景,本文将以ASP.NET为示例,演示如何实现输入文本/URL,生成二维码,在线显示与下载的完整... 目录创建前端页面(Index.cshtml)后端二维码生成逻辑(Index.cshtml.cs)总结