本文主要是介绍JustAuth升级到v1.8.1版本,新增AuthState工具类,可自动生成state,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
JustAuth(gitee | github),如你所见,它仅仅是一个第三方授权登录的工具类库,它可以让我们脱离繁琐的第三方登录SDK,让登录变得So easy!
JustAuth的功能
史上最全的整合第三方登录的工具,目前已支持Github、Gitee、微博、钉钉、百度、Coding、腾讯云开发者平台、OSChina、支付宝、QQ、微信、淘宝、Google、Facebook、抖音、领英、小米、微软和今日头条等第三方平台的授权登录。 Login, so easy!
JustAuth特点
废话不多说,就俩字:
- 全:已集成十多家第三方平台(国内外常用的基本都已包含),后续依然还有扩展计划!
- 简: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&redirect_uri=http://dblog-web.zhyd.me/oauth/callback/github&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的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!