为了账号安全,请及时绑定邮箱和手机立即绑定

JustAuth发布1.10.0版本,集成华为和企业微信登录,更加灵活的state缓存

标签:
Java 开源

JustAuth发布1.10.0版本,集成华为和企业微信登录,更加灵活的state缓存

更新内容

新增

  • 增加AuthCache配置类AuthCacheConfig.java,可以自定义缓存有效期以及是否开启定时任务
  • 简单封装极简版的针对JustAuth的Log工具类
  • 集成华为登录
  • 集成企业微信

修改

  • 抽取 cache 接口,方便用户自行集成 cache
  • 修改AuthChecker#checkCode方法,对于不同平台使用不同参数接受code的情况统一做处理
  • 添加StringUtil单元测试,修复bug

删除

  • 去掉slf4j依赖,

其他

  • 规范测试类

集成华为授权登录

一、注册并登录

二、申请开发者认证

点击右上角“管理中心”,如果你是第一次注册登录华为开发者平台,那么需要先进行账号认证
请自行操作。
注:如果你是新用户,那么可能注册完成后就会要求你认证,页面如下:

image

选择“个人开发者”认证,接下来会让你选择认证方式,
image

我选择的“支付宝扫码认证”,这种方式确实十分快,几分钟就完事了(PS:“身份证人工审核认证 ”这种认证方式,本人没用,所以不知道具体的认证时间)。

三、创建应用

认证完成后,重新进入管理中心:https://developer.huawei.com/consumer/cn/console#/serviceCards/
image
这个“开发者联盟使用向导”可以视个人情况选择关闭,接下来就是重点了,如果不想走弯路,一定要仔细按照教程步骤来做,当时我就折腾了老大会才搞明白。

首先,我一开始创建应用的时候是按照官网教程:https://developer.huawei.com/consumer/cn/devservice/doc/31107操作的,官网教程中说的是创建“免安装/H5应用”,
image
image
但是我在实际操作的时候,并没有看到这个选项
image

当时我还以为是华为的网站更新而文档没有更新,所以专门去提了工单
image

后来才得知是“开通了白名单才能申请“免安装/H5应用”,个人开发者一般是无法申请的”。

那么,难道个人开发者就没法用了吗?当然不是,要不也不会有这篇教程了…

当时我根据网站上列出的这几种应用类型,挨个点了一遍,大致浏览了以下各类型应用需要的参数,也不知道是因为心有灵犀还是因为别的原因,当时自己选来选去,最终选了一个“服务器应用”,奔着试试看的想法,大不了换种类型重新试。

选定应用类型后,根据提示,需要先创建产品
image
点击“创建产品”按钮后会弹出窗口,按照提示填入相应内容即可:
image

创建完产品后,会弹出提示框告知产品的appid和secret,记得先保存起来。

最终创建完成后的应用内容如下

image

四、对接SDK完成授权登录

参考开放平台鉴权:https://developer.huawei.com/consumer/cn/devservice/doc/30101文档说明,获取授权链接,然后拿授权code换取accessToken。

4.1 获取授权链接

根据文档说明,我们拼装一下授权地址:

https://oauth-login.cloud.huawei.com/oauth2/v2/authorize?response_type=code&client_id=[创建产品时给的appid]&redirect_uri=[上面填的回调地址]&access_type=offline&scope=https%3A%2F%2Fwww.huawei.com%2Fauth%2Faccount%2Fbase.profile

浏览器直接访问这个地址,就会跳转到华为的授权登录页面

image

点击“授权并登录”后,就会跳转到我们配置的“回调地址”中,并附带一个authorization_code参数(注,因为我们生成授权链接的时候没有带state参数,所以回调中也就没有这个参数)。

4.2 使用code获取accessToken

根据文档说明,我们编写以下代码获取token:

String code = "上一步获取的authorization_code";
HttpResponse response = HttpRequest.post("https://oauth-login.cloud.huawei.com/oauth2/v2/token")
	.form("grant_type", "authorization_code")
	.form("code", code)
	.form("client_id", "[创建产品时给的appid]")
	.form("client_secret", "[创建产品时给的secret]")
	.form("redirect_uri", "[上面填的回调地址]")
	.execute();
System.out.println(response.body());

请求成功后会返回以下内容

{
	"access_token": "accessToken",
	"expires_in": 3600,
	"refresh_token": "refreshToken",
	"scope": "https:\/\/www.huawei.com\/auth\/account\/base.profile",
	"token_type": "Bearer"
}

4.3 使用accessToken获取用户信息

到这一步又是一个麻烦,因为开放平台鉴权:https://developer.huawei.com/consumer/cn/devservice/doc/30101文档中只有获取授权地址和获取accessToken以及刷新token的api,但是并没有获取用户信息的接口说明,我翻遍了华为的api文档,也没有找到相关的信息(可能是我比较菜,没找到藏哪儿了)。当然, 这是难不倒我们的。

有朋友会问,官网都没提供文档说明,你怎么能搞出来? (蜜汁微笑~~( ̄▽ ̄)")

虽然官网没有这方便的api,但是我在他官网发现了另一份SDK:华为开放平台JavaScript SDK:https://developer.huawei.com/consumer/cn/devservice/doc/30104,既然js能实现,那么我们可以从这儿入手,把sdk下载下来,研究一下他的jssdk怎么实现的,然后想办法翻译成Java语言。

SDK下载来后直接解压,是一个web工程,目录结构如下(已隐去非必要的文件):

.
|-- WebContent
|   |-- WEB-INF
|   |   |           `-- lib
|   |   |           `-- fastjson-1.1.32.jar
|   |   `-- web.xml
|   |-- handleTransit.html
|   |-- index.html
|   `-- js
|       |-- NspClient.js
|       `-- jquery-1.10.2.js
`-- src
    `-- com
        `-- huawei
            `-- demo
                |-- GetAccessToken.java
                `-- HttpUtil.java

其中的java代码,我们可以直接忽略,没什么用。我们主要关注这三个文件:

  • NspClient.js 核心js代码,负责发送api请求
  • index.html 首页,负责演示授权登录
  • handleTransit.html 使用authorization_code获取token的,并把token存到cookie中,然后跳转刷新页面。

在源码中,我注意到在index.html中有一段代码:

var option = {
	respontype : "code",
	appid:"",
	handle_login_uri:"http://ip:port/NSPClient/handleTransit.html"
}

var nspp = new NspClient(option);

nspp.checkLogin(function() {
	// 调用接口
	nspp.api('OpenUP.User.getInfo', {
		callback : function(data) {
			if (!!data) {
				alert("userName:" + data.userName + ",userID:" + data.userID)
			}
		},
		onError : function(err) {
			if (!!err) {
				alert("error");
			}
		}
	});
});

根据代码字面意思,可以猜到,这个就是获取用户信息的接口,不过这儿有一点疑问:正常的oauth授权流程中,获取用户信息,必然会用到accesstoken,但是这里面却没有显示引用token的地方,考虑handleTransit.html文件中有把token存cookie的操作,所以在这儿看来引用token的操作应该是被封装到NspClient.js中。下一步我们就以nspp.api('OpenUP.User.getInfo', {})为入口,阅读NspClient.js的源码实现。

nspp.api()源码如下(by zhyd xx开头的是我添加的注释,方便各位理解):

/**
 * api请求(向后台发请求的公共方法)
 *
 * @param name =
 *            api名称
 * @param Settings={
 *            param:{json格式参数}, dataType:
 *            ’POST/GET/JSONP/XPOST(同POST)’,callback:回调方法 }
 *
 * @desc JSONP方式采用JS方式调用网关,其它方式通过JSON方式
 */
NspClient.prototype.api = function (name, settings, apiUrl) {
	var self = this;
	// by zhyd 通过阅读下面的代码,发现这才是真正的请求api的地址,源码分析见下面【关于`url`】
	var url = this.url;
	var API = {}, paramData;
	paramData = settings.param;

	// 调用测试桩时使用
	// by zhyd 在`index.html`页面中,调用`nspp.api()`时,只传入了name和settings,并没有传入apiUrl,所以这个if代码块并不会执行,里面的内容我们直接忽略。
	if (apiUrl) {
		if (window.console) {
			window.console.warn('正在使用测试桩,接口名为:' + name);
		}
		url = apiUrl;
		if (settings.dataType === 'XPOST') {
			settings.dataType = 'JSONP';
		}
	}
    // by zhyd 入参settings中并没有传入param,所以这个for循环代码块也不会执行,里面的内容我们直接忽略。
	for (var i in paramData) {
		if (typeof paramData[i] === 'object') {
			paramData[i] = NSPHelper.jsonToString(paramData[i]);
		}
		API[i] = paramData[i];
	}

	//            if (!apiUrl && name && name.indexOf("OpenOther.System") < 0) {
	//                name = "OpenOther.Delegate." + name.replace(new RegExp("\\.", "g"), "_");
	//            }

    // by zhyd 关键!指定调用的api名称
	API.nsp_svc = name;

	var type = 'JSONP';
	// by zhyd 入参未传入dataType,所以type一定等于'JSONP',下面的switch代码块,我们直接看case 'JSONP'下的即可。
	if (!!settings.dataType) {
		type = settings.dataType;
	}
	switch (type) {
		case 'JSONP':
		    // 通过JSONP方式请求api【关于`getJSONP`】
			this.getJSONP(url, API, settings.callback, settings.onError);
			break;
		case 'XPOST':
			NspClient.xdr.send(url, "POST", API, settings.callback,
				settings.error);
			break;
		case 'POST':
		case 'GET':
		case 'post':
		case 'get':
			NspClient.xdr.send(url, settings.dataType, API,
				settings.callback, settings.error);
			break;
		default:
			this.getJSONP(url, API, settings.callback, settings.onError);
	}

};

ok,上面api的代码已经大致理解了,关键部分就那几处,分开解释。

  • 关于url

这个url是在创建NspClient时指定的,源码如下:

/**
 * NspClient Class
 *
 * @param {obj}
 *            option为类似如下配置参数: { appid: 开发平台在OpenGw的对应应用id ,
 *            access_token:xxx, debug:xxx }
 */
var NspClient = function (option) {
	this.handleLoginUri = option && option.handle_login_uri ? option.handle_login_uri
		: nspClientCfg.handle_login_uri;
	// 登录成功后,保存登录状态页面
	// by zhyd 这儿指定
	this.url = nspClientCfg.opengw_api_url;// OpenGw API接口地址
	this.loginUrl = nspClientCfg.login_url;// 登录页面地址
	this.urlGetToken = nspClientCfg.get_token_url;// 应用级API前获取token的接口地址
	this.authApi = 'open.auth.check';
	this.appid = option && option.appid ? option.appid
		: nspClientCfg.app_id;// 开发者联盟的appid
	this.secret = option && option.secret ? option.secret
		: nspClientCfg.serect;
	this.access_token = option && option.access_token ? option.access_token
		: '';
	this.respontype = option && option.respontype ? option.respontype
		: nspClientCfg.respontype;
};

我们再看nspClientCfg.opengw_api_url的内容

var gwAccess = {
	opengw_api_url: "https://api.vmall.com/rest.php",// OpenGw  API接口地址
	opengw_cross_url: "https://api.vmall.com/nspcross.html",
	login_url: "https://login.vmall.com/oauth2/authorize",// 登录页面地址
	get_token_url: "https://login.vmall.com/oauth2/token",// 应用级API前获取token的接口地址
	xdr_proxy_url: "/",
	respontype: "token"
};

到这儿我们就明白了,实际的获取用户的api接口是https://api.vmall.com/rest.php

  • 关于getJSONP

源码如下

/**
 * 组装数据,发送jsonp请求
 *
 * @param {string}
 *            url 请求地址
 * @param {object}
 *            data 请求数据
 * @param {function}
 *            callback 成功回调函数
 * @return undefined
 */
NspClient.prototype.getJSONP = function (url, data, callback, onError) {
	var self = this;
	// by zhyd api请求参数,表示当前时间戳
	data.nsp_ts = new Date().getTime();
	// by zhyd 在这儿传入的token
	if (!data.access_token) {
		data.access_token = encodeURIComponent(this.access_token ? this.access_token
			: NSPHelper.getCookie('access_token'));
	}
	// by zhyd 下面这两个也是api请求参数,fmt表示是js请求,cb代表JSONP回调方法的名称
	data.nsp_fmt = 'JS';
	data.nsp_cb = '_jqjsp';

	/*
	 * 使用jquery的jsonp可以简化代码,减少代码量,但无法获得nsp_status,暂时搁浅 $.ajax({ url :
	 * url, data : data, dataType : "jsonp", jsonp : 'nsp_cb', success :
	 * function(data, textStatus, jqXHR) { }, error : function(e) { if
	 * (typeof onError == 'function') { onError(e); } else { if
	 * (window.console) { console.warn("request failed!"); } } } });
	 */
	ajax({
		url: url,
		data: data,
		dataType: "jsonp",
		// jsonpCallback:"_jqjsp",
		timeout: 30000,
		callbackParameter: "nsp_cb",
		success: function (data, nsp_status) {
			// by zhyd 这儿的回调处理我们直接忽略
		},
		error: function (e) {
			// by zhyd 这儿的异常处理我们直接忽略
		}
	});
};

ok,到这一步,我们算是全部理解了华为javascript sdk的获取用户信息的api操作流程,也正如上面猜的, accessToken就是在js内部做的处理。

那么接下来,我们就需要将这些代码整理翻译成java语言

 HttpResponse response = HttpRequest.post("https://api.vmall.com/rest.php")
	.form("nsp_ts", System.currentTimeMillis())
	.form("access_token", ["4.2 使用code获取accessToken"中获取的token])
	.form("nsp_fmt", "JS")
	.form("nsp_cb", "_jqjsp")
	.form("nsp_svc", "OpenUP.User.getInfo")
	.execute();
System.out.println(response.body());

打印结果为:

_jqjsp({"gender":1,"headPictureURL":"xxx","languageCode":"zh-CN","userID":"xxx","userName":"xxx","userState":1,"userValidStatus":1}, 0)

典型的JSONP格式,那么有没有办法去掉这个_jqjsp()?如果不去掉,我们还需要手动处理这些东西,才能得到一个真正的json内容,那么我们就尝试以下,还记得上面说的("nsp_cb", "_jqjsp")这个就是指定的callback的方法,那么我们直接去掉这个参数,试试行不行

image

果然! 不得不说:WHNB!

五、通过JustAuth集成华为授权登录

参考JustAuth官方文档:https://docs.justauth.whnb.wang,根据快速开始的说明,我们需要引入依赖:

<dependency>
  <groupId>me.zhyd.oauth</groupId>
  <artifactId>JustAuth</artifactId>
  <version>1.10.0</version>
</dependency>

然后通过以下方式调用api进行授权

// 创建授权request
AuthRequest authRequest = new AuthGiteeRequest(AuthConfig.builder()
      .clientId("clientId")
      .clientSecret("clientSecret")
      .redirectUri("redirectUri")
      .build());
// 生成授权页面
authRequest.authorize();
// 授权登录后会返回code(auth_code(仅限支付宝))、state,1.8.0版本后,可以用AuthCallback类作为回调接口的参数
// 注:JustAuth默认保存state的时效为3分钟,3分钟内未使用则会自动清除过期的state
authRequest.login(callback);

当然,项目是有配套demo的,可以直接使用https://gitee.com/yadong.zhang/JustAuth-demo这个demo项目进行测试。

clone下来项目后,在RestAuthController#getAuthRequest(String)方法中找到case "huawei":,然后将上面我们申请应用时的那三个属性(appid,secret,redirectUri)配置到代码中:

case "huawei":
    authRequest = new AuthHuaweiRequest(AuthConfig.builder()
            .clientId("[创建产品时提供的appid]")
            .clientSecret("[创建产品时提供的secret]")
            .redirectUri("[上面配置的回调地址]")
            .build());
    break;

注,JustAuth-demo项目中的controller方法比较规范,是根据source(平台名)区别的具体执行request,所以,如果直接使用本例的话,需要在创建华为应用时指定redirectUrihttp://127.0.0.1:8443/oauth/callback/huawei,当然,你也可以直接对JustAuth-demo项目进行改造。

然后启动项目,访问http://localhost:8443,点击最后的华为登录即可

正常的在华为授权页面登录成功后,会出现以下页面
image

点击“授权并登录”即可看到登录后的用户信息。

集成企业微信授权登录

关于JustAuth

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

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

项目开源地址:gitee | github

JustAuth的特点

废话不多说,就俩字:

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

JustAuth相关项目

demo

springboot插件

其他开源作品

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

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
全栈工程师
手记
粉丝
9132
获赞与收藏
5502

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消