diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..3d1c15e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# JustAuth 开发组IDE 编辑器标准 +root = true + +# 空格替代Tab缩进在各种编辑工具下效果一致 +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true + +[*.java] +indent_size = 4 + +[*.md] +insert_final_newline = false +trim_trailing_whitespace = false + diff --git a/README.md b/README.md index 34142e8..d330815 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,18 @@ + + + + + + + +
@@ -88,7 +96,9 @@ authRequest.login(callback); 注:`1.8.0`版本后,增加了`state`参数校验,用于防止[CSRF](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0)。强烈建议,保证单次流程内`state`的唯一性,且每个`state`只可用一次。 -**配套Demo**:[JustAuth-demo](https://gitee.com/yadong.zhang/JustAuth-demo) +**配套Demo**: +- [Springboot版](https://gitee.com/yadong.zhang/JustAuth-demo) +- [jFinal版](https://github.com/zhangyd-c/jfinal-justauth-demo) 具体的例子可以参考: @@ -118,6 +128,10 @@ authRequest.login(callback); | | [AuthMicrosoftRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java) | 参考文档 | | | [AuthMiRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthMiRequest.java) | 参考文档 | | | [AuthToutiaoRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthToutiaoRequest.java) | 参考文档 | +| | [AuthTeambitionRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthTeambitionRequest.java) | 参考文档 | +| | [AuthRenrenRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthRenrenRequest.java) | 参考文档 | +| | [AuthPinterestRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthPinterestRequest.java) | 参考文档 | +| | [AuthStackOverflowRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthStackOverflowRequest.java) | 参考文档 | | | [AuthCsdnRequest](https://gitee.com/yadong.zhang/JustAuth/blob/master/src/main/java/me/zhyd/oauth/request/AuthCsdnRequest.java) | 无 | _请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经下线。如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了_ @@ -166,4 +180,4 @@ _请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经 | 支付宝 | 微信 | | :------------: | :------------: | -| | | \ No newline at end of file +| | | diff --git a/developer.md b/developer.md index 0d94b18..d02b93a 100644 --- a/developer.md +++ b/developer.md @@ -3,4 +3,5 @@ - · yadong.zhang : [Github] | [Gitee] | [个人网站] - · yangkai.shen : [Github] | [个人网站] - · skqing : [Gitee] | [个人网站] +- · pengisgood : [Github] | [个人网站] - 千年等一回,我只为等你... diff --git a/example.md b/example.md index fb5f25c..2d60551 100644 --- a/example.md +++ b/example.md @@ -70,7 +70,7 @@ _注:非全部平台,部分平台可能不存在图例_ #### 授权微软 -暂无 +![授权微软](https://images.gitee.com/uploads/images/2019/0718/224146_681aa535_784199.png "授权微软") #### 授权小米 @@ -80,16 +80,28 @@ _注:非全部平台,部分平台可能不存在图例_ 暂无 +#### 授权Teambition + +![授权Teambition](https://images.gitee.com/uploads/images/2019/0718/224119_3da514ab_784199.png "授权Teambition") + +#### 授权Pinterest + +![授权Pinterest](https://images.gitee.com/uploads/images/2019/0718/155012_6290f500_784199.jpeg "授权Pinterest") + +#### 授权Renren + +![授权Renre](https://images.gitee.com/uploads/images/2019/0718/155035_8e26c10a_784199.jpeg "授权Renren") + +#### 授权Stack Overflow + +![授权Stack Overflow](https://images.gitee.com/uploads/images/2019/0718/192639_cc301ba7_784199.png "授权Stack Overflow") + +#### 授权Twitter + +暂无 + #### 授权csdn 暂无 -#### 授权Pinterest - -![授权Pinterest](https://images.gitee.com/uploads/images/2019/0718/155012_6290f500_784199.jpeg "在这里输入图片标题") - -#### 授权Renren - -![授权Renre](https://images.gitee.com/uploads/images/2019/0718/155035_8e26c10a_784199.jpeg "在这里输入图片标题") - -_请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经下线。如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了_ \ No newline at end of file +_请知悉:经咨询CSDN官方客服得知,CSDN的授权开放平台已经下线。如果以前申请过的应用,可以继续使用,但是不再支持申请新的应用。so, 本项目中的CSDN登录只能针对少部分用户使用了_ diff --git a/pom.xml b/pom.xml index 65a7890..c0d9d7d 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ me.zhyd.oauth JustAuth - 1.8.1 + 1.9.0 JustAuth https://gitee.com/yadong.zhang/JustAuth @@ -30,7 +30,7 @@ - yadong.zhang + Yadong.Zhang yadong.zhang0415@gmail.com https://www.zhyd.me @@ -39,6 +39,11 @@ shenyangkai1994@gmail.com https://xkcoding.com + + Hongwei.Peng + pengisgood@gmail.com + https://github.com/pengisgood + diff --git a/src/main/java/me/zhyd/oauth/config/AuthConfig.java b/src/main/java/me/zhyd/oauth/config/AuthConfig.java index 8b1f7da..2e65bcd 100644 --- a/src/main/java/me/zhyd/oauth/config/AuthConfig.java +++ b/src/main/java/me/zhyd/oauth/config/AuthConfig.java @@ -1,6 +1,7 @@ package me.zhyd.oauth.config; -import lombok.*; +import lombok.Builder; +import lombok.Getter; /** * JustAuth配置类 @@ -9,11 +10,8 @@ import lombok.*; * @version 1.0 * @since 1.8 */ -@Setter @Getter @Builder -@NoArgsConstructor -@AllArgsConstructor public class AuthConfig { /** @@ -51,4 +49,11 @@ public class AuthConfig { * 1.8.0版本新增参数 */ private String state; + + /** + * Stack Overflow Key + *

+ * 1.9.0版本新增参数 + */ + private String stackOverflowKey; } diff --git a/src/main/java/me/zhyd/oauth/config/AuthSource.java b/src/main/java/me/zhyd/oauth/config/AuthSource.java index 3326847..f712210 100644 --- a/src/main/java/me/zhyd/oauth/config/AuthSource.java +++ b/src/main/java/me/zhyd/oauth/config/AuthSource.java @@ -1,7 +1,7 @@ package me.zhyd.oauth.config; import me.zhyd.oauth.exception.AuthException; -import me.zhyd.oauth.request.ResponseStatus; +import me.zhyd.oauth.model.AuthResponseStatus; /** * 各api需要的url, 用枚举类分平台类型管理 @@ -79,7 +79,7 @@ public enum AuthSource { @Override public String accessToken() { - throw new AuthException(ResponseStatus.UNSUPPORTED); + throw new AuthException(AuthResponseStatus.UNSUPPORTED); } @Override @@ -110,6 +110,11 @@ public enum AuthSource { public String revoke() { return "https://openapi.baidu.com/rest/2.0/passport/auth/revokeAuthorization"; } + + @Override + public String refresh() { + return "https://openapi.baidu.com/oauth/2.0/token"; + } }, /** * csdn @@ -224,6 +229,11 @@ public enum AuthSource { public String userInfo() { return "https://graph.qq.com/user/get_user_info"; } + + @Override + public String refresh() { + return "https://graph.qq.com/oauth2.0/token"; + } }, /** * 微信 @@ -265,7 +275,7 @@ public enum AuthSource { @Override public String userInfo() { - throw new AuthException(ResponseStatus.UNSUPPORTED); + throw new AuthException(AuthResponseStatus.UNSUPPORTED); } }, /** @@ -420,6 +430,95 @@ public enum AuthSource { public String userInfo() { return "https://open.snssdk.com/data/user_profile"; } + }, + /** + * Teambition + */ + TEAMBITION { + @Override + public String authorize() { + return "https://account.teambition.com/oauth2/authorize"; + } + + @Override + public String accessToken() { + return "https://account.teambition.com/oauth2/access_token"; + } + + @Override + public String refresh() { + return "https://account.teambition.com/oauth2/refresh_token"; + } + + @Override + public String userInfo() { + return "https://api.teambition.com/users/me"; + } + }, + + /** + * 人人网 + */ + RENREN { + @Override + public String authorize() { + return "https://graph.renren.com/oauth/authorize"; + } + + @Override + public String accessToken() { + return "https://graph.renren.com/oauth/token"; + } + + @Override + public String refresh() { + return "https://graph.renren.com/oauth/token"; + } + + @Override + public String userInfo() { + return "https://api.renren.com/v2/user/get"; + } + }, + + /** + * Pinterest + */ + PINTEREST { + @Override + public String authorize() { + return "https://api.pinterest.com/oauth"; + } + + @Override + public String accessToken() { + return "https://api.pinterest.com/v1/oauth/token"; + } + + @Override + public String userInfo() { + return "https://api.pinterest.com/v1/me"; + } + }, + + /** + * Stack Overflow + */ + STACK_OVERFLOW { + @Override + public String authorize() { + return "https://stackoverflow.com/oauth"; + } + + @Override + public String accessToken() { + return "https://stackoverflow.com/oauth/access_token/json"; + } + + @Override + public String userInfo() { + return "https://api.stackexchange.com/2.2/me"; + } }; /** @@ -449,7 +548,7 @@ public enum AuthSource { * @return url */ public String revoke() { - throw new AuthException(ResponseStatus.UNSUPPORTED); + throw new AuthException(AuthResponseStatus.UNSUPPORTED); } /** @@ -458,7 +557,7 @@ public enum AuthSource { * @return url */ public String refresh() { - throw new AuthException(ResponseStatus.UNSUPPORTED); + throw new AuthException(AuthResponseStatus.UNSUPPORTED); } -} \ No newline at end of file +} diff --git a/src/main/java/me/zhyd/oauth/enums/AuthBaiduErrorCode.java b/src/main/java/me/zhyd/oauth/enums/AuthBaiduErrorCode.java deleted file mode 100644 index 0fb6f7a..0000000 --- a/src/main/java/me/zhyd/oauth/enums/AuthBaiduErrorCode.java +++ /dev/null @@ -1,62 +0,0 @@ -package me.zhyd.oauth.enums; - -import me.zhyd.oauth.utils.StringUtils; - -/** - * 百度授权登录时的异常状态码 - * - * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 - */ -public enum AuthBaiduErrorCode { - OK("ok", "ok", "ok"), - INVALID_REQUEST("invalid_request", "invalid refresh token", "请求缺少某个必需参数,包含一个不支持的参数或参数值,或者格式不正确。"), - INVALID_CLIENT("invalid_client", "unknown client id", "client_id”、“client_secret”参数无效。"), - INVALID_GRANT("invalid_grant", "The provided authorization grant is revoked", "提供的Access Grant是无效的、过期的或已撤销的,例如,Authorization Code无效(一个授权码只能使用一次)、Refresh Token无效、redirect_uri与获取Authorization Code时提供的不一致、Devie Code无效(一个设备授权码只能使用一次)等。"), - UNAUTHORIZED_CLIENT("unauthorized_client", "The client is not authorized to use this authorization grant type", "应用没有被授权,无法使用所指定的grant_type。"), - UNSUPPORTED_GRANT_TYPE("unsupported_grant_type", "The authorization grant type is not supported", "“grant_type”百度OAuth2.0服务不支持该参数。"), - INVALID_SCOPE("invalid_scope", "The requested scope is exceeds the scope granted by the resource owner", "请求的“scope”参数是无效的、未知的、格式不正确的、或所请求的权限范围超过了数据拥有者所授予的权限范围。"), - EXPIRED_TOKEN("expired_token", "refresh token has been used", "提供的Refresh Token已过期"), - REDIRECT_URI_MISMATCH("redirect_uri_mismatch", "Invalid redirect uri", "“redirect_uri”所在的根域与开发者注册应用时所填写的根域名不匹配。"), - UNSUPPORTED_RESPONSE_TYPE("unsupported_response_type", "The response type is not supported", "“response_type”参数值不为百度OAuth2.0服务所支持,或者应用已经主动禁用了对应的授权模式"), - SLOW_DOWN("slow_down", "The device is polling too frequently", "Device Flow中,设备通过Device Code换取Access Token的接口过于频繁,两次尝试的间隔应大于5秒。"), - AUTHORIZATION_PENDING("authorization_pending", "User has not yet completed the authorization", "Device Flow中,用户还没有对Device Code完成授权操作。"), - AUTHORIZATION_DECLINED("authorization_declined", "User has declined the authorization", "Device Flow中,用户拒绝了对Device Code的授权操作。"), - INVALID_REFERER("invalid_referer", "Invalid Referer", "Implicit Grant模式中,浏览器请求的Referer与根域名绑定不匹配"); - - private String code; - private String msg; - private String desc; - - AuthBaiduErrorCode(String code, String msg, String desc) { - this.code = code; - this.msg = msg; - this.desc = desc; - } - - public static AuthBaiduErrorCode getErrorCode(String code) { - if (StringUtils.isEmpty(code)) { - return OK; - } - AuthBaiduErrorCode[] errorCodes = AuthBaiduErrorCode.values(); - for (AuthBaiduErrorCode errorCode : errorCodes) { - if (code.equalsIgnoreCase(errorCode.getCode())) { - return errorCode; - } - } - return OK; - } - - public String getCode() { - return code; - } - - public String getMsg() { - return msg; - } - - public String getDesc() { - return desc; - } -} diff --git a/src/main/java/me/zhyd/oauth/enums/AuthDingTalkErrorCode.java b/src/main/java/me/zhyd/oauth/enums/AuthDingTalkErrorCode.java deleted file mode 100644 index fb593d2..0000000 --- a/src/main/java/me/zhyd/oauth/enums/AuthDingTalkErrorCode.java +++ /dev/null @@ -1,404 +0,0 @@ -package me.zhyd.oauth.enums; - -/** - * 钉钉授权登录时的异常状态码 - * - * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 - */ -public enum AuthDingTalkErrorCode { - EC1_MINUS(-1, "系统繁忙", "服务器暂不可用,建议稍候再重试1次,最多重试3次"), - EC0(0, "请求成功", "接口调用成功"), - EC404(404, "请求的URI地址不存在", "地址不存在,检查下url是否和文档里写的一致"), - EC33001(33001, "无效的企业ID", "请确认下access_token是否正确"), - EC33002(33002, "无效的微应用的名称", "校验下微应用的名称字段,不能为空且长度不能超过10个字符"), - EC33003(33003, "无效的微应用的描述", "校验下微应用的描述字段,不能为空且长度不能超过20个字符"), - EC33004(33004, "无效的微应用的ICON", "校验下微应用的icon字段,不能为空且确保图标存在"), - EC33005(33005, "无效的微应用的移动端主页", "校验下微应用的移动端主页,不能为空且必须以http开头或https开头"), - EC33006(33006, "无效的微应用的PC端主页", "校验下微应用的PC端主页,必须以http开头或https开头"), - EC33007(33007, "微应用的移动端的主页与PC端主页不同", "校验下微应用的PC端主页,确保它和移动端主页的域名保持一致"), - EC33008(33008, "无效的微应用OA后台的主页", "校验下微应用的后台管理的主页失败,必须以http开头或https开头"), - EC34001(34001, "无效的会话id", "检查下所传的chatId字段是否为空"), - EC34002(34002, "无效的会话消息的发送者", "检查下sender字段是否为空"), - EC34003(34003, "无效的会话消息的发送者的企业Id", "检查下发送者的企业Id"), - EC34004(34004, "无效的会话消息的类型", "检查下msgtype字段,是否为空,是否是定义的那几种类型"), - EC34005(34005, "无效的会话音频消息的播放时间", "该错误码已废弃"), - EC34006(34006, "发送者不在企业中", "检查下发送者是否在企业中"), - EC34007(34007, "发送者不在会话中", "检查下发送者是否在会话id中"), - EC34008(34008, "图片不能为空", "如果发的是图片消息,检查下图片是否为空"), - EC34009(34009, "链接内容不能为空", "检查下messageUrl字段是否为空"), - EC34010(34010, "文件不能为空", "检查下media_id字段是否为空"), - EC34011(34011, "音频文件不能为空", "检查下media_id字段是否为空"), - EC34012(34012, "找不到发送者的企业", "检查下发送者是否是真实的"), - EC34013(34013, "找不到群会话对象", "检查下chatid是否真实存在"), - EC34014(34014, "会话消息的json结构无效或不完整", "检查下消息的json格式是否正确,json的key对应msgtype的value值"), - EC34015(34015, "发送群会话消息失败", "消息发送失败,建议稍后再重试下"), - EC34016(34016, "消息内容长度超过限制", "检查下消息的content字段长度是否超过5000,title字段长度是否超过64,markdown字段长度是否超过5000,single_title字段长度是否超过20,single_url字段长度是否超过500,btn_json_list字段长度是否超过1000"), - EC40001(40001, "获取access_token时Secret错误,或者access_token无效", "检查下access_token是否正确"), - EC40002(40002, "不合法的凭证类型", "无"), - EC40003(40003, "不合法的UserID", "确保该id在通讯录中存在,且是在你所传access_token对应的企业里"), - EC40004(40004, "不合法的媒体文件类型", "检查下type字段,只支持image,voice,file"), - EC40005(40005, "不合法的文件类型", "如果是文件类型,检查下是否是支持的那几种,目前只支持doc,docx,xls,xlsx,ppt,pptx,zip,pdf,rar"), - EC40006(40006, "不合法的文件大小", "检查下文件大小,image类型最大1MB,file类型最大10MB,voice类型最大2MB"), - EC40007(40007, "不合法的媒体文件id", "检查下mediaId是否为空,是否真实存在"), - EC40008(40008, "不合法的消息类型", "检查下msgtype是否为空,确保它在开放平台定义的几种类型里,具体见消息类型及格式"), - EC40009(40009, "不合法的部门id", "检查下部门id是否为空,是否为数字且大于0"), - EC40010(40010, "不合法的父部门id", "检查下父部门id是否为一个数字"), - EC40011(40011, "不合法的排序order", "检查下order字段是否为空,是否为数字且大于0"), - EC40012(40012, "不合法的发送者", "检查下sender字段是否为空,是否真实存在"), - EC40013(40013, "不合法的corpid", "检查下corpid是否有效"), - EC40014(40014, "不合法的access_token", "检查下access_token是否正确,注意access_token这个参数应该是带在url后面的"), - EC40015(40015, "发送者不在会话中", "检查下sender字段和cid字段是否能对应上"), - EC40016(40016, "不合法的会话ID", "检查下cid字段是否为空,是否有效"), - EC40017(40017, "在会话中没有找到与发送者在同一企业的人", "cid对应的消息接收者为空,检查下cid字段"), - EC40018(40018, "不允许以递归方式查询部门用户列表", "检查下fetchChild字段,目前不支持递归查询"), - EC40019(40019, "该手机号码对应的用户最多可以加入5个非认证企业", "无"), - EC40020(40020, "当前团队人数已经达到上限,用电脑登录钉钉企业管理后台,升级成为认证企业", "无"), - EC40021(40021, "更换的号码已注册过钉钉,无法使用该号码", "无"), - EC40022(40022, "企业中的手机号码和登录钉钉的手机号码不一致,暂时不支持修改用户信息,可以删除后重新添加", "无"), - EC40023(40023, "部门人数达到上限", "部门人数不能超过1000"), - EC40024(40024, "(安全校验不通过)保存失败,团队人数超限。请在手机钉钉绑定支付宝完成实名认证,或者申请企业认证,人数上限自动扩充", "无"), - EC40025(40025, "无效的部门JSONArray对象,合法格式需要用中括号括起来,且如果属于多部门,部门id需要用逗号分隔", "无"), - EC40029(40029, "不合法的oauth_code", "无"), - EC40031(40031, "不合法的UserID列表", "指定的UserID列表,至少存在一个UserID不在通讯录中"), - EC40032(40032, "不合法的UserID列表长度", "检查下列表是否为空,且长度合适。创建部门接口的userPerimits最多接收10000个"), - EC40033(40033, "不合法的请求字符,不能包含\\uxxxx格式的字符", "无"), - EC40035(40035, "不合法的参数", "检查下有没有传请求参数,一般发生在http post形式的接口里,没有传参数"), - EC40038(40038, "不合法的请求格式", "检查下参数中是不是少了某个字段,具体参考各个文档的参数介绍"), - EC40039(40039, "不合法的URL长度", "无"), - EC40048(40048, "url中包含不合法domain", "发消息接口中消息url链接不安全"), - EC40055(40055, "不合法的agent结构", "已废弃"), - EC40056(40056, "不合法的agentid", "检查下agentid字段是否为空,是否真实存在"), - EC40057(40057, "不合法的callbackurl", "无"), - EC40061(40061, "设置应用头像失败", "无"), - EC40062(40062, "不合法的应用模式", "无"), - EC40063(40063, "不合法的分机号", "tel字段长度超长,长度不能超过50"), - EC40064(40064, "不合法的工作地址", "workPlace长度超长,长度不能超过50个字符"), - EC40065(40065, "不合法的备注", "remark长度超长,长度不能超过1024个字符"), - EC40066(40066, "不合法的部门列表", "部门id列表长度太长,不能超过10000,并且每个id必须是数字"), - EC40067(40067, "标题长度不合法", "检查下标题长度"), - EC40068(40068, "不合法的偏移量", "偏移量必须大于0"), - EC40069(40069, "不合法的分页大小", "分页大小不合法,具体参考每个接口的参数定义"), - EC40070(40070, "不合法的排序参数", "具体参考获取部门成员接口里面对order字段的定义"), - EC40073(40073, "不存在的openid", "openid不能为空"), - EC40077(40077, "不存在的预授权码", "无"), - EC40078(40078, "不存在的临时授权码", "临时授权码不能为空,且只能被使用一次"), - EC40079(40079, "不存在的授权信息", "检查下企业是否授权"), - EC40080(40080, "不合法的suitesecret", "无"), - EC40082(40082, "不合法的suitetoken", "检查下token"), - EC40083(40083, "不合法的suiteid", "suiteKey字段不合法"), - EC40084(40084, "不合法的永久授权码", "检查下永久授权码是否正确"), - EC40085(40085, "不存在的suiteticket", "检查下suiteticket是否正确,确保是由回调接口正确来接收suiteticket"), - EC40086(40086, "不合法的第三方应用appid", "appid字段不能为空"), - EC40087(40087, "创建永久授权码失败", "稍后再重试下,确保参数都传对"), - EC40088(40088, "不合法的套件key或secret", "稍后再重试下,确保suiteKey和suiteSecret都传对且一一对应"), - EC40089(40089, "不合法的corpid或corpsecret", "稍后再重试下,确保corpid和corpsecret字段传对且一一对应"), - EC40090(40090, "套件已经不存在", "检查下suiteKey字段是否正确"), - EC40091(40091, "用户授权码创建失败,需要用户重新授权", "创建永久授权码失败,需要用户重新授权产生临时授权码"), - EC40103(40103, "用户开启了账号保护,无法被加入到您的团队", "用户在钉钉“我的-设置-隐私”出开启了账号保护"), - EC40104(40104, "无效手机号", "检查手机号格式是否正确"), - EC41001(41001, "缺少access_token参数", "检查下access_token是否传了,注意该参数必须跟在请求url中"), - EC41002(41002, "缺少corpid参数", "检查下corpid是否为空"), - EC41003(41003, "缺少refresh_token参数", "检查下refresh_token是否为空"), - EC41004(41004, "缺少secret参数", "检查下secret参数是否为空"), - EC41005(41005, "缺少多媒体文件数据", "无"), - EC41006(41006, "缺少media_id参数", "检查下media_id参数是否为空"), - EC41007(41007, "无效的ssocode", "sso的永久授权code无效,检查下是否为空"), - EC41008(41008, "缺少oauth", "无"), - EC41009(41009, "缺少UserID", "检查下UserID是否为空"), - EC41010(41010, "缺少url", "检查下url是否为空"), - EC41011(41011, "缺少agentid", "检查下agentid是否为空"), - EC41012(41012, "缺少应用头像mediaid", "检查下mediaid是否为空"), - EC41013(41013, "缺少应用名字", "检查应用名字是否为空"), - EC41014(41014, "缺少应用描述", "检查应用描述是否为空"), - EC41015(41015, "缺少JSON参数", "检查JSON参数是否为空"), - EC41021(41021, "缺少suitekey", "检查suitekey参数是否为空"), - EC41022(41022, "缺少suitetoken", "检查suitetoken参数是否为空"), - EC41023(41023, "缺少suiteticket", "检查suiteticket参数是否为空"), - EC41024(41024, "缺少suitesecret", "检查suitesecret参数是否为空"), - EC41025(41025, "缺少permanent_code", "检查permanent_code永久授权码参数是否为空"), - EC41026(41026, "缺少tmp_auth_code", "检查tmp_auth_code临时授权码参数是否为空"), - EC41027(41027, "需要授权企业的corpid参数", "检查corpid参数是否为空"), - EC41028(41028, "禁止给全员发送消息", "检查是否有全员发送消息的权限,ISV没有该权限"), - EC41029(41029, "超过消息接收者人数上限", "发送OA消息人数超上限(企业消息人数上限:5000,ISV消息人数上限:1000)"), - EC41030(41030, "企业未对该套件授权", "检查该企业是否已经对该套件进行授权"), - EC41031(41031, "auth_corpid和permanent_code不匹配", "激活套件时使用的auth_corpid和permanent_code不匹配"), - EC41041(41041, "查询间隔时间太长", "考勤打卡数据查询间隔时间超过7天"), - EC41044(41044, "禁止发送消息", "检查是否有权限发送消息"), - EC41045(41045, "单应用全员消息/每天总量超限", "无"), - EC41046(41046, "超过发送全员消息的每分钟次数上限", "企业OA消息全员发送每天不能超过3次,ISV不能发送全员消息"), - EC41047(41047, "超过给该企业发消息的每分钟次数上限", "企业OA消息每分钟不能超过1500次,ISV OA消息每分钟不能超过200次"), - EC41048(41048, "超过给企业发消息的每分钟次数总上限", ""), - EC41049(41049, "包含违禁内容", "检查消息文本中是否有黄色、反动等词语"), - EC41050(41050, "无效的活动编码", "无"), - EC41051(41051, "活动权益的校验失败", "无"), - EC41100(41100, "时间参数不合法", "时间参数不能为空,且为“yyyy-MM-dd hh:mm:ss”格式"), - EC41101(41101, "数据内容过长", "请求体字符长度不能大于4096"), - EC41102(41102, "参数值过大", "上传文件或者idlist等参数过大"), - EC42001(42001, "access_token超时", "请检查网络状态"), - EC42002(42002, "refresh_token超时", "请检查网络状态"), - EC42003(42003, "oauth_code超时", "请检查网络状态"), - EC42007(42007, "预授权码失效", "请检查该预授权码是否已经使用过"), - EC42008(42008, "临时授权码失效", "请检查该临时授权码是否已经使用过或者是否不正确"), - EC42009(42009, "suitetoken失效", "请检查该suitetoken是否已经过期"), - EC43001(43001, "需要GET请求", "请检查http请求方式是否正确"), - EC43002(43002, "需要POST请求", "请检查http请求方式是否正确"), - EC43003(43003, "需要HTTPS", "请检查调用接口协议是否是https"), - EC43004(43004, "无效的HTTP HEADER Content-Type", "请检查请求头中的content-type是否正确"), - EC43005(43005, "需要Content-Type为application/json;charset=UTF-8", "请检查请求头中的content-type是否是“application/json;charset=UTF-8”"), - EC43007(43007, "需要授权", "该接口需要access_token才能调用"), - EC43008(43008, "参数需要multipart类型", "检查提交参数中的ENCTYPE是否是multipart类型"), - EC43009(43009, "post参数需要json类型", "请检查post参数数据是否是json类型"), - EC44001(44001, "多媒体文件为空", "请检查多媒体文件数据是否为空"), - EC44002(44002, "POST的数据包为空", "请检查POST的数据包是否为空"), - EC44003(44003, "图文消息内容为空", "请检查图文消息参数是否为空"), - EC44004(44004, "文本消息内容为空", "请检查文本消息参数是否为空"), - EC45001(45001, "多媒体文件大小超过限制", "无"), - EC45002(45002, "消息内容超过限制", "无"), - EC45003(45003, "标题字段超过限制", "无"), - EC45004(45004, "描述字段超过限制", "无"), - EC45005(45005, "链接字段超过限制", "无"), - EC45006(45006, "图片链接字段超过限制", "无"), - EC45007(45007, "语音播放时间超过限制", "无"), - EC45008(45008, "图文消息超过限制", "无"), - EC45009(45009, "接口调用超过限制", "无"), - EC45016(45016, "系统分组,不允许修改", "无"), - EC45017(45017, "分组名字过长", "无"), - EC45018(45018, "分组数量超过上限", "无"), - EC45024(45024, "账号数量超过上限", "无"), - EC46001(46001, "不存在媒体数据", "无"), - EC46004(46004, "不存在的员工", "无"), - EC47001(47001, "解析JSON/XML内容错误", "无"), - EC48002(48002, "Api禁用", "无"), - EC48003(48003, "suitetoken无效", "无"), - EC48004(48004, "授权关系无效", "无"), - EC49000(49000, "缺少chatid", "请检查参数中是否有chatid"), - EC49001(49001, "绑定的微应用超过个数限制", "绑定群会话和微应用超过5个"), - EC49002(49002, "一个群只能被一个ISV套件绑定一次", "无"), - EC49003(49003, "操作者必须为群主", "无"), - EC49004(49004, "添加成员列表和删除成员列表不能有交集", "无"), - EC49005(49005, "群人数超过人数限制", "无"), - EC49006(49006, "群成员列表必须包含群主", "无"), - EC49007(49007, "超过创建群的个数上限", "无"), - EC49008(49008, "不合法的群类型,只能传入0或2", "无"), - EC49009(49009, "企业群不能添加外部联系人,群主只能为企业员工", "无"), - EC49010(49010, "群成员不能为空", "无"), - EC49011(49011, "群员工列表超长", "无"), - EC49012(49012, "群外部联系人列表超长", "无"), - EC49013(49013, "群主不能为空", "无"), - EC49014(49014, "非法的群主类型,只能为emp或者ext", "无"), - EC49015(49015, "不合法的群名称", "无"), - EC49016(49016, "查询企业员工不存在", "无"), - EC49017(49017, "查询企业外部联系人不存在", "无"), - EC49018(49018, "群主非企业员工", "无"), - EC49019(49019, "群主非企业外部通讯录人员", "无"), - EC49020(49020, "某人处于勿扰模式,拒绝加入群聊;请先与TA建立好友关系", "无"), - EC49021(49021, "非好友建立群聊,认证用户一天只能拉50个人,非认证用户一天只能拉10个人", "无"), - EC49022(49022, "某人拒绝加入群聊", "无"), - EC49023(49023, "某人处于勿扰模式,拒绝加入群聊;请先与TA建立好友关系", "无"), - EC50001(50001, "redirect_uri未授权", "无"), - EC50002(50002, "员工不在权限范围", "无"), - EC50003(50003, "应用已停用", "无"), - EC50004(50004, "企业部门不在授权范围", "检查企业部门是否设置可见范围,具体排查方法请参考通讯录FAQ"), - EC50005(50005, "企业已禁用", "无"), - EC52010(52010, "无效的corpid", "请检查corpid参数是否正确"), - EC52011(52011, "jsapi ticket 读取失败", "请检查ticket参数是否正确"), - EC52012(52012, "jsapi 签名生成失败", "请检查“url, nonceStr, timestamp, ticket”等参数是否正确"), - EC52013(52013, "签名校验失败", "请检查“url, nonceStr, timestamp, ticket”等参数是否正确"), - EC52014(52014, "无效的url参数", "请检查url参数是否正确"), - EC52015(52015, "无效的随机字符串参数", "请检查nonceStr参数是否正确"), - EC52016(52016, "无效的签名参数", "请检查“url, nonceStr, timestamp, ticket”等参数是否正确"), - EC52017(52017, "无效的jsapi列表参数", "请检查dd.config中的jsApiList参数是否正确"), - EC52018(52018, "无效的时间戳", "请检查timestamp参数是否正确"), - EC52019(52019, "无效的agentid", "请检查agentid参数是否正确"), - EC60001(60001, "不合法的部门名称", "请检查部门名称是否正确,长度不能超过64个字符"), - EC60002(60002, "部门层级深度超过限制", "无"), - EC60003(60003, "部门不存在", "无"), - EC60004(60004, "父亲部门不存在", "无"), - EC60005(60005, "不允许删除有成员的部门", "无"), - EC60006(60006, "不允许删除有子部门的部门", "无"), - EC60007(60007, "不允许删除根部门", "无"), - EC60008(60008, "父部门下该部门名称已存在", "无"), - EC60009(60009, "部门名称含有非法字符", "无"), - EC60010(60010, "部门存在循环关系", "无"), - EC60011(60011, "没有调用该接口的权限", "请检查当前请求使用的access_token是否有对该部门/人的操作权限,查看获取appSecret授权范围"), - EC60012(60012, "不允许删除默认应用", "无"), - EC60013(60013, "不允许关闭应用", "无"), - EC60014(60014, "不允许开启应用", "无"), - EC60015(60015, "不允许修改默认应用可见范围", "无"), - EC60016(60016, "部门id已经存在", "无"), - EC60017(60017, "不允许设置企业", "无"), - EC60018(60018, "不允许更新根部门", "无"), - EC60019(60019, "从部门查询人员失败", "请检查该成员是否在该部门中"), - EC60020(60020, "访问ip不在白名单之中", "如果是企业应用,检查配置的服务器出口ip地址是否和请求ip地址一致。如果是isv应用,请检查套件ip白名单和请求ip是否一致"), - EC60067(60067, "部门的企业群群主不存在", "无"), - EC60068(60068, "部门的管理员不存在", "无"), - EC60102(60102, "UserID在公司中已存在", "无"), - EC60103(60103, "手机号码不合法", "无"), - EC60104(60104, "手机号码在公司中已存在", "无"), - EC60105(60105, "邮箱不合法", "无"), - EC60106(60106, "邮箱已存在", "无"), - EC60107(60107, "使用该手机登录钉钉的用户已经在企业中", "无"), - EC60110(60110, "部门个数超出限制", "无"), - EC60111(60111, "UserID不存在", "无"), - EC60112(60112, "用户name不合法", "无"), - EC60113(60113, "身份认证信息(手机/邮箱)不能同时为空", "无"), - EC60114(60114, "性别不合法", "无"), - EC60118(60118, "用户无有效邀请字段(邮箱,手机号)", "无"), - EC60119(60119, "不合法的position", "无"), - EC60120(60120, "用户已禁用", "无"), - EC60121(60121, "找不到该用户", "检查该企业下该员工是否存在"), - EC60122(60122, "不合法的extattr", "无"), - EC60123(60123, "不合法的jobnumber", "无"), - EC60124(60124, "用户不在此群中", "无"), - EC60125(60125, "CRM配置信息创建失败", "无"), - EC60126(60126, "CRM配置信息更新失败", "无"), - EC60127(60127, "CRM人员配置信息删除失败", "无"), - EC70001(70001, "企业不存在或者已经被解散", "无"), - EC70002(70002, "获取套件下的微应用失败", "无"), - EC70003(70003, "agentid对应微应用不存在", "无"), - EC70004(70004, "企业下没有对应该agentid的微应用", "注意:代表应用和企业映射关系的ID (appId的实例化ID),同一个ISV应用在不同企业的agentId不一致"), - EC70005(70005, "ISV激活套件失败", "请检查激活套件使用的参数是否正确"), - EC71006(71006, "回调地址已经存在", "无"), - EC71007(71007, "回调地址已不存在", "无"), - EC71008(71008, "回调call_back_tag必须在指定的call_back_tag列表中", "无"), - EC71009(71009, "返回文本非success", "回调地址返回的内容必须是“success”文本经过加密后的结果"), - EC71010(71010, "POST的JSON数据不包含所需要的参数字段或包含的参数格式非法", "无"), - EC71011(71011, "传入的url参数不是合法的url格式", "合法的URL地址是协议+域名+端口+路径path+参数组成"), - EC71012(71012, "url地址访问异常", "无"), - EC71013(71013, "此域名或IP不能注册或者接收回调事件", "注意回调地址的域名或者IP必须在套件的ip白名单中,并且该ip必须为外网ip"), - EC72001(72001, "获取钉盘空间失败", "检查domain、agent_id、access_token参数是否正确有效"), - EC72002(72002, "授权钉盘空间访问权限失败", "无"), - EC80001(80001, "可信域名没有IPC备案,后续将不能在该域名下正常使用jssdk", "无"), - EC81001(81001, "两个用户没有任何关系,请先相互成为好友", "无"), - EC81002(81002, "用户拒收消息", "无"), - EC88005(88005, "管理日历个人日历操作失败", "无"), - EC89001(89001, "管理日历启动导出任务失败", "无"), - EC89011(89011, "管理日历写入数据失败", "无"), - EC89012(89012, "管理日历更新数据失败", "无"), - EC90001(90001, "您的服务器调用钉钉开放平台所有接口的请求都被暂时禁用了", "无"), - EC90002(90002, "您的服务器调用钉钉开放平台当前接口的所有请求都被暂时禁用了", "无"), - EC90003(90003, "您的企业调用钉钉开放平台所有接口的请求都被暂时禁用了,仅对企业自己的Accesstoken有效", "无"), - EC90004(90004, "您当前使用的CorpId及CorpSecret被暂时禁用了,仅对企业自己的Accesstoken有效", "无"), - EC90005(90005, "您的企业调用当前接口次数过多,请求被暂时禁用了,仅对企业自己的Accesstoken有效", "无"), - EC90006(90006, "您当前使用的CorpId及CorpSecret调用当前接口次数过多,请求被暂时禁用了,仅对企业自己的Accesstoken有效", "无"), - EC90007(90007, "您当前要调用的企业的接口次数过多,对该企业的所有请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效", "无"), - EC90008(90008, "您当前要调用的企业的当前接口次数过多,对此企业下该接口的所有请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效", "无"), - EC90009(90009, "您调用企业接口超过了限制,对所有企业的所有接口的请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效", "无"), - EC90010(90010, "您调用企业当前接口超过了限制,对所有企业的该接口的请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效", "无"), - EC90011(90011, "您的套件调用企业接口超过了限制,该套件的所有请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效", "无"), - EC90012(90012, "您的套件调用企业当前接口超过了限制,该套件对此接口的所有请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效", "无"), - EC90013(90013, "您的套件调用当前企业的接口超过了限制,该套件对此企业的所有请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效", "无"), - EC90014(90014, "您的套件调用企业当前接口超过了限制,该套件对此企业该接口的所有请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效", "无"), - EC900001(900001, "加密明文文本非法", "加密明文不能为空"), - EC900002(900002, "加密时间戳参数非法", "加密时间戳不能为空"), - EC900003(900003, "加密随机字符串参数非法", "加密随机字符串不能为空"), - EC900004(900004, "不合法的aeskey", "检查aeskey是否符合规格,长度固定为43个字符,从a-z, A-Z, 0-9共62个字符中选取,是AESKey的Base64编码。解码后即为32字节长的AESKey"), - EC900005(900005, "签名不匹配", "检查签名计算的参数是否正确。请参考文档获取签名参数"), - EC900006(900006, "计算签名错误", "检查签名计算的参数是否正确。请参考文档获取签名参数"), - EC900007(900007, "计算加密文字错误", "检查是否安装JRE补丁或者对应的JRE版本是否正常。 请参考文档ISV应用开发准备工作"), - EC900008(900008, "计算解密文字错误", "检查是否安装JRE补丁或者对应的JRE版本是否正常。 请参考文档ISV应用开发准备工作"), - EC900009(900009, "计算解密文字长度不匹配", "检查aeskey是否符合规格。长度固定为43个字符,从a-z, A-Z, 0-9共62个字符中选取,是AESKey的Base64编码"), - EC900010(900010, "计算解密文字corpid不匹配", "检查corpid是否正确或者是否为当前企业的corpid"), - EC400010(400010, "激活的设备不存在(未绑定)", "无"), - EC400011(400011, "设备已经激活", "无"), - EC400020(400020, "无访问权限", "无"), - EC400021(400021, "密钥错误", "无"), - EC400022(400022, "设备不存在", "无"), - EC400023(400023, "用户不存在", "无"), - EC400040(400040, "回调不存在", "检查是否注册回调事件"), - EC400041(400041, "回调已经存在", "检查该回调事件是否已注册过"), - EC400042(400042, "企业不存在", "无"), - EC400043(400043, "企业不合法", "无"), - EC400050(400050, "回调地址无效", "检查回调地址是否正确或者符合地址格式"), - EC400051(400051, "回调地址访问异常", "注意回调地址必须部署到外网以便开发平台通过回调地址推送回调信息"), - EC400052(400052, "回调地址访返回数据错误", "无"), - EC400053(400053, "回调地址在黑名单中无法注册", "回调地址已添加黑名单,无法注册"), - EC400054(400054, "回调URL访问超时", "无"), - EC400055(400055, "回调设备不在线", "无"), - EC400056(400056, "回调访问设备失败", "无"), - EC400057(400057, "回调访问设备不存在", "无"), - EC420001(420001, "客户不存在", "无"), - EC420002(420002, "客户查询失败", "无"), - EC420003(420003, "联系人不存在", "无"), - EC420004(420004, "联系人查询失败", "无"), - EC420005(420005, "客户删除失败", "无"), - EC420006(420006, "联系人删除失败", "无"), - EC420007(420007, "跟进人绑定失败", "无"), - EC420008(420008, "客户id非法", "无"), - EC420009(420009, "跟进人id非法", "无"), - EC4200010(4200010, "客户联系人id非法", "无"), - EC4200011(4200011, "客户描述表单不存在", "无"), - EC4200012(4200012, "客户描述表单查询失败", "无"), - EC4200013(4200013, "联系人描述表单不存在", "无"), - EC4200014(4200014, "联系人描述表单查询失败", "无"), - EC4200015(4200015, "客户描述表单格式校验错误", "无"), - EC4200016(4200016, "客户描述表单格缺少固定字段", "无"), - EC4200017(4200017, "客户联系人描述表单格式校验错误", "无"), - EC4200018(4200018, "客户联系人描述表单格缺少固定字段", "无"), - EC4200019(4200019, "客户描述表单数据格式校验错误", "无"), - EC4200020(4200020, "客户描述表单数据缺少固定字段", "无"), - EC4200021(4200021, "客户联系人描述表单数据格式校验错误", "无"), - EC4200022(4200022, "客户联系人描述表单数据缺少固定字段", "无"), - EC800001(800001, "仅限ISV调用", "只有ISV微应用才能调用"), - EC41042(41042, "加密失败", "无"), - EC41043(41043, "解密失败", "无"), - EC40100(40100, "分机号已经存在", "无"), - EC40101(40101, "邮箱已经存在", "无"), - EC33013(33013, "企业自建微应用的个数过多,通过接口创建微应用受限", "此限制只针对企业自建微应用,对ISV应用没有限制"), - EC90017(90017, "此IP使用CorpId及CorpSecret调用接口的CorpId个数超过限制", "从该ip发起超过XX个corpid的请求被限制"), - EC40102(40102, "过期的临时授权码", "注意临时授权只能使用一次后就不能在使用。 需要重新执行授权操作有开放平台推送新的临时授权码"), - EC52020(52020, "未找到服务窗授权", "无"), - EC52021(52021, "未找到微应用授权", "无"), - EC52022(52022, "无效的jsapi类型", "无"), - EC52023(52023, "无效的服务窗agentid", "检查服务窗微应用是否停用或者删除"), - EC52024(52024, "无效的jsapi tag", "无"), - EC52025(52025, "无效的安全微应用", "无"), - EC52026(52026, "无效的安全微应用URL", "无"), - EC71014(71014, "获取套件下的服务窗应用失败", "无"), - EC72003(72003, "钉盘空间添加文件失败", "无"), - EC60128(60128, "无效的主管id", "无"), - EC200001(200001, "表单不能为空", "无"), - EC200004(200004, "APP_ID 不允许为空", "app_id为创建套件成功后,创建的ISV微应用的微应用ID。 可以登录开发者后台查看"), - EC200005(200005, "表单名称不允许为空", "无"), - EC200006(200006, "表单内容不允许为空", "无"), - EC200007(200007, "表单值不允许为空", "无"), - EC200008(200008, "表单uuid不存在", "无"), - EC400001(400001, "系统错误", "无"), - EC400002(400002, "参数错误", "检查参数是否符合规格。具体请参考当前接口的文档的参数说明和参数示例"), - EC400003(400003, "时间戳无效", "检查随机时间戳是否符合规格。具体请参考当前接口的文档的参数说明和参数示例"), - EC400004(400004, "随机数无效", "检查随机随机数是否符合规格。具体请参考当前接口的文档的参数说明和参数示例"); - - private int code; - private String desc; - private String solution; - - AuthDingTalkErrorCode(int code, String desc, String solution) { - this.code = code; - this.desc = desc; - this.solution = solution; - } - - public static AuthDingTalkErrorCode getErrorCode(int errorCode) { - AuthDingTalkErrorCode[] errorCodes = AuthDingTalkErrorCode.values(); - for (AuthDingTalkErrorCode code : errorCodes) { - if (code.getCode() == errorCode) { - return code; - } - } - return EC1_MINUS; - } - - public int getCode() { - return code; - } - - public String getDesc() { - return desc; - } - - public String getSolution() { - return solution; - } -} diff --git a/src/main/java/me/zhyd/oauth/enums/AuthToutiaoErrorCode.java b/src/main/java/me/zhyd/oauth/enums/AuthToutiaoErrorCode.java index b8ba6d5..11007b2 100644 --- a/src/main/java/me/zhyd/oauth/enums/AuthToutiaoErrorCode.java +++ b/src/main/java/me/zhyd/oauth/enums/AuthToutiaoErrorCode.java @@ -1,5 +1,8 @@ package me.zhyd.oauth.enums; +import lombok.AllArgsConstructor; +import lombok.Getter; + /** * 今日头条授权登录时的异常状态码 * @@ -7,6 +10,8 @@ package me.zhyd.oauth.enums; * @version 1.0 * @since 1.8 */ +@Getter +@AllArgsConstructor public enum AuthToutiaoErrorCode { EC0(0, "接口调用成功"), EC1(1, "API配置错误,未传入Client Key"), @@ -29,11 +34,6 @@ public enum AuthToutiaoErrorCode { private int code; private String desc; - AuthToutiaoErrorCode(int code, String desc) { - this.code = code; - this.desc = desc; - } - public static AuthToutiaoErrorCode getErrorCode(int errorCode) { AuthToutiaoErrorCode[] errorCodes = AuthToutiaoErrorCode.values(); for (AuthToutiaoErrorCode code : errorCodes) { @@ -43,12 +43,4 @@ public enum AuthToutiaoErrorCode { } return EC999; } - - public int getCode() { - return code; - } - - public String getDesc() { - return desc; - } } diff --git a/src/main/java/me/zhyd/oauth/enums/AuthUserGender.java b/src/main/java/me/zhyd/oauth/enums/AuthUserGender.java new file mode 100644 index 0000000..3e39e3d --- /dev/null +++ b/src/main/java/me/zhyd/oauth/enums/AuthUserGender.java @@ -0,0 +1,36 @@ +package me.zhyd.oauth.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 用户性别 + * + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @version 1.0 + * @since 1.8 + */ +@Getter +@AllArgsConstructor +public enum AuthUserGender { + MALE(1, "男"), FEMALE(0, "女"), UNKNOWN(-1, "未知"); + private int code; + private String desc; + + public static AuthUserGender getRealGender(String code) { + if (code == null) { + return UNKNOWN; + } + String[] males = {"m", "男", "1", "male"}; + if (Arrays.asList(males).contains(code.toLowerCase())) { + return MALE; + } + String[] females = {"f", "女", "0", "female"}; + if (Arrays.asList(females).contains(code.toLowerCase())) { + return FEMALE; + } + return UNKNOWN; + } +} diff --git a/src/main/java/me/zhyd/oauth/exception/AuthException.java b/src/main/java/me/zhyd/oauth/exception/AuthException.java index aed33b2..f4f7473 100644 --- a/src/main/java/me/zhyd/oauth/exception/AuthException.java +++ b/src/main/java/me/zhyd/oauth/exception/AuthException.java @@ -1,6 +1,6 @@ package me.zhyd.oauth.exception; -import me.zhyd.oauth.request.ResponseStatus; +import me.zhyd.oauth.model.AuthResponseStatus; /** * @author yadong.zhang (yadong.zhang0415(a)gmail.com) @@ -13,16 +13,16 @@ public class AuthException extends RuntimeException { private String errorMsg; public AuthException(String errorMsg) { - this(ResponseStatus.FAILURE.getCode(), errorMsg); + this(AuthResponseStatus.FAILURE.getCode(), errorMsg); } public AuthException(int errorCode, String errorMsg) { - super(errorCode + ":" + errorMsg); + super(errorMsg); this.errorCode = errorCode; this.errorMsg = errorMsg; } - public AuthException(ResponseStatus status) { + public AuthException(AuthResponseStatus status) { super(status.getMsg()); } @@ -30,6 +30,10 @@ public class AuthException extends RuntimeException { super(message, cause); } + public AuthException(Throwable cause) { + super(cause); + } + public int getErrorCode() { return errorCode; } diff --git a/src/main/java/me/zhyd/oauth/model/AuthResponse.java b/src/main/java/me/zhyd/oauth/model/AuthResponse.java index 04f9ceb..484a743 100644 --- a/src/main/java/me/zhyd/oauth/model/AuthResponse.java +++ b/src/main/java/me/zhyd/oauth/model/AuthResponse.java @@ -3,7 +3,6 @@ package me.zhyd.oauth.model; import lombok.Builder; import lombok.Getter; import lombok.Setter; -import me.zhyd.oauth.request.ResponseStatus; /** * JustAuth统一授权响应类 @@ -13,7 +12,6 @@ import me.zhyd.oauth.request.ResponseStatus; * @since 1.8 */ @Getter -@Setter @Builder public class AuthResponse { /** @@ -37,6 +35,6 @@ public class AuthResponse { * @return true or false */ public boolean ok() { - return this.code == ResponseStatus.SUCCESS.getCode(); + return this.code == AuthResponseStatus.SUCCESS.getCode(); } } diff --git a/src/main/java/me/zhyd/oauth/request/ResponseStatus.java b/src/main/java/me/zhyd/oauth/model/AuthResponseStatus.java similarity index 69% rename from src/main/java/me/zhyd/oauth/request/ResponseStatus.java rename to src/main/java/me/zhyd/oauth/model/AuthResponseStatus.java index 82a78c4..21ca6f6 100644 --- a/src/main/java/me/zhyd/oauth/request/ResponseStatus.java +++ b/src/main/java/me/zhyd/oauth/model/AuthResponseStatus.java @@ -1,11 +1,16 @@ -package me.zhyd.oauth.request; +package me.zhyd.oauth.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; /** * @author yadong.zhang (yadong.zhang0415(a)gmail.com) * @version 1.0 * @since 1.8 */ -public enum ResponseStatus { +@Getter +@AllArgsConstructor +public enum AuthResponseStatus { SUCCESS(2000, "Success"), FAILURE(5000, "Failure"), NOT_IMPLEMENTED(5001, "Not Implemented"), @@ -20,18 +25,5 @@ public enum ResponseStatus { private int code; private String msg; - - ResponseStatus(int code, String msg) { - this.code = code; - this.msg = msg; - } - - public int getCode() { - return code; - } - - public String getMsg() { - return msg; - } } diff --git a/src/main/java/me/zhyd/oauth/model/AuthUser.java b/src/main/java/me/zhyd/oauth/model/AuthUser.java index 1db92ec..ad64129 100644 --- a/src/main/java/me/zhyd/oauth/model/AuthUser.java +++ b/src/main/java/me/zhyd/oauth/model/AuthUser.java @@ -1,10 +1,10 @@ package me.zhyd.oauth.model; import lombok.Builder; -import lombok.Data; import lombok.Getter; import lombok.Setter; import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; /** * 授权成功后的用户信息,根据授权平台的不同,获取的数据完整性也不同 diff --git a/src/main/java/me/zhyd/oauth/model/AuthUserGender.java b/src/main/java/me/zhyd/oauth/model/AuthUserGender.java deleted file mode 100644 index 910a66a..0000000 --- a/src/main/java/me/zhyd/oauth/model/AuthUserGender.java +++ /dev/null @@ -1,44 +0,0 @@ -package me.zhyd.oauth.model; - -import java.util.Arrays; - -/** - * 用户性别 - * - * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 - */ -public enum AuthUserGender { - MALE(1, "男"), FEMALE(0, "女"), UNKNOW(-1, "未知"); - private int code; - private String desc; - - AuthUserGender(int code, String desc) { - this.code = code; - this.desc = desc; - } - - public static AuthUserGender getRealGender(String code) { - if (code == null) { - return UNKNOW; - } - String[] males = {"m", "男", "1", "male", "F"}; - if (Arrays.asList(males).contains(code)) { - return MALE; - } - String[] females = {"f", "女", "0", "female"}; - if (Arrays.asList(females).contains(code)) { - return FEMALE; - } - return UNKNOW; - } - - public int getCode() { - return code; - } - - public String getDesc() { - return desc; - } -} diff --git a/src/main/java/me/zhyd/oauth/request/AuthAlipayRequest.java b/src/main/java/me/zhyd/oauth/request/AuthAlipayRequest.java index fbcb952..18b49ad 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthAlipayRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthAlipayRequest.java @@ -9,11 +9,11 @@ import com.alipay.api.response.AlipaySystemOauthTokenResponse; import com.alipay.api.response.AlipayUserInfoShareResponse; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthUser; -import me.zhyd.oauth.model.AuthUserGender; import me.zhyd.oauth.utils.StringUtils; import me.zhyd.oauth.utils.UrlBuilder; @@ -24,14 +24,14 @@ import me.zhyd.oauth.utils.UrlBuilder; * @version 1.0 * @since 1.8 */ -public class AuthAlipayRequest extends BaseAuthRequest { +public class AuthAlipayRequest extends AuthDefaultRequest { private AlipayClient alipayClient; public AuthAlipayRequest(AuthConfig config) { super(config, AuthSource.ALIPAY); this.alipayClient = new DefaultAlipayClient(AuthSource.ALIPAY.accessToken(), config.getClientId(), config.getClientSecret(), "json", "UTF-8", config - .getAlipayPublicKey(), "RSA2"); + .getAlipayPublicKey(), "RSA2"); } @Override @@ -43,17 +43,17 @@ public class AuthAlipayRequest extends BaseAuthRequest { try { response = this.alipayClient.execute(request); } catch (Exception e) { - throw new AuthException("Unable to get token from alipay using code [" + authCallback.getAuth_code() + "]", e); + throw new AuthException(e); } if (!response.isSuccess()) { throw new AuthException(response.getSubMsg()); } return AuthToken.builder() - .accessToken(response.getAccessToken()) - .uid(response.getUserId()) - .expireIn(Integer.parseInt(response.getExpiresIn())) - .refreshToken(response.getRefreshToken()) - .build(); + .accessToken(response.getAccessToken()) + .uid(response.getUserId()) + .expireIn(Integer.parseInt(response.getExpiresIn())) + .refreshToken(response.getRefreshToken()) + .build(); } @Override @@ -70,20 +70,19 @@ public class AuthAlipayRequest extends BaseAuthRequest { throw new AuthException(response.getSubMsg()); } - String province = response.getProvince(), - city = response.getCity(); + String province = response.getProvince(), city = response.getCity(); String location = String.format("%s %s", StringUtils.isEmpty(province) ? "" : province, StringUtils.isEmpty(city) ? "" : city); return AuthUser.builder() - .uuid(response.getUserId()) - .username(StringUtils.isEmpty(response.getUserName()) ? response.getNickName() : response.getUserName()) - .nickname(response.getNickName()) - .avatar(response.getAvatar()) - .location(location) - .gender(AuthUserGender.getRealGender(response.getGender())) - .token(authToken) - .source(AuthSource.ALIPAY) - .build(); + .uuid(response.getUserId()) + .username(StringUtils.isEmpty(response.getUserName()) ? response.getNickName() : response.getUserName()) + .nickname(response.getNickName()) + .avatar(response.getAvatar()) + .location(location) + .gender(AuthUserGender.getRealGender(response.getGender())) + .token(authToken) + .source(AuthSource.ALIPAY) + .build(); } /** @@ -93,6 +92,11 @@ public class AuthAlipayRequest extends BaseAuthRequest { */ @Override public String authorize() { - return UrlBuilder.getAlipayAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); + return UrlBuilder.fromBaseUrl(source.authorize()) + .queryParam("app_id", config.getClientId()) + .queryParam("scope", "auth_user") + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("state", getRealState(config.getState())) + .build(); } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthBaiduRequest.java b/src/main/java/me/zhyd/oauth/request/AuthBaiduRequest.java index 46f6a90..92d97a3 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthBaiduRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthBaiduRequest.java @@ -5,7 +5,7 @@ import cn.hutool.http.HttpResponse; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; -import me.zhyd.oauth.enums.AuthBaiduErrorCode; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.model.*; import me.zhyd.oauth.utils.UrlBuilder; @@ -17,7 +17,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * @version 1.0 * @since 1.8 */ -public class AuthBaiduRequest extends BaseAuthRequest { +public class AuthBaiduRequest extends AuthDefaultRequest { public AuthBaiduRequest(AuthConfig config) { super(config, AuthSource.BAIDU); @@ -25,40 +25,51 @@ public class AuthBaiduRequest extends BaseAuthRequest { @Override protected AuthToken getAccessToken(AuthCallback authCallback) { - String accessTokenUrl = UrlBuilder.getBaiduAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode(), config - .getRedirectUri()); - HttpResponse response = HttpRequest.post(accessTokenUrl).execute(); - JSONObject accessTokenObject = JSONObject.parseObject(response.body()); - AuthBaiduErrorCode errorCode = AuthBaiduErrorCode.getErrorCode(accessTokenObject.getString("error")); - if (AuthBaiduErrorCode.OK != errorCode) { - throw new AuthException(errorCode.getDesc()); - } - return AuthToken.builder() - .accessToken(accessTokenObject.getString("access_token")) - .refreshToken(accessTokenObject.getString("refresh_token")) - .scope(accessTokenObject.getString("scope")) - .expireIn(accessTokenObject.getIntValue("expires_in")) - .build(); + HttpResponse response = doPostAuthorizationCode(authCallback.getCode()); + return getAuthToken(response); } @Override protected AuthUser getUserInfo(AuthToken authToken) { - String accessToken = authToken.getAccessToken(); - HttpResponse response = HttpRequest.get(UrlBuilder.getBaiduUserInfoUrl(accessToken)).execute(); + HttpResponse response = doGetUserInfo(authToken); String userInfo = response.body(); JSONObject object = JSONObject.parseObject(userInfo); - AuthBaiduErrorCode errorCode = AuthBaiduErrorCode.getErrorCode(object.getString("error")); - if (AuthBaiduErrorCode.OK != errorCode) { - throw new AuthException(errorCode.getDesc()); - } + this.checkResponse(object); return AuthUser.builder() - .uuid(object.getString("userid")) - .username(object.getString("username")) - .nickname(object.getString("username")) - .gender(AuthUserGender.getRealGender(object.getString("sex"))) - .token(authToken) - .source(AuthSource.BAIDU) - .build(); + .uuid(object.getString("userid")) + .username(object.getString("username")) + .nickname(object.getString("username")) + .avatar(object.getString("portrait")) + .remark(object.getString("userdetail")) + .gender(AuthUserGender.getRealGender(object.getString("sex"))) + .token(authToken) + .source(AuthSource.BAIDU) + .build(); + } + + @Override + public AuthResponse revoke(AuthToken authToken) { + HttpResponse response = doGetRevoke(authToken); + JSONObject object = JSONObject.parseObject(response.body()); + this.checkResponse(object); + // 返回1表示取消授权成功,否则失败 + AuthResponseStatus status = object.getIntValue("result") == 1 ? AuthResponseStatus.SUCCESS : AuthResponseStatus.FAILURE; + return AuthResponse.builder().code(status.getCode()).msg(status.getMsg()).build(); + } + + @Override + public AuthResponse refresh(AuthToken authToken) { + String refreshUrl = UrlBuilder.fromBaseUrl(this.source.refresh()) + .queryParam("grant_type", "refresh_token") + .queryParam("refresh_token", authToken.getRefreshToken()) + .queryParam("client_id", this.config.getClientId()) + .queryParam("client_secret", this.config.getClientSecret()) + .build(); + HttpResponse response = HttpRequest.get(refreshUrl).execute(); + return AuthResponse.builder() + .code(AuthResponseStatus.SUCCESS.getCode()) + .data(this.getAuthToken(response)) + .build(); } /** @@ -68,23 +79,35 @@ public class AuthBaiduRequest extends BaseAuthRequest { */ @Override public String authorize() { - return UrlBuilder.getBaiduAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); + return UrlBuilder.fromBaseUrl(source.authorize()) + .queryParam("response_type", "code") + .queryParam("client_id", config.getClientId()) + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("display", "popup") + .queryParam("state", getRealState(config.getState())) + .build(); } - @Override - public AuthResponse revoke(AuthToken authToken) { - String accessToken = authToken.getAccessToken(); - HttpResponse response = HttpRequest.get(UrlBuilder.getBaiduRevokeUrl(accessToken)).execute(); - String userInfo = response.body(); - JSONObject object = JSONObject.parseObject(userInfo); - if (object.containsKey("error_code")) { - return AuthResponse.builder() - .code(ResponseStatus.FAILURE.getCode()) - .msg(object.getString("error_msg")) - .build(); + /** + * 检查响应内容是否正确 + * + * @param object 请求响应内容 + */ + private void checkResponse(JSONObject object) { + if (object.containsKey("error") || object.containsKey("error_code")) { + String msg = object.containsKey("error_description") ? object.getString("error_description") : object.getString("error_msg"); + throw new AuthException(msg); } - ResponseStatus status = object.getIntValue("result") == 1 ? ResponseStatus.SUCCESS : ResponseStatus.FAILURE; - return AuthResponse.builder().code(status.getCode()).msg(status.getMsg()).build(); } + private AuthToken getAuthToken(HttpResponse response) { + JSONObject accessTokenObject = JSONObject.parseObject(response.body()); + this.checkResponse(accessTokenObject); + return AuthToken.builder() + .accessToken(accessTokenObject.getString("access_token")) + .refreshToken(accessTokenObject.getString("refresh_token")) + .scope(accessTokenObject.getString("scope")) + .expireIn(accessTokenObject.getIntValue("expires_in")) + .build(); + } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthCodingRequest.java b/src/main/java/me/zhyd/oauth/request/AuthCodingRequest.java index 690a5ee..070d57b 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthCodingRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthCodingRequest.java @@ -1,6 +1,5 @@ package me.zhyd.oauth.request; -import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; @@ -9,7 +8,7 @@ import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthUser; -import me.zhyd.oauth.model.AuthUserGender; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.utils.UrlBuilder; /** @@ -19,7 +18,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * @version 1.0 * @since 1.8 */ -public class AuthCodingRequest extends BaseAuthRequest { +public class AuthCodingRequest extends AuthDefaultRequest { public AuthCodingRequest(AuthConfig config) { super(config, AuthSource.CODING); @@ -27,43 +26,48 @@ public class AuthCodingRequest extends BaseAuthRequest { @Override protected AuthToken getAccessToken(AuthCallback authCallback) { - String accessTokenUrl = UrlBuilder.getCodingAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode()); - HttpResponse response = HttpRequest.get(accessTokenUrl).execute(); + HttpResponse response = doGetAuthorizationCode(authCallback.getCode()); JSONObject accessTokenObject = JSONObject.parseObject(response.body()); - if (accessTokenObject.getIntValue("code") != 0) { - throw new AuthException("Unable to get token from coding using code [" + authCallback.getCode() + "]"); - } + this.checkResponse(accessTokenObject); return AuthToken.builder() - .accessToken(accessTokenObject.getString("access_token")) - .expireIn(accessTokenObject.getIntValue("expires_in")) - .refreshToken(accessTokenObject.getString("refresh_token")) - .build(); + .accessToken(accessTokenObject.getString("access_token")) + .expireIn(accessTokenObject.getIntValue("expires_in")) + .refreshToken(accessTokenObject.getString("refresh_token")) + .build(); } @Override protected AuthUser getUserInfo(AuthToken authToken) { - String accessToken = authToken.getAccessToken(); - HttpResponse response = HttpRequest.get(UrlBuilder.getCodingUserInfoUrl(accessToken)).execute(); + HttpResponse response = doGetUserInfo(authToken); JSONObject object = JSONObject.parseObject(response.body()); - if (object.getIntValue("code") != 0) { - throw new AuthException(object.getString("msg")); - } + this.checkResponse(object); object = object.getJSONObject("data"); return AuthUser.builder() - .uuid(object.getString("id")) - .username(object.getString("name")) - .avatar("https://coding.net/" + object.getString("avatar")) - .blog("https://coding.net/" + object.getString("path")) - .nickname(object.getString("name")) - .company(object.getString("company")) - .location(object.getString("location")) - .gender(AuthUserGender.getRealGender(object.getString("sex"))) - .email(object.getString("email")) - .remark(object.getString("slogan")) - .token(authToken) - .source(AuthSource.CODING) - .build(); + .uuid(object.getString("id")) + .username(object.getString("name")) + .avatar("https://coding.net/" + object.getString("avatar")) + .blog("https://coding.net/" + object.getString("path")) + .nickname(object.getString("name")) + .company(object.getString("company")) + .location(object.getString("location")) + .gender(AuthUserGender.getRealGender(object.getString("sex"))) + .email(object.getString("email")) + .remark(object.getString("slogan")) + .token(authToken) + .source(AuthSource.CODING) + .build(); + } + + /** + * 检查响应内容是否正确 + * + * @param object 请求响应内容 + */ + private void checkResponse(JSONObject object) { + if (object.getIntValue("code") != 0) { + throw new AuthException(object.getString("msg")); + } } /** @@ -73,6 +77,12 @@ public class AuthCodingRequest extends BaseAuthRequest { */ @Override public String authorize() { - return UrlBuilder.getCodingAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); + return UrlBuilder.fromBaseUrl(source.authorize()) + .queryParam("response_type", "code") + .queryParam("client_id", config.getClientId()) + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("scope", "user") + .queryParam("state", getRealState(config.getState())) + .build(); } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthCsdnRequest.java b/src/main/java/me/zhyd/oauth/request/AuthCsdnRequest.java index 43817c2..9043f04 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthCsdnRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthCsdnRequest.java @@ -1,16 +1,14 @@ package me.zhyd.oauth.request; -import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthUser; -import me.zhyd.oauth.model.AuthUserGender; -import me.zhyd.oauth.utils.UrlBuilder; /** * CSDN登录 @@ -19,7 +17,8 @@ import me.zhyd.oauth.utils.UrlBuilder; * @version 1.0 * @since 1.8 */ -public class AuthCsdnRequest extends BaseAuthRequest { +@Deprecated +public class AuthCsdnRequest extends AuthDefaultRequest { public AuthCsdnRequest(AuthConfig config) { super(config, AuthSource.CSDN); @@ -27,42 +26,36 @@ public class AuthCsdnRequest extends BaseAuthRequest { @Override protected AuthToken getAccessToken(AuthCallback authCallback) { - String accessTokenUrl = UrlBuilder.getCsdnAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode(), config - .getRedirectUri()); - HttpResponse response = HttpRequest.post(accessTokenUrl).execute(); + HttpResponse response = doPostAuthorizationCode(authCallback.getCode()); JSONObject accessTokenObject = JSONObject.parseObject(response.body()); - if (accessTokenObject.containsKey("error_code")) { - throw new AuthException("Unable to get token from csdn using code [" + authCallback.getCode() + "]"); - } + this.checkResponse(accessTokenObject); return AuthToken.builder().accessToken(accessTokenObject.getString("access_token")).build(); } @Override protected AuthUser getUserInfo(AuthToken authToken) { - String accessToken = authToken.getAccessToken(); - HttpResponse response = HttpRequest.get(UrlBuilder.getCsdnUserInfoUrl(accessToken)).execute(); + HttpResponse response = doGetUserInfo(authToken); JSONObject object = JSONObject.parseObject(response.body()); - if (object.containsKey("error_code")) { - throw new AuthException(object.getString("error")); - } + this.checkResponse(object); return AuthUser.builder() - .uuid(object.getString("username")) - .username(object.getString("username")) - .remark(object.getString("description")) - .blog(object.getString("website")) - .gender(AuthUserGender.UNKNOW) - .token(authToken) - .source(AuthSource.CSDN) - .build(); + .uuid(object.getString("username")) + .username(object.getString("username")) + .remark(object.getString("description")) + .blog(object.getString("website")) + .gender(AuthUserGender.UNKNOWN) + .token(authToken) + .source(AuthSource.CSDN) + .build(); } /** - * 返回认证url,可自行跳转页面 + * 检查响应内容是否正确 * - * @return 返回授权地址 + * @param object 请求响应内容 */ - @Override - public String authorize() { - return UrlBuilder.getCsdnAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); + private void checkResponse(JSONObject object) { + if (object.containsKey("error_code")) { + throw new AuthException(object.getString("error")); + } } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthDefaultRequest.java b/src/main/java/me/zhyd/oauth/request/AuthDefaultRequest.java new file mode 100644 index 0000000..9f022fe --- /dev/null +++ b/src/main/java/me/zhyd/oauth/request/AuthDefaultRequest.java @@ -0,0 +1,198 @@ +package me.zhyd.oauth.request; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.model.*; +import me.zhyd.oauth.utils.AuthChecker; +import me.zhyd.oauth.utils.StringUtils; +import me.zhyd.oauth.utils.UrlBuilder; + +/** + * 默认的request处理类 + * + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @author yangkai.shen (https://xkcoding.com) + * @version 1.0 + * @since 1.8 + */ +@Data +@Slf4j +public abstract class AuthDefaultRequest implements AuthRequest { + protected AuthConfig config; + protected AuthSource source; + + public AuthDefaultRequest(AuthConfig config, AuthSource source) { + this.config = config; + this.source = source; + if (!AuthChecker.isSupportedAuth(config, source)) { + throw new AuthException(AuthResponseStatus.PARAMETER_INCOMPLETE); + } + // 校验配置合法性 + AuthChecker.checkConfig(config, source); + } + + protected abstract AuthToken getAccessToken(AuthCallback authCallback); + + protected abstract AuthUser getUserInfo(AuthToken authToken); + + @Override + public AuthResponse login(AuthCallback authCallback) { + try { + AuthChecker.checkCode(source == AuthSource.ALIPAY ? authCallback.getAuth_code() : authCallback.getCode()); + AuthChecker.checkState(authCallback.getState(), config.getState()); + + AuthToken authToken = this.getAccessToken(authCallback); + AuthUser user = this.getUserInfo(authToken); + return AuthResponse.builder().code(AuthResponseStatus.SUCCESS.getCode()).data(user).build(); + } catch (Exception e) { + log.error("Failed to login with oauth authorization.", e); + return this.responseError(e); + } + } + + private AuthResponse responseError(Exception e) { + int errorCode = AuthResponseStatus.FAILURE.getCode(); + if (e instanceof AuthException) { + errorCode = ((AuthException) e).getErrorCode(); + } + return AuthResponse.builder().code(errorCode).msg(e.getMessage()).build(); + } + + /** + * 返回认证url,可自行跳转页面 + * + * @return 返回授权地址 + */ + @Override + public String authorize() { + return UrlBuilder.fromBaseUrl(source.authorize()) + .queryParam("response_type", "code") + .queryParam("client_id", config.getClientId()) + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("state", getRealState(config.getState())) + .build(); + } + + /** + * 返回获取accessToken的url + * + * @return 返回获取accessToken的url + */ + protected String accessTokenUrl(String code) { + return UrlBuilder.fromBaseUrl(source.accessToken()) + .queryParam("code", code) + .queryParam("client_id", config.getClientId()) + .queryParam("client_secret", config.getClientSecret()) + .queryParam("grant_type", "authorization_code") + .queryParam("redirect_uri", config.getRedirectUri()) + .build(); + } + + /** + * 返回获取accessToken的url + * + * @return 返回获取accessToken的url + */ + protected String refreshTokenUrl(String refreshToken) { + return UrlBuilder.fromBaseUrl(source.refresh()) + .queryParam("client_id", config.getClientId()) + .queryParam("client_secret", config.getClientSecret()) + .queryParam("refresh_token", refreshToken) + .queryParam("grant_type", "refresh_token") + .queryParam("redirect_uri", config.getRedirectUri()) + .build(); + } + + /** + * 返回获取userInfo的url + * + * @return 返回获取userInfo的url + */ + protected String userInfoUrl(AuthToken authToken) { + return UrlBuilder.fromBaseUrl(source.userInfo()).queryParam("access_token", authToken.getAccessToken()).build(); + } + + /** + * 返回获取revoke authorization的url + * + * @return 返回获取revoke authorization的url + */ + protected String revokeUrl(AuthToken authToken) { + return UrlBuilder.fromBaseUrl(source.revoke()).queryParam("access_token", authToken.getAccessToken()).build(); + } + + /** + * 获取state,如果为空, 则默认去当前日期的时间戳 + * + * @param state 原始的state + * @return 返回不为null的state + */ + protected String getRealState(String state) { + return StringUtils.isEmpty(state) ? String.valueOf(System.currentTimeMillis()) : state; + } + + /** + * 通用的 authorizationCode 协议 + * + * @param code code码 + * @return HttpResponse + */ + protected HttpResponse doPostAuthorizationCode(String code) { + return HttpRequest.post(accessTokenUrl(code)).execute(); + } + + /** + * 通用的 authorizationCode 协议 + * + * @param code code码 + * @return HttpResponse + */ + protected HttpResponse doGetAuthorizationCode(String code) { + return HttpRequest.get(accessTokenUrl(code)).execute(); + } + + /** + * 通用的 用户信息 + * + * @param authToken token封装 + * @return HttpResponse + */ + protected HttpResponse doPostUserInfo(AuthToken authToken) { + return HttpRequest.post(userInfoUrl(authToken)).execute(); + } + + /** + * 通用的 用户信息 + * + * @param authToken token封装 + * @return HttpResponse + */ + protected HttpResponse doGetUserInfo(AuthToken authToken) { + return HttpRequest.get(userInfoUrl(authToken)).execute(); + } + + /** + * 通用的post形式的取消授权方法 + * + * @param authToken token封装 + * @return HttpResponse + */ + protected HttpResponse doPostRevoke(AuthToken authToken) { + return HttpRequest.post(revokeUrl(authToken)).execute(); + } + + /** + * 通用的post形式的取消授权方法 + * + * @param authToken token封装 + * @return HttpResponse + */ + protected HttpResponse doGetRevoke(AuthToken authToken) { + return HttpRequest.get(revokeUrl(authToken)).execute(); + } +} diff --git a/src/main/java/me/zhyd/oauth/request/AuthDingTalkRequest.java b/src/main/java/me/zhyd/oauth/request/AuthDingTalkRequest.java index ff802cc..69956a7 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthDingTalkRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthDingTalkRequest.java @@ -6,9 +6,11 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; -import me.zhyd.oauth.enums.AuthDingTalkErrorCode; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.exception.AuthException; -import me.zhyd.oauth.model.*; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthToken; +import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.utils.GlobalAuthUtil; import me.zhyd.oauth.utils.UrlBuilder; @@ -19,7 +21,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * @version 1.0 * @since 1.8 */ -public class AuthDingTalkRequest extends BaseAuthRequest { +public class AuthDingTalkRequest extends AuthDefaultRequest { public AuthDingTalkRequest(AuthConfig config) { super(config, AuthSource.DINGTALK); @@ -33,32 +35,26 @@ public class AuthDingTalkRequest extends BaseAuthRequest { @Override protected AuthUser getUserInfo(AuthToken authToken) { String code = authToken.getAccessCode(); - // 根据timestamp, appSecret计算签名值 - String timestamp = System.currentTimeMillis() + ""; - String urlEncodeSignature = GlobalAuthUtil.generateDingTalkSignature(config.getClientSecret(), timestamp); JSONObject param = new JSONObject(); param.put("tmp_auth_code", code); - HttpResponse response = HttpRequest.post(UrlBuilder.getDingTalkUserInfoUrl(urlEncodeSignature, timestamp, config - .getClientId())).body(param.toJSONString()).execute(); - String userInfo = response.body(); - JSONObject object = JSON.parseObject(userInfo); - AuthDingTalkErrorCode errorCode = AuthDingTalkErrorCode.getErrorCode(object.getIntValue("errcode")); - if (AuthDingTalkErrorCode.EC0 != errorCode) { - throw new AuthException(errorCode.getDesc()); + HttpResponse response = HttpRequest.post(userInfoUrl(authToken)).body(param.toJSONString()).execute(); + JSONObject object = JSON.parseObject(response.body()); + if (object.getIntValue("errcode") != 0) { + throw new AuthException(object.getString("errmsg")); } object = object.getJSONObject("user_info"); AuthToken token = AuthToken.builder() - .openId(object.getString("openid")) - .unionId(object.getString("unionid")) - .build(); + .openId(object.getString("openid")) + .unionId(object.getString("unionid")) + .build(); return AuthUser.builder() - .uuid(object.getString("unionid")) - .nickname(object.getString("nick")) - .username(object.getString("nick")) - .gender(AuthUserGender.UNKNOW) - .source(AuthSource.DINGTALK) - .token(token) - .build(); + .uuid(object.getString("unionid")) + .nickname(object.getString("nick")) + .username(object.getString("nick")) + .gender(AuthUserGender.UNKNOWN) + .source(AuthSource.DINGTALK) + .token(token) + .build(); } /** @@ -68,6 +64,31 @@ public class AuthDingTalkRequest extends BaseAuthRequest { */ @Override public String authorize() { - return UrlBuilder.getDingTalkQrConnectUrl(config.getClientId(), config.getRedirectUri(), config.getState()); + return UrlBuilder.fromBaseUrl(source.authorize()) + .queryParam("response_type", "code") + .queryParam("appid", config.getClientId()) + .queryParam("scope", "snsapi_login") + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("state", getRealState(config.getState())) + .build(); + } + + /** + * 返回获取userInfo的url + * + * @param authToken + * @return 返回获取userInfo的url + */ + @Override + protected String userInfoUrl(AuthToken authToken) { + // 根据timestamp, appSecret计算签名值 + String timestamp = System.currentTimeMillis() + ""; + String urlEncodeSignature = GlobalAuthUtil.generateDingTalkSignature(config.getClientSecret(), timestamp); + + return UrlBuilder.fromBaseUrl(source.userInfo()) + .queryParam("signature", urlEncodeSignature) + .queryParam("timestamp", timestamp) + .queryParam("accessKey", config.getClientId()) + .build(); } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthDouyinRequest.java b/src/main/java/me/zhyd/oauth/request/AuthDouyinRequest.java index cf29f19..9c46b4f 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthDouyinRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthDouyinRequest.java @@ -5,6 +5,7 @@ import cn.hutool.http.HttpResponse; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.model.*; import me.zhyd.oauth.utils.UrlBuilder; @@ -17,7 +18,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * @version 1.0 * @since 1.8 */ -public class AuthDouyinRequest extends BaseAuthRequest { +public class AuthDouyinRequest extends AuthDefaultRequest { public AuthDouyinRequest(AuthConfig config) { super(config, AuthSource.DOUYIN); @@ -25,65 +26,46 @@ public class AuthDouyinRequest extends BaseAuthRequest { @Override protected AuthToken getAccessToken(AuthCallback authCallback) { - String accessTokenUrl = UrlBuilder.getDouyinAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode()); - return this.getToken(accessTokenUrl); + return this.getToken(accessTokenUrl(authCallback.getCode())); } @Override protected AuthUser getUserInfo(AuthToken authToken) { - String accessToken = authToken.getAccessToken(); - String openId = authToken.getOpenId(); - String url = UrlBuilder.getDouyinUserInfoUrl(accessToken, openId); - HttpResponse response = HttpRequest.get(url).execute(); - JSONObject object = JSONObject.parseObject(response.body()); - - JSONObject userInfoObject = this.checkResponse(object); - + HttpResponse response = doGetUserInfo(authToken); + JSONObject userInfoObject = JSONObject.parseObject(response.body()); + this.checkResponse(userInfoObject); return AuthUser.builder() - .uuid(userInfoObject.getString("union_id")) - .username(userInfoObject.getString("nickname")) - .nickname(userInfoObject.getString("nickname")) - .avatar(userInfoObject.getString("avatar")) - .remark(userInfoObject.getString("description")) - .gender(AuthUserGender.UNKNOW) - .token(authToken) - .source(AuthSource.DOUYIN) - .build(); - } - - /** - * 返回认证url,可自行跳转页面 - * - * @return 返回授权地址 - */ - @Override - public String authorize() { - return UrlBuilder.getDouyinAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); + .uuid(userInfoObject.getString("union_id")) + .username(userInfoObject.getString("nickname")) + .nickname(userInfoObject.getString("nickname")) + .avatar(userInfoObject.getString("avatar")) + .remark(userInfoObject.getString("description")) + .gender(AuthUserGender.UNKNOWN) + .token(authToken) + .source(AuthSource.DOUYIN) + .build(); } @Override public AuthResponse refresh(AuthToken oldToken) { - String refreshTokenUrl = UrlBuilder.getDouyinRefreshUrl(config.getClientId(), oldToken.getRefreshToken()); return AuthResponse.builder() - .code(ResponseStatus.SUCCESS.getCode()) - .data(this.getToken(refreshTokenUrl)) - .build(); + .code(AuthResponseStatus.SUCCESS.getCode()) + .data(getToken(refreshTokenUrl(oldToken.getRefreshToken()))) + .build(); } /** * 检查响应内容是否正确 * * @param object 请求响应内容 - * @return 实际请求数据的json对象 */ - private JSONObject checkResponse(JSONObject object) { + private void checkResponse(JSONObject object) { String message = object.getString("message"); JSONObject data = object.getJSONObject("data"); int errorCode = data.getIntValue("error_code"); if ("error".equals(message) || errorCode != 0) { throw new AuthException(errorCode, data.getString("description")); } - return data; } /** @@ -96,14 +78,74 @@ public class AuthDouyinRequest extends BaseAuthRequest { HttpResponse response = HttpRequest.post(accessTokenUrl).execute(); String accessTokenStr = response.body(); JSONObject object = JSONObject.parseObject(accessTokenStr); - - JSONObject accessTokenObject = this.checkResponse(object); + this.checkResponse(object); return AuthToken.builder() - .accessToken(accessTokenObject.getString("access_token")) - .openId(accessTokenObject.getString("open_id")) - .expireIn(accessTokenObject.getIntValue("expires_in")) - .refreshToken(accessTokenObject.getString("refresh_token")) - .scope(accessTokenObject.getString("scope")) - .build(); + .accessToken(object.getString("access_token")) + .openId(object.getString("open_id")) + .expireIn(object.getIntValue("expires_in")) + .refreshToken(object.getString("refresh_token")) + .scope(object.getString("scope")) + .build(); + } + + /** + * 返回认证url,可自行跳转页面 + * + * @return 返回授权地址 + */ + @Override + public String authorize() { + return UrlBuilder.fromBaseUrl(source.authorize()) + .queryParam("response_type", "code") + .queryParam("client_key", config.getClientId()) + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("state", getRealState(config.getState())) + .queryParam("scope", "user_info") + .build(); + } + + /** + * 返回获取accessToken的url + * + * @param code oauth的授权码 + * @return 返回获取accessToken的url + */ + @Override + protected String accessTokenUrl(String code) { + return UrlBuilder.fromBaseUrl(source.accessToken()) + .queryParam("code", code) + .queryParam("client_key", config.getClientId()) + .queryParam("client_secret", config.getClientSecret()) + .queryParam("grant_type", "authorization_code") + .build(); + } + + /** + * 返回获取userInfo的url + * + * @param authToken oauth返回的token + * @return 返回获取userInfo的url + */ + @Override + protected String userInfoUrl(AuthToken authToken) { + return UrlBuilder.fromBaseUrl(source.userInfo()) + .queryParam("access_token", authToken.getAccessToken()) + .queryParam("open_id", authToken.getOpenId()) + .build(); + } + + /** + * 返回获取accessToken的url + * + * @param refreshToken oauth返回的refreshtoken + * @return 返回获取accessToken的url + */ + @Override + protected String refreshTokenUrl(String refreshToken) { + return UrlBuilder.fromBaseUrl(source.refresh()) + .queryParam("client_key", config.getClientId()) + .queryParam("refresh_token", refreshToken) + .queryParam("grant_type", "refresh_token") + .build(); } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthFacebookRequest.java b/src/main/java/me/zhyd/oauth/request/AuthFacebookRequest.java index bc8fcac..771c894 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthFacebookRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthFacebookRequest.java @@ -1,15 +1,14 @@ package me.zhyd.oauth.request; -import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthUser; -import me.zhyd.oauth.model.AuthUserGender; import me.zhyd.oauth.utils.UrlBuilder; /** @@ -19,7 +18,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * @version 1.0 * @since 1.8 */ -public class AuthFacebookRequest extends BaseAuthRequest { +public class AuthFacebookRequest extends AuthDefaultRequest { public AuthFacebookRequest(AuthConfig config) { super(config, AuthSource.FACEBOOK); @@ -27,31 +26,36 @@ public class AuthFacebookRequest extends BaseAuthRequest { @Override protected AuthToken getAccessToken(AuthCallback authCallback) { - String accessTokenUrl = UrlBuilder.getFacebookAccessTokenUrl(config.getClientId(), config.getClientSecret(), - authCallback.getCode(), config.getRedirectUri()); - HttpResponse response = HttpRequest.post(accessTokenUrl).execute(); + HttpResponse response = doPostAuthorizationCode(authCallback.getCode()); JSONObject accessTokenObject = JSONObject.parseObject(response.body()); - - if (accessTokenObject.containsKey("error")) { - throw new AuthException(accessTokenObject.getJSONObject("error").getString("message")); - } - + this.checkResponse(accessTokenObject); return AuthToken.builder() - .accessToken(accessTokenObject.getString("access_token")) - .expireIn(accessTokenObject.getIntValue("expires_in")) - .tokenType(accessTokenObject.getString("token_type")) - .build(); + .accessToken(accessTokenObject.getString("access_token")) + .expireIn(accessTokenObject.getIntValue("expires_in")) + .tokenType(accessTokenObject.getString("token_type")) + .build(); } @Override protected AuthUser getUserInfo(AuthToken authToken) { - String accessToken = authToken.getAccessToken(); - HttpResponse response = HttpRequest.get(UrlBuilder.getFacebookUserInfoUrl(accessToken)).execute(); + HttpResponse response = doGetUserInfo(authToken); String userInfo = response.body(); JSONObject object = JSONObject.parseObject(userInfo); - if (object.containsKey("error")) { - throw new AuthException(object.getJSONObject("error").getString("message")); - } + this.checkResponse(object); + return AuthUser.builder() + .uuid(object.getString("id")) + .username(object.getString("name")) + .nickname(object.getString("name")) + .avatar(getUserPicture(object)) + .location(object.getString("locale")) + .email(object.getString("email")) + .gender(AuthUserGender.getRealGender(object.getString("gender"))) + .token(authToken) + .source(AuthSource.FACEBOOK) + .build(); + } + + private String getUserPicture(JSONObject object) { String picture = null; if (object.containsKey("picture")) { JSONObject pictureObj = object.getJSONObject("picture"); @@ -60,26 +64,31 @@ public class AuthFacebookRequest extends BaseAuthRequest { picture = pictureObj.getString("url"); } } - return AuthUser.builder() - .uuid(object.getString("id")) - .username(object.getString("name")) - .nickname(object.getString("name")) - .avatar(picture) - .location(object.getString("locale")) - .email(object.getString("email")) - .gender(AuthUserGender.getRealGender(object.getString("gender"))) - .token(authToken) - .source(AuthSource.FACEBOOK) - .build(); + return picture; } /** - * 返回认证url,可自行跳转页面 + * 返回获取userInfo的url * - * @return 返回授权地址 + * @param authToken 用户token + * @return 返回获取userInfo的url */ @Override - public String authorize() { - return UrlBuilder.getFacebookAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); + protected String userInfoUrl(AuthToken authToken) { + return UrlBuilder.fromBaseUrl(source.userInfo()) + .queryParam("access_token", authToken.getAccessToken()) + .queryParam("fields", "id,name,birthday,gender,hometown,email,devices,picture.width(400)") + .build(); + } + + /** + * 检查响应内容是否正确 + * + * @param object 请求响应内容 + */ + private void checkResponse(JSONObject object) { + if (object.containsKey("error")) { + throw new AuthException(object.getJSONObject("error").getString("message")); + } } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthGiteeRequest.java b/src/main/java/me/zhyd/oauth/request/AuthGiteeRequest.java index 192f684..0522003 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthGiteeRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthGiteeRequest.java @@ -1,16 +1,14 @@ package me.zhyd.oauth.request; -import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthUser; -import me.zhyd.oauth.model.AuthUserGender; -import me.zhyd.oauth.utils.UrlBuilder; /** * Gitee登录 @@ -19,7 +17,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * @version 1.0 * @since 1.8 */ -public class AuthGiteeRequest extends BaseAuthRequest { +public class AuthGiteeRequest extends AuthDefaultRequest { public AuthGiteeRequest(AuthConfig config) { super(config, AuthSource.GITEE); @@ -27,45 +25,48 @@ public class AuthGiteeRequest extends BaseAuthRequest { @Override protected AuthToken getAccessToken(AuthCallback authCallback) { - String accessTokenUrl = UrlBuilder.getGiteeAccessTokenUrl(config.getClientId(), config.getClientSecret(), - authCallback.getCode(), config.getRedirectUri()); - HttpResponse response = HttpRequest.post(accessTokenUrl).execute(); + HttpResponse response = doPostAuthorizationCode(authCallback.getCode()); JSONObject accessTokenObject = JSONObject.parseObject(response.body()); - if (accessTokenObject.containsKey("error")) { - throw new AuthException("Unable to get token from gitee using code [" + authCallback.getCode() + "]"); - } - return AuthToken.builder().accessToken(accessTokenObject.getString("access_token")).build(); + this.checkResponse(accessTokenObject); + return AuthToken.builder() + .accessToken(accessTokenObject.getString("access_token")) + .refreshToken(accessTokenObject.getString("refresh_token")) + .scope(accessTokenObject.getString("scope")) + .tokenType(accessTokenObject.getString("token_type")) + .expireIn(accessTokenObject.getIntValue("expires_in")) + .build(); } @Override protected AuthUser getUserInfo(AuthToken authToken) { - String accessToken = authToken.getAccessToken(); - HttpResponse response = HttpRequest.get(UrlBuilder.getGiteeUserInfoUrl(accessToken)).execute(); + HttpResponse response = doGetUserInfo(authToken); String userInfo = response.body(); JSONObject object = JSONObject.parseObject(userInfo); + this.checkResponse(object); return AuthUser.builder() - .uuid(object.getString("id")) - .username(object.getString("login")) - .avatar(object.getString("avatar_url")) - .blog(object.getString("blog")) - .nickname(object.getString("name")) - .company(object.getString("company")) - .location(object.getString("address")) - .email(object.getString("email")) - .remark(object.getString("bio")) - .gender(AuthUserGender.UNKNOW) - .token(authToken) - .source(AuthSource.GITEE) - .build(); + .uuid(object.getString("id")) + .username(object.getString("login")) + .avatar(object.getString("avatar_url")) + .blog(object.getString("blog")) + .nickname(object.getString("name")) + .company(object.getString("company")) + .location(object.getString("address")) + .email(object.getString("email")) + .remark(object.getString("bio")) + .gender(AuthUserGender.UNKNOWN) + .token(authToken) + .source(AuthSource.GITEE) + .build(); } /** - * 返回认证url,可自行跳转页面 + * 检查响应内容是否正确 * - * @return 返回授权地址 + * @param object 请求响应内容 */ - @Override - public String authorize() { - return UrlBuilder.getGiteeAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); + private void checkResponse(JSONObject object) { + if (object.containsKey("error")) { + throw new AuthException(object.getString("error_description")); + } } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthGithubRequest.java b/src/main/java/me/zhyd/oauth/request/AuthGithubRequest.java index bd60e1a..c843a8b 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthGithubRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthGithubRequest.java @@ -1,19 +1,14 @@ package me.zhyd.oauth.request; -import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthUser; -import me.zhyd.oauth.model.AuthUserGender; -import me.zhyd.oauth.utils.GlobalAuthUtil; -import me.zhyd.oauth.utils.UrlBuilder; - -import java.util.Map; /** * Github登录 @@ -22,7 +17,7 @@ import java.util.Map; * @version 1.0 * @since 1.8 */ -public class AuthGithubRequest extends BaseAuthRequest { +public class AuthGithubRequest extends AuthDefaultRequest { public AuthGithubRequest(AuthConfig config) { super(config, AuthSource.GITHUB); @@ -30,44 +25,45 @@ public class AuthGithubRequest extends BaseAuthRequest { @Override protected AuthToken getAccessToken(AuthCallback authCallback) { - String accessTokenUrl = UrlBuilder.getGithubAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode(), config.getRedirectUri()); - HttpResponse response = HttpRequest.post(accessTokenUrl).execute(); - Map res = GlobalAuthUtil.parseStringToMap(response.body()); - if (res.containsKey("error")) { - throw new AuthException(res.get("error") + ":" + res.get("error_description")); - } - return AuthToken.builder().accessToken(res.get("access_token")).build(); + HttpResponse response = doPostAuthorizationCode(authCallback.getCode()); + JSONObject accessTokenObject = JSONObject.parseObject(response.body()); + this.checkResponse(accessTokenObject); + return AuthToken.builder() + .accessToken(accessTokenObject.getString("access_token")) + .scope(accessTokenObject.getString("scope")) + .tokenType(accessTokenObject.getString("token_type")) + .build(); } @Override protected AuthUser getUserInfo(AuthToken authToken) { - String accessToken = authToken.getAccessToken(); - HttpResponse response = HttpRequest.get(UrlBuilder.getGithubUserInfoUrl(accessToken)).execute(); - String userInfo = response.body(); - JSONObject object = JSONObject.parseObject(userInfo); + HttpResponse response = doGetUserInfo(authToken); + JSONObject object = JSONObject.parseObject(response.body()); + this.checkResponse(object); return AuthUser.builder() - .uuid(object.getString("id")) - .username(object.getString("login")) - .avatar(object.getString("avatar_url")) - .blog(object.getString("blog")) - .nickname(object.getString("name")) - .company(object.getString("company")) - .location(object.getString("location")) - .email(object.getString("email")) - .remark(object.getString("bio")) - .gender(AuthUserGender.UNKNOW) - .token(authToken) - .source(AuthSource.GITHUB) - .build(); + .uuid(object.getString("id")) + .username(object.getString("login")) + .avatar(object.getString("avatar_url")) + .blog(object.getString("blog")) + .nickname(object.getString("name")) + .company(object.getString("company")) + .location(object.getString("location")) + .email(object.getString("email")) + .remark(object.getString("bio")) + .gender(AuthUserGender.UNKNOWN) + .token(authToken) + .source(AuthSource.GITHUB) + .build(); } /** - * 返回认证url,可自行跳转页面 + * 检查响应内容是否正确 * - * @return 返回授权地址 + * @param object 请求响应内容 */ - @Override - public String authorize() { - return UrlBuilder.getGithubAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); + private void checkResponse(JSONObject object) { + if (object.containsKey("error")) { + throw new AuthException(object.getString("error_description")); + } } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthGoogleRequest.java b/src/main/java/me/zhyd/oauth/request/AuthGoogleRequest.java index dea8f54..a6a603a 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthGoogleRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthGoogleRequest.java @@ -1,15 +1,14 @@ package me.zhyd.oauth.request; -import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthUser; -import me.zhyd.oauth.model.AuthUserGender; import me.zhyd.oauth.utils.UrlBuilder; /** @@ -19,7 +18,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * @version 1.3 * @since 1.3 */ -public class AuthGoogleRequest extends BaseAuthRequest { +public class AuthGoogleRequest extends AuthDefaultRequest { public AuthGoogleRequest(AuthConfig config) { super(config, AuthSource.GOOGLE); @@ -27,42 +26,35 @@ public class AuthGoogleRequest extends BaseAuthRequest { @Override protected AuthToken getAccessToken(AuthCallback authCallback) { - String accessTokenUrl = UrlBuilder.getGoogleAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode(), config - .getRedirectUri()); - HttpResponse response = HttpRequest.post(accessTokenUrl).execute(); + HttpResponse response = doPostAuthorizationCode(authCallback.getCode()); JSONObject accessTokenObject = JSONObject.parseObject(response.body()); - - if (accessTokenObject.containsKey("error") || accessTokenObject.containsKey("error_description")) { - throw new AuthException("get google access_token has error:[" + accessTokenObject.getString("error") + "], error_description:[" + accessTokenObject - .getString("error_description") + "]"); - } - + this.checkResponse(accessTokenObject); return AuthToken.builder() - .accessToken(accessTokenObject.getString("access_token")) - .expireIn(accessTokenObject.getIntValue("expires_in")) - .scope(accessTokenObject.getString("scope")) - .tokenType(accessTokenObject.getString("token_type")) - .idToken(accessTokenObject.getString("id_token")) - .build(); + .accessToken(accessTokenObject.getString("access_token")) + .expireIn(accessTokenObject.getIntValue("expires_in")) + .scope(accessTokenObject.getString("scope")) + .tokenType(accessTokenObject.getString("token_type")) + .idToken(accessTokenObject.getString("id_token")) + .build(); } @Override protected AuthUser getUserInfo(AuthToken authToken) { - String accessToken = authToken.getIdToken(); - HttpResponse response = HttpRequest.get(UrlBuilder.getGoogleUserInfoUrl(accessToken)).execute(); + HttpResponse response = doGetUserInfo(authToken); String userInfo = response.body(); JSONObject object = JSONObject.parseObject(userInfo); + this.checkResponse(object); return AuthUser.builder() - .uuid(object.getString("sub")) - .username(object.getString("name")) - .avatar(object.getString("picture")) - .nickname(object.getString("name")) - .location(object.getString("locale")) - .email(object.getString("email")) - .gender(AuthUserGender.UNKNOW) - .token(authToken) - .source(AuthSource.GOOGLE) - .build(); + .uuid(object.getString("sub")) + .username(object.getString("name")) + .avatar(object.getString("picture")) + .nickname(object.getString("name")) + .location(object.getString("locale")) + .email(object.getString("email")) + .gender(AuthUserGender.UNKNOWN) + .token(authToken) + .source(AuthSource.GOOGLE) + .build(); } /** @@ -72,6 +64,34 @@ public class AuthGoogleRequest extends BaseAuthRequest { */ @Override public String authorize() { - return UrlBuilder.getGoogleAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); + return UrlBuilder.fromBaseUrl(source.authorize()) + .queryParam("response_type", "code") + .queryParam("client_id", config.getClientId()) + .queryParam("scope", "openid%20email%20profile") + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("state", getRealState(config.getState())) + .build(); + } + + /** + * 返回获取userInfo的url + * + * @param authToken + * @return 返回获取userInfo的url + */ + @Override + protected String userInfoUrl(AuthToken authToken) { + return UrlBuilder.fromBaseUrl(source.userInfo()).queryParam("id_token", authToken.getAccessToken()).build(); + } + + /** + * 检查响应内容是否正确 + * + * @param object 请求响应内容 + */ + private void checkResponse(JSONObject object) { + if (object.containsKey("error") || object.containsKey("error_description")) { + throw new AuthException(object.getString("error_description")); + } } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthLinkedinRequest.java b/src/main/java/me/zhyd/oauth/request/AuthLinkedinRequest.java index 23cfa6b..b552256 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthLinkedinRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthLinkedinRequest.java @@ -6,6 +6,7 @@ import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.model.*; import me.zhyd.oauth.utils.StringUtils; @@ -19,7 +20,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * @version 1.0 * @since 1.8 */ -public class AuthLinkedinRequest extends BaseAuthRequest { +public class AuthLinkedinRequest extends AuthDefaultRequest { public AuthLinkedinRequest(AuthConfig config) { super(config, AuthSource.LINKEDIN); @@ -27,24 +28,47 @@ public class AuthLinkedinRequest extends BaseAuthRequest { @Override protected AuthToken getAccessToken(AuthCallback authCallback) { - String accessTokenUrl = UrlBuilder.getLinkedinAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode(), config - .getRedirectUri()); - return this.getToken(accessTokenUrl); + return this.getToken(accessTokenUrl(authCallback.getCode())); } @Override protected AuthUser getUserInfo(AuthToken authToken) { String accessToken = authToken.getAccessToken(); - HttpResponse response = HttpRequest.get(UrlBuilder.getLinkedinUserInfoUrl()) - .header("Host", "api.linkedin.com") - .header("Connection", "Keep-Alive") - .header("Authorization", "Bearer " + accessToken) - .execute(); + HttpResponse response = HttpRequest.get(userInfoUrl(authToken)) + .header("Host", "api.linkedin.com") + .header("Connection", "Keep-Alive") + .header("Authorization", "Bearer " + accessToken) + .execute(); JSONObject userInfoObject = JSONObject.parseObject(response.body()); this.checkResponse(userInfoObject); - // 组装用户名 + String userName = getUserName(userInfoObject); + + // 获取用户头像 + String avatar = this.getAvatar(userInfoObject); + + // 获取用户邮箱地址 + String email = this.getUserEmail(accessToken); + return AuthUser.builder() + .uuid(userInfoObject.getString("id")) + .username(userName) + .nickname(userName) + .avatar(avatar) + .email(email) + .token(authToken) + .gender(AuthUserGender.UNKNOWN) + .source(AuthSource.LINKEDIN) + .build(); + } + + /** + * 获取用户的真实名 + * + * @param userInfoObject 用户json对象 + * @return 用户名 + */ + private String getUserName(JSONObject userInfoObject) { String firstName, lastName; // 获取firstName if (userInfoObject.containsKey("localizedFirstName")) { @@ -58,58 +82,49 @@ public class AuthLinkedinRequest extends BaseAuthRequest { } else { lastName = getUserName(userInfoObject, "lastName"); } - String userName = firstName + " " + lastName; + return firstName + " " + lastName; + } - // 获取用户头像 + /** + * 获取用户的头像 + * + * @param userInfoObject 用户json对象 + * @return 用户的头像地址 + */ + private String getAvatar(JSONObject userInfoObject) { String avatar = null; JSONObject profilePictureObject = userInfoObject.getJSONObject("profilePicture"); if (profilePictureObject.containsKey("displayImage~")) { JSONArray displayImageElements = profilePictureObject.getJSONObject("displayImage~") - .getJSONArray("elements"); + .getJSONArray("elements"); if (null != displayImageElements && displayImageElements.size() > 0) { JSONObject largestImageObj = displayImageElements.getJSONObject(displayImageElements.size() - 1); avatar = largestImageObj.getJSONArray("identifiers").getJSONObject(0).getString("identifier"); } } - - // 获取用户邮箱地址 - String email = this.getUserEmail(accessToken); - return AuthUser.builder() - .uuid(userInfoObject.getString("id")) - .username(userName) - .nickname(userName) - .avatar(avatar) - .email(email) - .token(authToken) - .gender(AuthUserGender.UNKNOW) - .source(AuthSource.LINKEDIN) - .build(); + return avatar; } /** - * 返回认证url,可自行跳转页面 + * 获取用户的email * - * @return 返回授权地址 + * @param accessToken 用户授权后返回的token + * @return 用户的邮箱地址 */ - @Override - public String authorize() { - return UrlBuilder.getLinkedinAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); - } - private String getUserEmail(String accessToken) { String email = null; HttpResponse emailResponse = HttpRequest.get("https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))") - .header("Host", "api.linkedin.com") - .header("Connection", "Keep-Alive") - .header("Authorization", "Bearer " + accessToken) - .execute(); - System.out.println(emailResponse.body()); + .header("Host", "api.linkedin.com") + .header("Connection", "Keep-Alive") + .header("Authorization", "Bearer " + accessToken) + .execute(); JSONObject emailObj = JSONObject.parseObject(emailResponse.body()); + this.checkResponse(emailObj); if (emailObj.containsKey("elements")) { email = emailObj.getJSONArray("elements") - .getJSONObject(0) - .getJSONObject("handle~") - .getString("emailAddress"); + .getJSONObject(0) + .getJSONObject("handle~") + .getString("emailAddress"); } return email; } @@ -125,20 +140,25 @@ public class AuthLinkedinRequest extends BaseAuthRequest { @Override public AuthResponse refresh(AuthToken oldToken) { - if (StringUtils.isEmpty(oldToken.getRefreshToken())) { - throw new AuthException(ResponseStatus.UNSUPPORTED); + String refreshToken = oldToken.getRefreshToken(); + if (StringUtils.isEmpty(refreshToken)) { + throw new AuthException(AuthResponseStatus.UNSUPPORTED); } - String refreshTokenUrl = UrlBuilder.getLinkedinRefreshUrl(config.getClientId(), config.getClientSecret(), oldToken - .getRefreshToken()); + String refreshTokenUrl = refreshTokenUrl(refreshToken); return AuthResponse.builder() - .code(ResponseStatus.SUCCESS.getCode()) - .data(this.getToken(refreshTokenUrl)) - .build(); + .code(AuthResponseStatus.SUCCESS.getCode()) + .data(this.getToken(refreshTokenUrl)) + .build(); } - private void checkResponse(JSONObject userInfoObject) { - if (userInfoObject.containsKey("error")) { - throw new AuthException(userInfoObject.getString("error_description")); + /** + * 检查响应内容是否正确 + * + * @param object 请求响应内容 + */ + private void checkResponse(JSONObject object) { + if (object.containsKey("error")) { + throw new AuthException(object.getString("error_description")); } } @@ -150,18 +170,47 @@ public class AuthLinkedinRequest extends BaseAuthRequest { */ private AuthToken getToken(String accessTokenUrl) { HttpResponse response = HttpRequest.post(accessTokenUrl) - .header("Host", "www.linkedin.com") - .header("Content-Type", "application/x-www-form-urlencoded") - .execute(); + .header("Host", "www.linkedin.com") + .contentType("application/x-www-form-urlencoded") + .execute(); String accessTokenStr = response.body(); JSONObject accessTokenObject = JSONObject.parseObject(accessTokenStr); this.checkResponse(accessTokenObject); return AuthToken.builder() - .accessToken(accessTokenObject.getString("access_token")) - .expireIn(accessTokenObject.getIntValue("expires_in")) - .refreshToken(accessTokenObject.getString("refresh_token")) - .build(); + .accessToken(accessTokenObject.getString("access_token")) + .expireIn(accessTokenObject.getIntValue("expires_in")) + .refreshToken(accessTokenObject.getString("refresh_token")) + .build(); + } + + /** + * 返回认证url,可自行跳转页面 + * + * @return 返回授权地址 + */ + @Override + public String authorize() { + return UrlBuilder.fromBaseUrl(source.authorize()) + .queryParam("response_type", "code") + .queryParam("client_id", config.getClientId()) + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("state", getRealState(config.getState())) + .queryParam("scope", "r_liteprofile%20r_emailaddress%20w_member_social") + .build(); + } + + /** + * 返回获取userInfo的url + * + * @param authToken + * @return 返回获取userInfo的url + */ + @Override + protected String userInfoUrl(AuthToken authToken) { + return UrlBuilder.fromBaseUrl(source.userInfo()) + .queryParam("projection", "(id,firstName,lastName,profilePicture(displayImage~:playableStreams))") + .build(); } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthMiRequest.java b/src/main/java/me/zhyd/oauth/request/AuthMiRequest.java index 149c86c..2fddab2 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthMiRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthMiRequest.java @@ -7,6 +7,7 @@ import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.model.*; import me.zhyd.oauth.utils.UrlBuilder; @@ -21,7 +22,7 @@ import java.text.MessageFormat; * @since 1.5 */ @Slf4j -public class AuthMiRequest extends BaseAuthRequest { +public class AuthMiRequest extends AuthDefaultRequest { private static final String PREFIX = "&&&START&&&"; public AuthMiRequest(AuthConfig config) { @@ -30,8 +31,7 @@ public class AuthMiRequest extends BaseAuthRequest { @Override protected AuthToken getAccessToken(AuthCallback authCallback) { - String accessTokenUrl = UrlBuilder.getMiAccessTokenUrl(config.getClientId(), config.getClientSecret(), config.getRedirectUri(), authCallback.getCode()); - return getToken(accessTokenUrl); + return getToken(accessTokenUrl(authCallback.getCode())); } private AuthToken getToken(String accessTokenUrl) { @@ -44,22 +44,21 @@ public class AuthMiRequest extends BaseAuthRequest { } return AuthToken.builder() - .accessToken(accessTokenObject.getString("access_token")) - .expireIn(accessTokenObject.getIntValue("expires_in")) - .scope(accessTokenObject.getString("scope")) - .tokenType(accessTokenObject.getString("token_type")) - .refreshToken(accessTokenObject.getString("refresh_token")) - .openId(accessTokenObject.getString("openId")) - .macAlgorithm(accessTokenObject.getString("mac_algorithm")) - .macKey(accessTokenObject.getString("mac_key")) - .build(); + .accessToken(accessTokenObject.getString("access_token")) + .expireIn(accessTokenObject.getIntValue("expires_in")) + .scope(accessTokenObject.getString("scope")) + .tokenType(accessTokenObject.getString("token_type")) + .refreshToken(accessTokenObject.getString("refresh_token")) + .openId(accessTokenObject.getString("openId")) + .macAlgorithm(accessTokenObject.getString("mac_algorithm")) + .macKey(accessTokenObject.getString("mac_key")) + .build(); } @Override protected AuthUser getUserInfo(AuthToken authToken) { // 获取用户信息 - HttpResponse userResponse = HttpRequest.get(UrlBuilder.getMiUserInfoUrl(config.getClientId(), authToken.getAccessToken())) - .execute(); + HttpResponse userResponse = doGetUserInfo(authToken); JSONObject userProfile = JSONObject.parseObject(userResponse.body()); if ("error".equalsIgnoreCase(userProfile.getString("result"))) { @@ -69,42 +68,32 @@ public class AuthMiRequest extends BaseAuthRequest { JSONObject user = userProfile.getJSONObject("data"); AuthUser authUser = AuthUser.builder() - .uuid(authToken.getOpenId()) - .username(user.getString("miliaoNick")) - .nickname(user.getString("miliaoNick")) - .avatar(user.getString("miliaoIcon")) - .email(user.getString("mail")) - .gender(AuthUserGender.UNKNOW) - .token(authToken) - .source(AuthSource.MI) - .build(); + .uuid(authToken.getOpenId()) + .username(user.getString("miliaoNick")) + .nickname(user.getString("miliaoNick")) + .avatar(user.getString("miliaoIcon")) + .email(user.getString("mail")) + .gender(AuthUserGender.UNKNOWN) + .token(authToken) + .source(AuthSource.MI) + .build(); // 获取用户邮箱手机号等信息 String emailPhoneUrl = MessageFormat.format("{0}?clientId={1}&token={2}", "https://open.account.xiaomi.com/user/phoneAndEmail", config - .getClientId(), authToken.getAccessToken()); + .getClientId(), authToken.getAccessToken()); HttpResponse emailResponse = HttpRequest.get(emailPhoneUrl).execute(); JSONObject userEmailPhone = JSONObject.parseObject(emailResponse.body()); if (!"error".equalsIgnoreCase(userEmailPhone.getString("result"))) { JSONObject emailPhone = userEmailPhone.getJSONObject("data"); authUser.setEmail(emailPhone.getString("email")); - }else { + } else { log.warn("小米开发平台暂时不对外开放用户手机及邮箱信息的获取"); } return authUser; } - /** - * 返回认证url,可自行跳转页面 - * - * @return 返回授权地址 - */ - @Override - public String authorize() { - return UrlBuilder.getMiAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); - } - /** * 刷新access token (续期) * @@ -113,9 +102,40 @@ public class AuthMiRequest extends BaseAuthRequest { */ @Override public AuthResponse refresh(AuthToken authToken) { - String miRefreshUrl = UrlBuilder.getMiRefreshUrl(config.getClientId(), config.getClientSecret(), config.getRedirectUri(), authToken - .getRefreshToken()); + return AuthResponse.builder() + .code(AuthResponseStatus.SUCCESS.getCode()) + .data(getToken(refreshTokenUrl(authToken.getRefreshToken()))) + .build(); + } - return AuthResponse.builder().code(ResponseStatus.SUCCESS.getCode()).data(getToken(miRefreshUrl)).build(); + /** + * 返回认证url,可自行跳转页面 + * + * @return 返回授权地址 + */ + @Override + public String authorize() { + return UrlBuilder.fromBaseUrl(source.authorize()) + .queryParam("response_type", "code") + .queryParam("client_id", config.getClientId()) + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("state", getRealState(config.getState())) + .queryParam("scope", "user/profile%20user/openIdV2%20user/phoneAndEmail") + .queryParam("skip_confirm", "false") + .build(); + } + + /** + * 返回获取userInfo的url + * + * @param authToken + * @return 返回获取userInfo的url + */ + @Override + protected String userInfoUrl(AuthToken authToken) { + return UrlBuilder.fromBaseUrl(source.userInfo()) + .queryParam("clientId", config.getClientId()) + .queryParam("token", authToken.getAccessToken()) + .build(); } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java b/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java index cbf3252..4b5e864 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthMicrosoftRequest.java @@ -2,16 +2,15 @@ package me.zhyd.oauth.request; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; -import cn.hutool.http.HttpUtil; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.model.*; import me.zhyd.oauth.utils.UrlBuilder; -import java.util.HashMap; -import java.util.Map; +import static me.zhyd.oauth.utils.GlobalAuthUtil.parseQueryToMap; /** * 微软登录 @@ -20,17 +19,14 @@ import java.util.Map; * @version 1.5 * @since 1.5 */ -public class AuthMicrosoftRequest extends BaseAuthRequest { +public class AuthMicrosoftRequest extends AuthDefaultRequest { public AuthMicrosoftRequest(AuthConfig config) { super(config, AuthSource.MICROSOFT); } @Override protected AuthToken getAccessToken(AuthCallback authCallback) { - String accessTokenUrl = UrlBuilder.getMicrosoftAccessTokenUrl(config.getClientId(), config.getClientSecret(), config - .getRedirectUri(), authCallback.getCode()); - - return getToken(accessTokenUrl); + return getToken(accessTokenUrl(authCallback.getCode())); } /** @@ -40,30 +36,33 @@ public class AuthMicrosoftRequest extends BaseAuthRequest { * @return token对象 */ private AuthToken getToken(String accessTokenUrl) { - Map paramMap = new HashMap<>(6); - HttpUtil.decodeParamMap(accessTokenUrl, "UTF-8").forEach(paramMap::put); HttpResponse response = HttpRequest.post(accessTokenUrl) - .header("Host", "https://login.microsoftonline.com") - .header("Content-Type", "application/x-www-form-urlencoded") - .form(paramMap) - .execute(); + .header("Host", "https://login.microsoftonline.com") + .contentType("application/x-www-form-urlencoded") + .form(parseQueryToMap(accessTokenUrl)) + .execute(); String accessTokenStr = response.body(); JSONObject accessTokenObject = JSONObject.parseObject(accessTokenStr); this.checkResponse(accessTokenObject); return AuthToken.builder() - .accessToken(accessTokenObject.getString("access_token")) - .expireIn(accessTokenObject.getIntValue("expires_in")) - .scope(accessTokenObject.getString("scope")) - .tokenType(accessTokenObject.getString("token_type")) - .refreshToken(accessTokenObject.getString("refresh_token")) - .build(); + .accessToken(accessTokenObject.getString("access_token")) + .expireIn(accessTokenObject.getIntValue("expires_in")) + .scope(accessTokenObject.getString("scope")) + .tokenType(accessTokenObject.getString("token_type")) + .refreshToken(accessTokenObject.getString("refresh_token")) + .build(); } - private void checkResponse(JSONObject response) { - if (response.containsKey("error")) { - throw new AuthException(response.getString("error_description")); + /** + * 检查响应内容是否正确 + * + * @param object 请求响应内容 + */ + private void checkResponse(JSONObject object) { + if (object.containsKey("error")) { + throw new AuthException(object.getString("error_description")); } } @@ -72,31 +71,20 @@ public class AuthMicrosoftRequest extends BaseAuthRequest { String token = authToken.getAccessToken(); String tokenType = authToken.getTokenType(); String jwt = tokenType + " " + token; - HttpResponse response = HttpRequest.get(UrlBuilder.getMicrosoftUserInfoUrl()) - .header("Authorization", jwt) - .execute(); + HttpResponse response = HttpRequest.get(userInfoUrl(authToken)).header("Authorization", jwt).execute(); String userInfo = response.body(); JSONObject object = JSONObject.parseObject(userInfo); + this.checkResponse(object); return AuthUser.builder() - .uuid(object.getString("id")) - .username(object.getString("userPrincipalName")) - .nickname(object.getString("displayName")) - .location(object.getString("officeLocation")) - .email(object.getString("mail")) - .gender(AuthUserGender.UNKNOW) - .token(authToken) - .source(AuthSource.MICROSOFT) - .build(); - } - - /** - * 返回认证url,可自行跳转页面 - * - * @return 返回授权地址 - */ - @Override - public String authorize() { - return UrlBuilder.getMicrosoftAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); + .uuid(object.getString("id")) + .username(object.getString("userPrincipalName")) + .nickname(object.getString("displayName")) + .location(object.getString("officeLocation")) + .email(object.getString("mail")) + .gender(AuthUserGender.UNKNOWN) + .token(authToken) + .source(AuthSource.MICROSOFT) + .build(); } /** @@ -107,9 +95,73 @@ public class AuthMicrosoftRequest extends BaseAuthRequest { */ @Override public AuthResponse refresh(AuthToken authToken) { - String refreshTokenUrl = UrlBuilder.getMicrosoftRefreshUrl(config.getClientId(), config.getClientSecret(), config - .getRedirectUri(), authToken.getRefreshToken()); + return AuthResponse.builder() + .code(AuthResponseStatus.SUCCESS.getCode()) + .data(getToken(refreshTokenUrl(authToken.getRefreshToken()))) + .build(); + } - return AuthResponse.builder().code(ResponseStatus.SUCCESS.getCode()).data(getToken(refreshTokenUrl)).build(); + /** + * 返回认证url,可自行跳转页面 + * + * @return 返回授权地址 + */ + @Override + public String authorize() { + return UrlBuilder.fromBaseUrl(source.authorize()) + .queryParam("response_type", "code") + .queryParam("client_id", config.getClientId()) + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("response_mode", "query") + .queryParam("scope", "offline_access%20user.read%20mail.read") + .queryParam("state", getRealState(config.getState())) + .build(); + } + + /** + * 返回获取accessToken的url + * + * @param code + * @return 返回获取accessToken的url + */ + @Override + protected String accessTokenUrl(String code) { + return UrlBuilder.fromBaseUrl(source.accessToken()) + .queryParam("code", code) + .queryParam("client_id", config.getClientId()) + .queryParam("client_secret", config.getClientSecret()) + .queryParam("grant_type", "authorization_code") + .queryParam("scope", "user.read%20mail.read") + .queryParam("redirect_uri", config.getRedirectUri()) + .build(); + } + + /** + * 返回获取userInfo的url + * + * @param authToken + * @return 返回获取userInfo的url + */ + @Override + protected String userInfoUrl(AuthToken authToken) { + return UrlBuilder.fromBaseUrl(source.userInfo()).build(); + } + + /** + * 返回获取accessToken的url + * + * @param refreshToken + * @return 返回获取accessToken的url + */ + @Override + protected String refreshTokenUrl(String refreshToken) { + return UrlBuilder.fromBaseUrl(source.refresh()) + .queryParam("client_id", config.getClientId()) + .queryParam("client_secret", config.getClientSecret()) + .queryParam("refresh_token", refreshToken) + .queryParam("grant_type", "refresh_token") + .queryParam("scope", "user.read%20mail.read") + .queryParam("redirect_uri", config.getRedirectUri()) + .build(); } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthOschinaRequest.java b/src/main/java/me/zhyd/oauth/request/AuthOschinaRequest.java index fe0110d..955a807 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthOschinaRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthOschinaRequest.java @@ -1,15 +1,14 @@ package me.zhyd.oauth.request; -import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthUser; -import me.zhyd.oauth.model.AuthUserGender; import me.zhyd.oauth.utils.UrlBuilder; /** @@ -19,7 +18,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * @version 1.0 * @since 1.8 */ -public class AuthOschinaRequest extends BaseAuthRequest { +public class AuthOschinaRequest extends AuthDefaultRequest { public AuthOschinaRequest(AuthConfig config) { super(config, AuthSource.OSCHINA); @@ -27,50 +26,76 @@ public class AuthOschinaRequest extends BaseAuthRequest { @Override protected AuthToken getAccessToken(AuthCallback authCallback) { - String accessTokenUrl = UrlBuilder.getOschinaAccessTokenUrl(config.getClientId(), config.getClientSecret(), - authCallback.getCode(), config.getRedirectUri()); - HttpResponse response = HttpRequest.post(accessTokenUrl).execute(); + HttpResponse response = doPostAuthorizationCode(authCallback.getCode()); JSONObject accessTokenObject = JSONObject.parseObject(response.body()); - if (accessTokenObject.containsKey("error")) { - throw new AuthException("Unable to get token from oschina using code [" + authCallback.getCode() + "]"); - } + this.checkResponse(accessTokenObject); return AuthToken.builder() - .accessToken(accessTokenObject.getString("access_token")) - .refreshToken(accessTokenObject.getString("refresh_token")) - .uid(accessTokenObject.getString("uid")) - .expireIn(accessTokenObject.getIntValue("expires_in")) - .build(); + .accessToken(accessTokenObject.getString("access_token")) + .refreshToken(accessTokenObject.getString("refresh_token")) + .uid(accessTokenObject.getString("uid")) + .expireIn(accessTokenObject.getIntValue("expires_in")) + .build(); } @Override protected AuthUser getUserInfo(AuthToken authToken) { - String accessToken = authToken.getAccessToken(); - HttpResponse response = HttpRequest.get(UrlBuilder.getOschinaUserInfoUrl(accessToken)).execute(); + HttpResponse response = doGetUserInfo(authToken); JSONObject object = JSONObject.parseObject(response.body()); - if (object.containsKey("error")) { - throw new AuthException(object.getString("error_description")); - } + this.checkResponse(object); return AuthUser.builder() - .uuid(object.getString("id")) - .username(object.getString("name")) - .nickname(object.getString("name")) - .avatar(object.getString("avatar")) - .blog(object.getString("url")) - .location(object.getString("location")) - .gender(AuthUserGender.getRealGender(object.getString("gender"))) - .email(object.getString("email")) - .token(authToken) - .source(AuthSource.OSCHINA) - .build(); + .uuid(object.getString("id")) + .username(object.getString("name")) + .nickname(object.getString("name")) + .avatar(object.getString("avatar")) + .blog(object.getString("url")) + .location(object.getString("location")) + .gender(AuthUserGender.getRealGender(object.getString("gender"))) + .email(object.getString("email")) + .token(authToken) + .source(AuthSource.OSCHINA) + .build(); } /** - * 返回认证url,可自行跳转页面 + * 返回获取accessToken的url * - * @return 返回授权地址 + * @param code + * @return 返回获取accessToken的url */ @Override - public String authorize() { - return UrlBuilder.getOschinaAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); + protected String accessTokenUrl(String code) { + return UrlBuilder.fromBaseUrl(source.accessToken()) + .queryParam("code", code) + .queryParam("client_id", config.getClientId()) + .queryParam("client_secret", config.getClientSecret()) + .queryParam("grant_type", "authorization_code") + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("dataType", "json") + .build(); + } + + /** + * 返回获取userInfo的url + * + * @param authToken + * @return 返回获取userInfo的url + */ + @Override + protected String userInfoUrl(AuthToken authToken) { + return UrlBuilder.fromBaseUrl(source.userInfo()) + .queryParam("access_token", authToken.getAccessToken()) + .queryParam("dataType", "json") + .build(); + } + + /** + * 检查响应内容是否正确 + * + * @param object 请求响应内容 + */ + private void checkResponse(JSONObject object) { + if (object.containsKey("error")) { + throw new AuthException(object.getString("error_description")); + } } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthPinterestRequest.java b/src/main/java/me/zhyd/oauth/request/AuthPinterestRequest.java new file mode 100644 index 0000000..e86a247 --- /dev/null +++ b/src/main/java/me/zhyd/oauth/request/AuthPinterestRequest.java @@ -0,0 +1,96 @@ +package me.zhyd.oauth.request; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import com.alibaba.fastjson.JSONObject; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.enums.AuthUserGender; +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthToken; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.utils.UrlBuilder; + +import java.util.Objects; + +import static me.zhyd.oauth.config.AuthSource.PINTEREST; + +/** + * Pinterest登录 + * + * @author hongwei.peng (pengisgood(at)gmail(dot)com) + * @version 1.9.0 + * @since 1.9.0 + */ +public class AuthPinterestRequest extends AuthDefaultRequest { + + private static final String FAILURE = "failure"; + + public AuthPinterestRequest(AuthConfig config) { + super(config, PINTEREST); + } + + @Override + protected AuthToken getAccessToken(AuthCallback authCallback) { + HttpResponse response = doPostAuthorizationCode(authCallback.getCode()); + JSONObject accessTokenObject = JSONObject.parseObject(response.body()); + this.checkResponse(accessTokenObject); + return AuthToken.builder() + .accessToken(accessTokenObject.getString("access_token")) + .tokenType(accessTokenObject.getString("token_type")) + .build(); + } + + @Override + protected AuthUser getUserInfo(AuthToken authToken) { + String userinfoUrl = UrlBuilder.fromBaseUrl(userInfoUrl(authToken)) + .queryParam("fields", "id,username,first_name,last_name,bio,image") + .build(); + HttpResponse response = HttpRequest.post(userinfoUrl).execute(); + JSONObject object = JSONObject.parseObject(response.body()); + this.checkResponse(object); + JSONObject userObj = object.getJSONObject("data"); + return AuthUser.builder() + .uuid(userObj.getString("id")) + .avatar(getAvatarUrl(userObj)) + .username(userObj.getString("username")) + .nickname(userObj.getString("first_name") + " " + userObj.getString("last_name")) + .gender(AuthUserGender.UNKNOWN) + .remark(userObj.getString("bio")) + .token(authToken) + .source(PINTEREST) + .build(); + } + + private String getAvatarUrl(JSONObject userObj) { + // image is a map data structure + JSONObject jsonObject = userObj.getJSONObject("image"); + if (Objects.isNull(jsonObject)) { + return null; + } + return jsonObject.getJSONObject("60x60").getString("url"); + } + + @Override + public String authorize() { + return UrlBuilder.fromBaseUrl(source.authorize()) + .queryParam("response_type", "code") + .queryParam("client_id", config.getClientId()) + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("state", getRealState(config.getState())) + .queryParam("scope", "read_public") + .build(); + } + + /** + * 检查响应内容是否正确 + * + * @param object 请求响应内容 + */ + private void checkResponse(JSONObject object) { + if (!object.containsKey("status") && FAILURE.equals(object.getString("status"))) { + throw new AuthException(object.getString("message")); + } + } + +} diff --git a/src/main/java/me/zhyd/oauth/request/AuthQqRequest.java b/src/main/java/me/zhyd/oauth/request/AuthQqRequest.java index e9bfffe..7943ea3 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthQqRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthQqRequest.java @@ -6,11 +6,9 @@ import cn.hutool.http.HttpResponse; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.exception.AuthException; -import me.zhyd.oauth.model.AuthCallback; -import me.zhyd.oauth.model.AuthToken; -import me.zhyd.oauth.model.AuthUser; -import me.zhyd.oauth.model.AuthUserGender; +import me.zhyd.oauth.model.*; import me.zhyd.oauth.utils.GlobalAuthUtil; import me.zhyd.oauth.utils.StringUtils; import me.zhyd.oauth.utils.UrlBuilder; @@ -25,33 +23,30 @@ import java.util.Map; * @version 1.0 * @since 1.8 */ -public class AuthQqRequest extends BaseAuthRequest { +public class AuthQqRequest extends AuthDefaultRequest { public AuthQqRequest(AuthConfig config) { super(config, AuthSource.QQ); } @Override protected AuthToken getAccessToken(AuthCallback authCallback) { - String accessTokenUrl = UrlBuilder.getQqAccessTokenUrl(config.getClientId(), config.getClientSecret(), - authCallback.getCode(), config.getRedirectUri()); - HttpResponse response = HttpRequest.get(accessTokenUrl).execute(); - Map accessTokenObject = GlobalAuthUtil.parseStringToMap(response.body()); - if (!accessTokenObject.containsKey("access_token")) { - throw new AuthException("Unable to get token from qq using code [" + authCallback.getCode() + "]"); - } - return AuthToken.builder() - .accessToken(accessTokenObject.get("access_token")) - .expireIn(Integer.valueOf(accessTokenObject.get("expires_in"))) - .refreshToken(accessTokenObject.get("refresh_token")) - .build(); + HttpResponse response = doGetAuthorizationCode(authCallback.getCode()); + return getAuthToken(response); + } + + @Override + public AuthResponse refresh(AuthToken authToken) { + HttpResponse response = HttpRequest.get(refreshTokenUrl(authToken.getRefreshToken())).execute(); + return AuthResponse.builder() + .code(AuthResponseStatus.SUCCESS.getCode()) + .data(getAuthToken(response)) + .build(); } @Override protected AuthUser getUserInfo(AuthToken authToken) { - String accessToken = authToken.getAccessToken(); String openId = this.getOpenId(authToken); - HttpResponse response = HttpRequest.get(UrlBuilder.getQqUserInfoUrl(config.getClientId(), accessToken, openId)) - .execute(); + HttpResponse response = doGetUserInfo(authToken); JSONObject object = JSONObject.parseObject(response.body()); if (object.getIntValue("ret") != 0) { throw new AuthException(object.getString("msg")); @@ -63,31 +58,22 @@ public class AuthQqRequest extends BaseAuthRequest { String location = String.format("%s-%s", object.getString("province"), object.getString("city")); return AuthUser.builder() - .username(object.getString("nickname")) - .nickname(object.getString("nickname")) - .avatar(avatar) - .location(location) - .uuid(openId) - .gender(AuthUserGender.getRealGender(object.getString("gender"))) - .token(authToken) - .source(AuthSource.QQ) - .build(); - } - - /** - * 返回认证url,可自行跳转页面 - * - * @return 返回授权地址 - */ - @Override - public String authorize() { - return UrlBuilder.getQqAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); + .username(object.getString("nickname")) + .nickname(object.getString("nickname")) + .avatar(avatar) + .location(location) + .uuid(openId) + .gender(AuthUserGender.getRealGender(object.getString("gender"))) + .token(authToken) + .source(AuthSource.QQ) + .build(); } private String getOpenId(AuthToken authToken) { - String accessToken = authToken.getAccessToken(); - HttpResponse response = HttpRequest.get(UrlBuilder.getQqOpenidUrl("https://graph.qq.com/oauth2.0/me", accessToken, config.isUnionId())) - .execute(); + HttpResponse response = HttpRequest.get(UrlBuilder.fromBaseUrl("https://graph.qq.com/oauth2.0/me") + .queryParam("access_token", authToken.getAccessToken()) + .queryParam("unionid", config.isUnionId() ? 1 : 0) + .build()).execute(); if (response.isOk()) { String body = response.body(); String removePrefix = StrUtil.replace(body, "callback(", ""); @@ -106,4 +92,31 @@ public class AuthQqRequest extends BaseAuthRequest { throw new AuthException("request error"); } + + /** + * 返回获取userInfo的url + * + * @param authToken 用户授权token + * @return 返回获取userInfo的url + */ + @Override + protected String userInfoUrl(AuthToken authToken) { + return UrlBuilder.fromBaseUrl(source.userInfo()) + .queryParam("access_token", authToken.getAccessToken()) + .queryParam("oauth_consumer_key", config.getClientId()) + .queryParam("openid", authToken.getOpenId()) + .build(); + } + + private AuthToken getAuthToken(HttpResponse response) { + Map accessTokenObject = GlobalAuthUtil.parseStringToMap(response.body()); + if (!accessTokenObject.containsKey("access_token") || accessTokenObject.containsKey("code")) { + throw new AuthException(accessTokenObject.get("msg")); + } + return AuthToken.builder() + .accessToken(accessTokenObject.get("access_token")) + .expireIn(Integer.valueOf(accessTokenObject.get("expires_in"))) + .refreshToken(accessTokenObject.get("refresh_token")) + .build(); + } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthRenrenRequest.java b/src/main/java/me/zhyd/oauth/request/AuthRenrenRequest.java new file mode 100644 index 0000000..c4d4af5 --- /dev/null +++ b/src/main/java/me/zhyd/oauth/request/AuthRenrenRequest.java @@ -0,0 +1,113 @@ +package me.zhyd.oauth.request; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.enums.AuthUserGender; +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.model.*; +import me.zhyd.oauth.utils.UrlBuilder; + +import java.util.Objects; + +import static me.zhyd.oauth.config.AuthSource.RENREN; +import static me.zhyd.oauth.model.AuthResponseStatus.SUCCESS; + +/** + * 人人登录 + * + * @author hongwei.peng (pengisgood(at)gmail(dot)com) + * @version 1.8.1 + * @since 1.8.1 + */ +public class AuthRenrenRequest extends AuthDefaultRequest { + + public AuthRenrenRequest(AuthConfig config) { + super(config, RENREN); + } + + @Override + protected AuthToken getAccessToken(AuthCallback authCallback) { + return this.getToken(accessTokenUrl(authCallback.getCode())); + } + + @Override + protected AuthUser getUserInfo(AuthToken authToken) { + HttpResponse response = doGetUserInfo(authToken); + JSONObject userObj = JSONObject.parseObject(response.body()).getJSONObject("response"); + + return AuthUser.builder() + .uuid(userObj.getString("id")) + .avatar(getAvatarUrl(userObj)) + .nickname(userObj.getString("name")) + .company(getCompany(userObj)) + .gender(getGender(userObj)) + .token(authToken) + .source(RENREN) + .build(); + } + + @Override + public AuthResponse refresh(AuthToken authToken) { + return AuthResponse.builder() + .code(SUCCESS.getCode()) + .data(getToken(this.refreshTokenUrl(authToken.getRefreshToken()))) + .build(); + } + + private AuthToken getToken(String url) { + HttpResponse response = HttpRequest.post(url).execute(); + JSONObject jsonObject = JSONObject.parseObject(response.body()); + if (jsonObject.containsKey("error")) { + throw new AuthException("Failed to get token from Renren: " + jsonObject); + } + + return AuthToken.builder() + .tokenType(jsonObject.getString("token_type")) + .expireIn(jsonObject.getIntValue("expires_in")) + .accessToken(jsonObject.getString("access_token")) + .refreshToken(jsonObject.getString("refresh_token")) + .openId(jsonObject.getJSONObject("user").getString("id")) + .build(); + } + + private String getAvatarUrl(JSONObject userObj) { + JSONArray jsonArray = userObj.getJSONArray("avatar"); + if (Objects.isNull(jsonArray) || jsonArray.isEmpty()) { + return null; + } + return jsonArray.getJSONObject(0).getString("url"); + } + + private AuthUserGender getGender(JSONObject userObj) { + JSONObject basicInformation = userObj.getJSONObject("basicInformation"); + if (Objects.isNull(basicInformation)) { + return AuthUserGender.UNKNOWN; + } + return AuthUserGender.getRealGender(basicInformation.getString("sex")); + } + + private String getCompany(JSONObject userObj) { + JSONArray jsonArray = userObj.getJSONArray("work"); + if (Objects.isNull(jsonArray) || jsonArray.isEmpty()) { + return null; + } + return jsonArray.getJSONObject(0).getString("name"); + } + + /** + * 返回获取userInfo的url + * + * @param authToken + * @return 返回获取userInfo的url + */ + @Override + protected String userInfoUrl(AuthToken authToken) { + return UrlBuilder.fromBaseUrl(source.userInfo()) + .queryParam("access_token", authToken.getAccessToken()) + .queryParam("userId", authToken.getOpenId()) + .build(); + } +} diff --git a/src/main/java/me/zhyd/oauth/request/AuthRequest.java b/src/main/java/me/zhyd/oauth/request/AuthRequest.java index d75651e..d06913c 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthRequest.java @@ -3,6 +3,7 @@ package me.zhyd.oauth.request; import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthResponse; +import me.zhyd.oauth.model.AuthResponseStatus; import me.zhyd.oauth.model.AuthToken; /** @@ -18,7 +19,7 @@ public interface AuthRequest { * @return 返回授权地址 */ default String authorize() { - throw new AuthException(ResponseStatus.NOT_IMPLEMENTED); + throw new AuthException(AuthResponseStatus.NOT_IMPLEMENTED); } /** @@ -28,7 +29,7 @@ public interface AuthRequest { * @return 返回登录成功后的用户信息 */ default AuthResponse login(AuthCallback authCallback) { - throw new AuthException(ResponseStatus.NOT_IMPLEMENTED); + throw new AuthException(AuthResponseStatus.NOT_IMPLEMENTED); } /** @@ -38,7 +39,7 @@ public interface AuthRequest { * @return AuthResponse */ default AuthResponse revoke(AuthToken authToken) { - throw new AuthException(ResponseStatus.NOT_IMPLEMENTED); + throw new AuthException(AuthResponseStatus.NOT_IMPLEMENTED); } /** @@ -48,6 +49,6 @@ public interface AuthRequest { * @return AuthResponse */ default AuthResponse refresh(AuthToken authToken) { - throw new AuthException(ResponseStatus.NOT_IMPLEMENTED); + throw new AuthException(AuthResponseStatus.NOT_IMPLEMENTED); } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthStackOverflowRequest.java b/src/main/java/me/zhyd/oauth/request/AuthStackOverflowRequest.java new file mode 100644 index 0000000..3784012 --- /dev/null +++ b/src/main/java/me/zhyd/oauth/request/AuthStackOverflowRequest.java @@ -0,0 +1,91 @@ +package me.zhyd.oauth.request; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import com.alibaba.fastjson.JSONObject; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.enums.AuthUserGender; +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthToken; +import me.zhyd.oauth.model.AuthUser; +import me.zhyd.oauth.utils.UrlBuilder; + +import static me.zhyd.oauth.config.AuthSource.STACK_OVERFLOW; +import static me.zhyd.oauth.utils.GlobalAuthUtil.parseQueryToMap; + +/** + * Stack Overflow登录 + * + * @author hongwei.peng (pengisgood(at)gmail(dot)com) + * @version 1.9.0 + * @since 1.9.0 + */ +public class AuthStackOverflowRequest extends AuthDefaultRequest { + + public AuthStackOverflowRequest(AuthConfig config) { + super(config, STACK_OVERFLOW); + } + + @Override + protected AuthToken getAccessToken(AuthCallback authCallback) { + String accessTokenUrl = accessTokenUrl(authCallback.getCode()); + HttpResponse response = HttpRequest.post(accessTokenUrl) + .contentType("application/x-www-form-urlencoded") + .form(parseQueryToMap(accessTokenUrl)) + .execute(); + JSONObject accessTokenObject = JSONObject.parseObject(response.body()); + this.checkResponse(accessTokenObject); + + return AuthToken.builder() + .accessToken(accessTokenObject.getString("access_token")) + .expireIn(accessTokenObject.getIntValue("expires")) + .build(); + } + + @Override + protected AuthUser getUserInfo(AuthToken authToken) { + String userInfoUrl = UrlBuilder.fromBaseUrl(this.source.userInfo()) + .queryParam("access_token", authToken.getAccessToken()) + .queryParam("site", "stackoverflow") + .queryParam("key", this.config.getStackOverflowKey()) + .build(); + HttpResponse response = HttpRequest.get(userInfoUrl).execute(); + JSONObject object = JSONObject.parseObject(response.body()); + this.checkResponse(object); + JSONObject userObj = object.getJSONArray("items").getJSONObject(0); + + return AuthUser.builder() + .uuid(userObj.getString("user_id")) + .avatar(userObj.getString("profile_image")) + .location(userObj.getString("location")) + .nickname(userObj.getString("display_name")) + .blog(userObj.getString("website_url")) + .gender(AuthUserGender.UNKNOWN) + .token(authToken) + .source(STACK_OVERFLOW) + .build(); + } + + @Override + public String authorize() { + return UrlBuilder.fromBaseUrl(source.authorize()) + .queryParam("response_type", "code") + .queryParam("client_id", config.getClientId()) + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("state", getRealState(config.getState())) + .queryParam("scope", "read_inbox") + .build(); + } + + /** + * 检查响应内容是否正确 + * + * @param object 请求响应内容 + */ + private void checkResponse(JSONObject object) { + if (object.containsKey("error")) { + throw new AuthException(object.getString("error_description")); + } + } +} diff --git a/src/main/java/me/zhyd/oauth/request/AuthTaobaoRequest.java b/src/main/java/me/zhyd/oauth/request/AuthTaobaoRequest.java index 8add7cd..55760bd 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthTaobaoRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthTaobaoRequest.java @@ -1,6 +1,5 @@ package me.zhyd.oauth.request; -import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; @@ -9,7 +8,7 @@ import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthUser; -import me.zhyd.oauth.model.AuthUserGender; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.utils.GlobalAuthUtil; import me.zhyd.oauth.utils.UrlBuilder; @@ -20,7 +19,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * @version 1.0 * @since 1.8 */ -public class AuthTaobaoRequest extends BaseAuthRequest { +public class AuthTaobaoRequest extends AuthDefaultRequest { public AuthTaobaoRequest(AuthConfig config) { super(config, AuthSource.TAOBAO); @@ -33,12 +32,10 @@ public class AuthTaobaoRequest extends BaseAuthRequest { @Override protected AuthUser getUserInfo(AuthToken authToken) { - String accessCode = authToken.getAccessCode(); - HttpResponse response = HttpRequest.post(UrlBuilder.getTaobaoAccessTokenUrl(this.config.getClientId(), this.config - .getClientSecret(), accessCode, this.config.getRedirectUri())).execute(); + HttpResponse response = doPostAuthorizationCode(authToken.getAccessCode()); JSONObject accessTokenObject = JSONObject.parseObject(response.body()); if (accessTokenObject.containsKey("error")) { - throw new AuthException(ResponseStatus.FAILURE + ":" + accessTokenObject.getString("error_description")); + throw new AuthException(accessTokenObject.getString("error_description")); } authToken.setAccessToken(accessTokenObject.getString("access_token")); authToken.setRefreshToken(accessTokenObject.getString("refresh_token")); @@ -48,13 +45,13 @@ public class AuthTaobaoRequest extends BaseAuthRequest { String nick = GlobalAuthUtil.urlDecode(accessTokenObject.getString("taobao_user_nick")); return AuthUser.builder() - .uuid(accessTokenObject.getString("taobao_user_id")) - .username(nick) - .nickname(nick) - .gender(AuthUserGender.UNKNOW) - .token(authToken) - .source(AuthSource.TAOBAO) - .build(); + .uuid(accessTokenObject.getString("taobao_user_id")) + .username(nick) + .nickname(nick) + .gender(AuthUserGender.UNKNOWN) + .token(authToken) + .source(AuthSource.TAOBAO) + .build(); } /** @@ -64,6 +61,12 @@ public class AuthTaobaoRequest extends BaseAuthRequest { */ @Override public String authorize() { - return UrlBuilder.getTaobaoAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); + return UrlBuilder.fromBaseUrl(source.authorize()) + .queryParam("response_type", "code") + .queryParam("client_id", config.getClientId()) + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("state", getRealState(config.getState())) + .queryParam("view", "web") + .build(); } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthTeambitionRequest.java b/src/main/java/me/zhyd/oauth/request/AuthTeambitionRequest.java new file mode 100644 index 0000000..81c6b3c --- /dev/null +++ b/src/main/java/me/zhyd/oauth/request/AuthTeambitionRequest.java @@ -0,0 +1,105 @@ +package me.zhyd.oauth.request; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import com.alibaba.fastjson.JSONObject; +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; +import me.zhyd.oauth.exception.AuthException; +import me.zhyd.oauth.model.*; + +/** + * Teambition授权登录 + * + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @version 1.0 + * @since 1.8 + */ +public class AuthTeambitionRequest extends AuthDefaultRequest { + + public AuthTeambitionRequest(AuthConfig config) { + super(config, AuthSource.TEAMBITION); + } + + /** + * @param authCallback 回调返回的参数 + * @return 所有信息 + */ + @Override + protected AuthToken getAccessToken(AuthCallback authCallback) { + HttpResponse response = HttpRequest.post(source.accessToken()) + .form("client_id", config.getClientId()) + .form("client_secret", config.getClientSecret()) + .form("code", authCallback.getCode()) + .form("grant_type", "code") + .execute(); + JSONObject accessTokenObject = JSONObject.parseObject(response.body()); + + this.checkResponse(accessTokenObject); + + return AuthToken.builder() + .accessToken(accessTokenObject.getString("access_token")) + .refreshToken(accessTokenObject.getString("refresh_token")) + .build(); + } + + @Override + protected AuthUser getUserInfo(AuthToken authToken) { + String accessToken = authToken.getAccessToken(); + + HttpResponse response = HttpRequest.get(source.userInfo()) + .header("Authorization", "OAuth2 " + accessToken) + .execute(); + JSONObject object = JSONObject.parseObject(response.body()); + + this.checkResponse(object); + + authToken.setUid(object.getString("_id")); + + return AuthUser.builder() + .uuid(object.getString("_id")) + .username(object.getString("name")) + .nickname(object.getString("name")) + .avatar(object.getString("avatarUrl")) + .blog(object.getString("website")) + .location(object.getString("location")) + .email(object.getString("email")) + .gender(AuthUserGender.UNKNOWN) + .token(authToken) + .source(AuthSource.TEAMBITION) + .build(); + } + + @Override + public AuthResponse refresh(AuthToken oldToken) { + String uid = oldToken.getUid(); + String refreshToken = oldToken.getRefreshToken(); + HttpResponse response = HttpRequest.post(source.refresh()) + .form("_userId", uid) + .form("refresh_token", refreshToken) + .execute(); + JSONObject refreshTokenObject = JSONObject.parseObject(response.body()); + + this.checkResponse(refreshTokenObject); + + return AuthResponse.builder() + .code(AuthResponseStatus.SUCCESS.getCode()) + .data(AuthToken.builder() + .accessToken(refreshTokenObject.getString("access_token")) + .refreshToken(refreshTokenObject.getString("refresh_token")) + .build()) + .build(); + } + + /** + * 检查响应内容是否正确 + * + * @param object 请求响应内容 + */ + private void checkResponse(JSONObject object) { + if ((object.containsKey("message") && object.containsKey("name"))) { + throw new AuthException(object.getString("name") + ", " + object.getString("message")); + } + } +} diff --git a/src/main/java/me/zhyd/oauth/request/AuthTencentCloudRequest.java b/src/main/java/me/zhyd/oauth/request/AuthTencentCloudRequest.java index 4841115..8a817c4 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthTencentCloudRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthTencentCloudRequest.java @@ -1,15 +1,14 @@ package me.zhyd.oauth.request; -import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthUser; -import me.zhyd.oauth.model.AuthUserGender; import me.zhyd.oauth.utils.UrlBuilder; /** @@ -19,7 +18,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * @version 1.0 * @since 1.8 */ -public class AuthTencentCloudRequest extends BaseAuthRequest { +public class AuthTencentCloudRequest extends AuthDefaultRequest { public AuthTencentCloudRequest(AuthConfig config) { super(config, AuthSource.TENCENT_CLOUD); @@ -27,42 +26,48 @@ public class AuthTencentCloudRequest extends BaseAuthRequest { @Override protected AuthToken getAccessToken(AuthCallback authCallback) { - String accessTokenUrl = UrlBuilder.getTencentCloudAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode()); - HttpResponse response = HttpRequest.get(accessTokenUrl).execute(); + HttpResponse response = doGetAuthorizationCode(authCallback.getCode()); JSONObject accessTokenObject = JSONObject.parseObject(response.body()); - if (accessTokenObject.getIntValue("code") != 0) { - throw new AuthException("Unable to get token from tencent cloud using code [" + authCallback.getCode() + "]: " + accessTokenObject.get("msg")); - } + this.checkResponse(accessTokenObject); return AuthToken.builder() - .accessToken(accessTokenObject.getString("access_token")) - .expireIn(accessTokenObject.getIntValue("expires_in")) - .refreshToken(accessTokenObject.getString("refresh_token")) - .build(); + .accessToken(accessTokenObject.getString("access_token")) + .expireIn(accessTokenObject.getIntValue("expires_in")) + .refreshToken(accessTokenObject.getString("refresh_token")) + .build(); } @Override protected AuthUser getUserInfo(AuthToken authToken) { - String accessToken = authToken.getAccessToken(); - HttpResponse response = HttpRequest.get(UrlBuilder.getTencentCloudUserInfoUrl(accessToken)).execute(); + HttpResponse response = doGetUserInfo(authToken); JSONObject object = JSONObject.parseObject(response.body()); + this.checkResponse(object); + + object = object.getJSONObject("data"); + return AuthUser.builder() + .uuid(object.getString("id")) + .username(object.getString("name")) + .avatar("https://dev.tencent.com/" + object.getString("avatar")) + .blog("https://dev.tencent.com/" + object.getString("path")) + .nickname(object.getString("name")) + .company(object.getString("company")) + .location(object.getString("location")) + .gender(AuthUserGender.getRealGender(object.getString("sex"))) + .email(object.getString("email")) + .remark(object.getString("slogan")) + .token(authToken) + .source(AuthSource.TENCENT_CLOUD) + .build(); + } + + /** + * 检查响应内容是否正确 + * + * @param object 请求响应内容 + */ + private void checkResponse(JSONObject object) { if (object.getIntValue("code") != 0) { throw new AuthException(object.getString("msg")); } - object = object.getJSONObject("data"); - return AuthUser.builder() - .uuid(object.getString("id")) - .username(object.getString("name")) - .avatar("https://dev.tencent.com/" + object.getString("avatar")) - .blog("https://dev.tencent.com/" + object.getString("path")) - .nickname(object.getString("name")) - .company(object.getString("company")) - .location(object.getString("location")) - .gender(AuthUserGender.getRealGender(object.getString("sex"))) - .email(object.getString("email")) - .remark(object.getString("slogan")) - .token(authToken) - .source(AuthSource.TENCENT_CLOUD) - .build(); } /** @@ -72,6 +77,12 @@ public class AuthTencentCloudRequest extends BaseAuthRequest { */ @Override public String authorize() { - return UrlBuilder.getTencentCloudAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); + return UrlBuilder.fromBaseUrl(source.authorize()) + .queryParam("response_type", "code") + .queryParam("client_id", config.getClientId()) + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("scope", "user") + .queryParam("state", getRealState(config.getState())) + .build(); } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthToutiaoRequest.java b/src/main/java/me/zhyd/oauth/request/AuthToutiaoRequest.java index 878f630..85ff25e 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthToutiaoRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthToutiaoRequest.java @@ -1,13 +1,15 @@ package me.zhyd.oauth.request; -import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.enums.AuthToutiaoErrorCode; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.exception.AuthException; -import me.zhyd.oauth.model.*; +import me.zhyd.oauth.model.AuthCallback; +import me.zhyd.oauth.model.AuthToken; +import me.zhyd.oauth.model.AuthUser; import me.zhyd.oauth.utils.UrlBuilder; /** @@ -17,7 +19,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * @version 1.5 * @since 1.5 */ -public class AuthToutiaoRequest extends BaseAuthRequest { +public class AuthToutiaoRequest extends AuthDefaultRequest { public AuthToutiaoRequest(AuthConfig config) { super(config, AuthSource.TOUTIAO); @@ -25,30 +27,25 @@ public class AuthToutiaoRequest extends BaseAuthRequest { @Override protected AuthToken getAccessToken(AuthCallback authCallback) { - String accessTokenUrl = UrlBuilder.getToutiaoAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode()); - HttpResponse response = HttpRequest.get(accessTokenUrl).execute(); + HttpResponse response = doGetAuthorizationCode(authCallback.getCode()); JSONObject accessTokenObject = JSONObject.parseObject(response.body()); - if (accessTokenObject.containsKey("error_code")) { - throw new AuthException(AuthToutiaoErrorCode.getErrorCode(accessTokenObject.getIntValue("error_code")).getDesc()); - } + this.checkResponse(accessTokenObject); return AuthToken.builder() - .accessToken(accessTokenObject.getString("access_token")) - .expireIn(accessTokenObject.getIntValue("expires_in")) - .openId(accessTokenObject.getString("open_id")) - .build(); + .accessToken(accessTokenObject.getString("access_token")) + .expireIn(accessTokenObject.getIntValue("expires_in")) + .openId(accessTokenObject.getString("open_id")) + .build(); } @Override protected AuthUser getUserInfo(AuthToken authToken) { - HttpResponse userResponse = HttpRequest.get(UrlBuilder.getToutiaoUserInfoUrl(config.getClientId(), authToken.getAccessToken())).execute(); + HttpResponse userResponse = doGetUserInfo(authToken); JSONObject userProfile = JSONObject.parseObject(userResponse.body()); - if (userProfile.containsKey("error_code")) { - throw new AuthException(AuthToutiaoErrorCode.getErrorCode(userProfile.getIntValue("error_code")).getDesc()); - } + this.checkResponse(userProfile); JSONObject user = userProfile.getJSONObject("data"); @@ -56,15 +53,15 @@ public class AuthToutiaoRequest extends BaseAuthRequest { String anonymousUserName = "匿名用户"; return AuthUser.builder() - .uuid(user.getString("uid")) - .username(isAnonymousUser ? anonymousUserName : user.getString("screen_name")) - .nickname(isAnonymousUser ? anonymousUserName : user.getString("screen_name")) - .avatar(user.getString("avatar_url")) - .remark(user.getString("description")) - .gender(AuthUserGender.getRealGender(user.getString("gender"))) - .token(authToken) - .source(AuthSource.TOUTIAO) - .build(); + .uuid(user.getString("uid")) + .username(isAnonymousUser ? anonymousUserName : user.getString("screen_name")) + .nickname(isAnonymousUser ? anonymousUserName : user.getString("screen_name")) + .avatar(user.getString("avatar_url")) + .remark(user.getString("description")) + .gender(AuthUserGender.getRealGender(user.getString("gender"))) + .token(authToken) + .source(AuthSource.TOUTIAO) + .build(); } /** @@ -74,6 +71,55 @@ public class AuthToutiaoRequest extends BaseAuthRequest { */ @Override public String authorize() { - return UrlBuilder.getToutiaoAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); + return UrlBuilder.fromBaseUrl(source.authorize()) + .queryParam("response_type", "code") + .queryParam("client_key", config.getClientId()) + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("state", getRealState(config.getState())) + .queryParam("auth_only", 1) + .queryParam("display", 0) + .build(); + } + + /** + * 返回获取accessToken的url + * + * @param code 授权码 + * @return 返回获取accessToken的url + */ + @Override + protected String accessTokenUrl(String code) { + return UrlBuilder.fromBaseUrl(source.accessToken()) + .queryParam("code", code) + .queryParam("client_key", config.getClientId()) + .queryParam("client_secret", config.getClientSecret()) + .queryParam("grant_type", "authorization_code") + .build(); + } + + /** + * 返回获取userInfo的url + * + * @param authToken 用户授权后的token + * @return 返回获取userInfo的url + */ + @Override + protected String userInfoUrl(AuthToken authToken) { + return UrlBuilder.fromBaseUrl(source.userInfo()) + .queryParam("client_key", config.getClientId()) + .queryParam("access_token", authToken.getAccessToken()) + .build(); + } + + /** + * 检查响应内容是否正确 + * + * @param object 请求响应内容 + */ + private void checkResponse(JSONObject object) { + if (object.containsKey("error_code")) { + throw new AuthException(AuthToutiaoErrorCode.getErrorCode(object.getIntValue("error_code")) + .getDesc()); + } } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthWeChatRequest.java b/src/main/java/me/zhyd/oauth/request/AuthWeChatRequest.java index d081c08..2d426c5 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthWeChatRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthWeChatRequest.java @@ -5,6 +5,7 @@ import cn.hutool.http.HttpResponse; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.model.*; import me.zhyd.oauth.utils.UrlBuilder; @@ -16,7 +17,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * @version 1.0 * @since 1.8 */ -public class AuthWeChatRequest extends BaseAuthRequest { +public class AuthWeChatRequest extends AuthDefaultRequest { public AuthWeChatRequest(AuthConfig config) { super(config, AuthSource.WECHAT); } @@ -29,55 +30,42 @@ public class AuthWeChatRequest extends BaseAuthRequest { */ @Override protected AuthToken getAccessToken(AuthCallback authCallback) { - String accessTokenUrl = UrlBuilder.getWeChatAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode()); - return this.getToken(accessTokenUrl); + return this.getToken(accessTokenUrl(authCallback.getCode())); } @Override protected AuthUser getUserInfo(AuthToken authToken) { - String accessToken = authToken.getAccessToken(); String openId = authToken.getOpenId(); - HttpResponse response = HttpRequest.get(UrlBuilder.getWeChatUserInfoUrl(accessToken, openId)).execute(); + HttpResponse response = doGetUserInfo(authToken); JSONObject object = JSONObject.parseObject(response.body()); this.checkResponse(object); String location = String.format("%s-%s-%s", object.getString("country"), object.getString("province"), object.getString("city")); - if (object.containsKey("unionid")){ + if (object.containsKey("unionid")) { authToken.setUnionId(object.getString("unionid")); } return AuthUser.builder() - .username(object.getString("nickname")) - .nickname(object.getString("nickname")) - .avatar(object.getString("headimgurl")) - .location(location) - .uuid(openId) - .gender(AuthUserGender.getRealGender(object.getString("sex"))) - .token(authToken) - .source(AuthSource.WECHAT) - .build(); - } - - /** - * 返回认证url,可自行跳转页面 - * - * @return 返回授权地址 - */ - @Override - public String authorize() { - return UrlBuilder.getWeChatAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); + .username(object.getString("nickname")) + .nickname(object.getString("nickname")) + .avatar(object.getString("headimgurl")) + .location(location) + .uuid(openId) + .gender(AuthUserGender.getRealGender(object.getString("sex"))) + .token(authToken) + .source(AuthSource.WECHAT) + .build(); } @Override public AuthResponse refresh(AuthToken oldToken) { - String refreshTokenUrl = UrlBuilder.getWeChatRefreshUrl(config.getClientId(), oldToken.getRefreshToken()); return AuthResponse.builder() - .code(ResponseStatus.SUCCESS.getCode()) - .data(this.getToken(refreshTokenUrl)) - .build(); + .code(AuthResponseStatus.SUCCESS.getCode()) + .data(this.getToken(refreshTokenUrl(oldToken.getRefreshToken()))) + .build(); } /** @@ -104,10 +92,72 @@ public class AuthWeChatRequest extends BaseAuthRequest { this.checkResponse(accessTokenObject); return AuthToken.builder() - .accessToken(accessTokenObject.getString("access_token")) - .refreshToken(accessTokenObject.getString("refresh_token")) - .expireIn(accessTokenObject.getIntValue("expires_in")) - .openId(accessTokenObject.getString("openid")) - .build(); + .accessToken(accessTokenObject.getString("access_token")) + .refreshToken(accessTokenObject.getString("refresh_token")) + .expireIn(accessTokenObject.getIntValue("expires_in")) + .openId(accessTokenObject.getString("openid")) + .build(); + } + + /** + * 返回认证url,可自行跳转页面 + * + * @return 返回授权地址 + */ + @Override + public String authorize() { + return UrlBuilder.fromBaseUrl(source.authorize()) + .queryParam("response_type", "code") + .queryParam("appid", config.getClientId()) + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("scope", "snsapi_login") + .queryParam("state", getRealState(config.getState()).concat("#wechat_redirect")) + .build(); + } + + /** + * 返回获取accessToken的url + * + * @param code 授权码 + * @return 返回获取accessToken的url + */ + @Override + protected String accessTokenUrl(String code) { + return UrlBuilder.fromBaseUrl(source.accessToken()) + .queryParam("code", code) + .queryParam("appid", config.getClientId()) + .queryParam("secret", config.getClientSecret()) + .queryParam("grant_type", "authorization_code") + .build(); + } + + /** + * 返回获取userInfo的url + * + * @param authToken 用户授权后的token + * @return 返回获取userInfo的url + */ + @Override + protected String userInfoUrl(AuthToken authToken) { + return UrlBuilder.fromBaseUrl(source.userInfo()) + .queryParam("access_token", authToken.getAccessToken()) + .queryParam("openid", authToken.getOpenId()) + .queryParam("lang", "zh_CN") + .build(); + } + + /** + * 返回获取userInfo的url + * + * @param refreshToken getAccessToken方法返回的refreshToken + * @return 返回获取userInfo的url + */ + @Override + protected String refreshTokenUrl(String refreshToken) { + return UrlBuilder.fromBaseUrl(source.refresh()) + .queryParam("appid", config.getClientId()) + .queryParam("refresh_token", refreshToken) + .queryParam("grant_type", "refresh_token") + .build(); } } diff --git a/src/main/java/me/zhyd/oauth/request/AuthWeiboRequest.java b/src/main/java/me/zhyd/oauth/request/AuthWeiboRequest.java index 366ee22..a792573 100644 --- a/src/main/java/me/zhyd/oauth/request/AuthWeiboRequest.java +++ b/src/main/java/me/zhyd/oauth/request/AuthWeiboRequest.java @@ -5,11 +5,11 @@ import cn.hutool.http.HttpResponse; import com.alibaba.fastjson.JSONObject; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.enums.AuthUserGender; import me.zhyd.oauth.exception.AuthException; import me.zhyd.oauth.model.AuthCallback; import me.zhyd.oauth.model.AuthToken; import me.zhyd.oauth.model.AuthUser; -import me.zhyd.oauth.model.AuthUserGender; import me.zhyd.oauth.utils.IpUtils; import me.zhyd.oauth.utils.StringUtils; import me.zhyd.oauth.utils.UrlBuilder; @@ -22,7 +22,7 @@ import me.zhyd.oauth.utils.UrlBuilder; * @version 1.0 * @since 1.8 */ -public class AuthWeiboRequest extends BaseAuthRequest { +public class AuthWeiboRequest extends AuthDefaultRequest { public AuthWeiboRequest(AuthConfig config) { super(config, AuthSource.WEIBO); @@ -30,20 +30,18 @@ public class AuthWeiboRequest extends BaseAuthRequest { @Override protected AuthToken getAccessToken(AuthCallback authCallback) { - String accessTokenUrl = UrlBuilder.getWeiboAccessTokenUrl(config.getClientId(), config.getClientSecret(), authCallback.getCode(), config - .getRedirectUri()); - HttpResponse response = HttpRequest.post(accessTokenUrl).execute(); + HttpResponse response = doPostAuthorizationCode(authCallback.getCode()); String accessTokenStr = response.body(); JSONObject accessTokenObject = JSONObject.parseObject(accessTokenStr); if (accessTokenObject.containsKey("error")) { - throw new AuthException("Unable to get token from weibo using code [" + authCallback.getCode() + "]:" + accessTokenObject.getString("error_description")); + throw new AuthException(accessTokenObject.getString("error_description")); } return AuthToken.builder() - .accessToken(accessTokenObject.getString("access_token")) - .uid(accessTokenObject.getString("uid")) - .openId(accessTokenObject.getString("uid")) - .expireIn(accessTokenObject.getIntValue("expires_in")) - .build(); + .accessToken(accessTokenObject.getString("access_token")) + .uid(accessTokenObject.getString("uid")) + .openId(accessTokenObject.getString("uid")) + .expireIn(accessTokenObject.getIntValue("expires_in")) + .build(); } @Override @@ -51,37 +49,41 @@ public class AuthWeiboRequest extends BaseAuthRequest { String accessToken = authToken.getAccessToken(); String uid = authToken.getUid(); String oauthParam = String.format("uid=%s&access_token=%s", uid, accessToken); - HttpResponse response = HttpRequest.get(UrlBuilder.getWeiboUserInfoUrl(oauthParam)) - .header("Authorization", "OAuth2 " + oauthParam) - .header("API-RemoteIP", IpUtils.getIp()) - .execute(); + HttpResponse response = HttpRequest.get(userInfoUrl(authToken)) + .header("Authorization", "OAuth2 " + oauthParam) + .header("API-RemoteIP", IpUtils.getIp()) + .execute(); String userInfo = response.body(); JSONObject object = JSONObject.parseObject(userInfo); if (object.containsKey("error")) { throw new AuthException(object.getString("error")); } return AuthUser.builder() - .uuid(object.getString("id")) - .username(object.getString("name")) - .avatar(object.getString("profile_image_url")) - .blog(StringUtils.isEmpty(object.getString("url")) ? "https://weibo.com/" + object.getString("profile_url") : object - .getString("url")) - .nickname(object.getString("screen_name")) - .location(object.getString("location")) - .remark(object.getString("description")) - .gender(AuthUserGender.getRealGender(object.getString("gender"))) - .token(authToken) - .source(AuthSource.WEIBO) - .build(); + .uuid(object.getString("id")) + .username(object.getString("name")) + .avatar(object.getString("profile_image_url")) + .blog(StringUtils.isEmpty(object.getString("url")) ? "https://weibo.com/" + object.getString("profile_url") : object + .getString("url")) + .nickname(object.getString("screen_name")) + .location(object.getString("location")) + .remark(object.getString("description")) + .gender(AuthUserGender.getRealGender(object.getString("gender"))) + .token(authToken) + .source(AuthSource.WEIBO) + .build(); } /** - * 返回认证url,可自行跳转页面 + * 返回获取userInfo的url * - * @return 返回授权地址 + * @param authToken + * @return 返回获取userInfo的url */ @Override - public String authorize() { - return UrlBuilder.getWeiboAuthorizeUrl(config.getClientId(), config.getRedirectUri(), config.getState()); + protected String userInfoUrl(AuthToken authToken) { + return UrlBuilder.fromBaseUrl(source.userInfo()) + .queryParam("access_token", authToken.getAccessToken()) + .queryParam("uid", authToken.getUid()) + .build(); } } diff --git a/src/main/java/me/zhyd/oauth/request/BaseAuthRequest.java b/src/main/java/me/zhyd/oauth/request/BaseAuthRequest.java deleted file mode 100644 index 8cf1fb1..0000000 --- a/src/main/java/me/zhyd/oauth/request/BaseAuthRequest.java +++ /dev/null @@ -1,66 +0,0 @@ -package me.zhyd.oauth.request; - -import lombok.Data; -import me.zhyd.oauth.config.AuthConfig; -import me.zhyd.oauth.config.AuthSource; -import me.zhyd.oauth.exception.AuthException; -import me.zhyd.oauth.model.AuthCallback; -import me.zhyd.oauth.model.AuthResponse; -import me.zhyd.oauth.model.AuthToken; -import me.zhyd.oauth.model.AuthUser; -import me.zhyd.oauth.utils.AuthChecker; - -/** - * @author yadong.zhang (yadong.zhang0415(a)gmail.com) - * @version 1.0 - * @since 1.8 - */ -@Data -public abstract class BaseAuthRequest implements AuthRequest { - protected AuthConfig config; - protected AuthSource source; - - public BaseAuthRequest(AuthConfig config, AuthSource source) { - this.config = config; - this.source = source; - if (!AuthChecker.isSupportedAuth(config, source)) { - throw new AuthException(ResponseStatus.PARAMETER_INCOMPLETE); - } - // 校验配置合法性 - AuthChecker.checkConfig(config, source); - } - - protected abstract AuthToken getAccessToken(AuthCallback authCallback); - - protected abstract AuthUser getUserInfo(AuthToken authToken); - - @Override - public AuthResponse login(AuthCallback authCallback) { - try { - AuthChecker.checkCode(source == AuthSource.ALIPAY ? authCallback.getAuth_code() : authCallback.getCode()); - AuthChecker.checkState(authCallback.getState(), config.getState()); - - AuthToken authToken = this.getAccessToken(authCallback); - AuthUser user = this.getUserInfo(authToken); - return AuthResponse.builder().code(ResponseStatus.SUCCESS.getCode()).data(user).build(); - } catch (Exception e) { - return this.responseError(e); - } - } - - private AuthResponse responseError(Exception e) { - int errorCode = ResponseStatus.FAILURE.getCode(); - if (e instanceof AuthException) { - errorCode = ((AuthException) e).getErrorCode(); - } - return AuthResponse.builder().code(errorCode).msg(e.getMessage()).build(); - } - - /** - * 返回认证url,可自行跳转页面 - * - * @return 返回授权地址 - */ - @Override - public abstract String authorize(); -} diff --git a/src/main/java/me/zhyd/oauth/utils/AuthChecker.java b/src/main/java/me/zhyd/oauth/utils/AuthChecker.java index 994424b..b4e3655 100644 --- a/src/main/java/me/zhyd/oauth/utils/AuthChecker.java +++ b/src/main/java/me/zhyd/oauth/utils/AuthChecker.java @@ -3,7 +3,7 @@ package me.zhyd.oauth.utils; import me.zhyd.oauth.config.AuthConfig; import me.zhyd.oauth.config.AuthSource; import me.zhyd.oauth.exception.AuthException; -import me.zhyd.oauth.request.ResponseStatus; +import me.zhyd.oauth.model.AuthResponseStatus; /** * 授权配置类的校验器 @@ -38,15 +38,15 @@ public class AuthChecker { public static void checkConfig(AuthConfig config, AuthSource source) { String redirectUri = config.getRedirectUri(); if (!GlobalAuthUtil.isHttpProtocol(redirectUri) && !GlobalAuthUtil.isHttpsProtocol(redirectUri)) { - throw new AuthException(ResponseStatus.ILLEGAL_REDIRECT_URI); + throw new AuthException(AuthResponseStatus.ILLEGAL_REDIRECT_URI); } // facebook的回调地址必须为https的链接 if (AuthSource.FACEBOOK == source && !GlobalAuthUtil.isHttpsProtocol(redirectUri)) { - throw new AuthException(ResponseStatus.ILLEGAL_REDIRECT_URI); + throw new AuthException(AuthResponseStatus.ILLEGAL_REDIRECT_URI); } // 支付宝在创建回调地址时,不允许使用localhost或者127.0.0.1 if (AuthSource.ALIPAY == source && GlobalAuthUtil.isLocalHost(redirectUri)) { - throw new AuthException(ResponseStatus.ILLEGAL_REDIRECT_URI); + throw new AuthException(AuthResponseStatus.ILLEGAL_REDIRECT_URI); } } @@ -57,7 +57,7 @@ public class AuthChecker { */ public static void checkCode(String code) { if (StringUtils.isEmpty(code)) { - throw new AuthException(ResponseStatus.ILLEGAL_CODE); + throw new AuthException(AuthResponseStatus.ILLEGAL_CODE); } } @@ -74,11 +74,11 @@ public class AuthChecker { } // 如果授权之前使用了state,但是回调时未返回state,则表示当前请求为非法的请求,可能正在被CSRF攻击 if (StringUtils.isEmpty(newState)) { - throw new AuthException(ResponseStatus.ILLEGAL_REQUEST); + throw new AuthException(AuthResponseStatus.ILLEGAL_REQUEST); } // 如果授权前后的state不一致,则表示当前请求为非法的请求,新的state可能为伪造 if (!newState.equals(originalState)) { - throw new AuthException(ResponseStatus.ILLEGAL_REQUEST); + throw new AuthException(AuthResponseStatus.ILLEGAL_REQUEST); } } } diff --git a/src/main/java/me/zhyd/oauth/utils/AuthState.java b/src/main/java/me/zhyd/oauth/utils/AuthState.java index 289cca1..948ac96 100644 --- a/src/main/java/me/zhyd/oauth/utils/AuthState.java +++ b/src/main/java/me/zhyd/oauth/utils/AuthState.java @@ -4,8 +4,9 @@ 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.config.AuthSource; import me.zhyd.oauth.exception.AuthException; -import me.zhyd.oauth.request.ResponseStatus; +import me.zhyd.oauth.model.AuthResponseStatus; import java.nio.charset.Charset; import java.util.concurrent.ConcurrentHashMap; @@ -30,6 +31,16 @@ public class AuthState { */ private static ConcurrentHashMap stateBucket = new ConcurrentHashMap<>(); + /** + * 生成随机的state + * + * @param source oauth平台 + * @return state + */ + public static String create(AuthSource source) { + return create(source.name()); + } + /** * 生成随机的state * @@ -113,7 +124,7 @@ public class AuthState { String noneSourceState = decodedState.substring(source.length() + 1); if (!noneSourceState.startsWith(currentIp)) { // ip不相同,可能为非法的请求 - throw new AuthException(ResponseStatus.ILLEGAL_REQUEST); + throw new AuthException(AuthResponseStatus.ILLEGAL_REQUEST); } String body = noneSourceState.substring(currentIp.length() + 1); log.debug("body is [{}]", body); @@ -158,6 +169,15 @@ public class AuthState { stateBucket.remove(key); } + /** + * 登录成功后,清除state + * + * @param source oauth平台 + */ + public static void delete(AuthSource source) { + delete(source.name()); + } + private static String getCurrentIp() { String currentIp = IpUtils.getIp(); return StringUtils.isEmpty(currentIp) ? EMPTY_STR : currentIp; diff --git a/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java b/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java index 22bb699..634463f 100644 --- a/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java +++ b/src/main/java/me/zhyd/oauth/utils/GlobalAuthUtil.java @@ -1,6 +1,9 @@ package me.zhyd.oauth.utils; import cn.hutool.core.codec.Base64; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.http.HttpUtil; import me.zhyd.oauth.exception.AuthException; import javax.crypto.Mac; @@ -12,9 +15,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; +import java.util.*; /** * 全局的工具类 @@ -82,6 +83,26 @@ public class GlobalAuthUtil { return res; } + + public static String parseMapToString(Map params, boolean encode) { + List paramList = new ArrayList<>(); + params.forEach((k, v) -> { + if (ObjectUtil.isNull(v)) { + paramList.add(k + "="); + } else { + String valueString = v.toString(); + paramList.add(k + "=" + (encode ? urlEncode(valueString) : valueString)); + } + }); + return CollUtil.join(paramList, "&"); + } + + public static Map parseQueryToMap(String url) { + Map paramMap = new HashMap<>(); + HttpUtil.decodeParamMap(url, "UTF-8").forEach(paramMap::put); + return paramMap; + } + public static boolean isHttpProtocol(String url) { if (StringUtils.isEmpty(url)) { return false; diff --git a/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java b/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java index d8ae653..f34be27 100644 --- a/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java +++ b/src/main/java/me/zhyd/oauth/utils/UrlBuilder.java @@ -1,844 +1,80 @@ package me.zhyd.oauth.utils; -import me.zhyd.oauth.config.AuthSource; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import lombok.Setter; -import java.text.MessageFormat; +import java.util.LinkedHashMap; +import java.util.Map; /** - * Url构建工具类 + *

+ * 构造URL + *

* - * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @author yangkai.shen (https://xkcoding.com) + * @date Created in 2019-07-18 15:47 * @version 1.0 - * @since 1.0 + * @since 1.8 */ +@Setter public class UrlBuilder { - private static final String GITHUB_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&code={3}&redirect_uri={4}"; - private static final String GITHUB_USER_INFO_PATTERN = "{0}?access_token={1}"; - private static final String GITHUB_AUTHORIZE_PATTERN = "{0}?client_id={1}&redirect_uri={2}&state={3}"; + private final Map params = new LinkedHashMap<>(7); + private String baseUrl; - private static final String GOOGLE_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&scope=openid%20email%20profile&redirect_uri={2}&state={3}"; - private static final String GOOGLE_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&code={3}&redirect_uri={4}&grant_type=authorization_code"; - private static final String GOOGLE_USER_INFO_PATTERN = "{0}?id_token={1}"; + private UrlBuilder() { - private static final String WEIBO_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&grant_type=authorization_code&code={3}&redirect_uri={4}"; - private static final String WEIBO_USER_INFO_PATTERN = "{0}?{1}"; - private static final String WEIBO_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}&state={3}"; - - private static final String GITEE_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&grant_type=authorization_code&code={3}&redirect_uri={4}"; - private static final String GITEE_USER_INFO_PATTERN = "{0}?access_token={1}"; - private static final String GITEE_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}&state={3}"; - - private static final String DING_TALK_QRCONNECT_PATTERN = "{0}?appid={1}&response_type=code&scope=snsapi_login&redirect_uri={2}&state={3}"; - private static final String DING_TALK_USER_INFO_PATTERN = "{0}?signature={1}×tamp={2}&accessKey={3}"; - - private static final String BAIDU_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&grant_type=authorization_code&code={3}&redirect_uri={4}"; - private static final String BAIDU_USER_INFO_PATTERN = "{0}?access_token={1}"; - private static final String BAIDU_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}&display=popup&state={3}"; - private static final String BAIDU_REVOKE_PATTERN = "{0}?access_token={1}"; - - private static final String CSDN_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&grant_type=authorization_code&code={3}&redirect_uri={4}"; - private static final String CSDN_USER_INFO_PATTERN = "{0}?access_token={1}"; - private static final String CSDN_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}&state={3}"; - - private static final String CODING_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&grant_type=authorization_code&code={3}"; - private static final String CODING_USER_INFO_PATTERN = "{0}?access_token={1}"; - private static final String CODING_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}&scope=user&state={3}"; - - private static final String TENCENT_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&grant_type=authorization_code&code={3}"; - private static final String TENCENT_USER_INFO_PATTERN = "{0}?access_token={1}"; - private static final String TENCENT_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}&scope=user&state={3}"; - - private static final String OSCHINA_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&grant_type=authorization_code&code={3}&redirect_uri={4}&dataType=json"; - private static final String OSCHINA_USER_INFO_PATTERN = "{0}?access_token={1}&dataType=json"; - private static final String OSCHINA_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}&state={3}"; - - private static final String ALIPAY_AUTHORIZE_PATTERN = "{0}?app_id={1}&scope=auth_user&redirect_uri={2}&state={3}"; - - private static final String QQ_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&grant_type=authorization_code&code={3}&redirect_uri={4}"; - private static final String QQ_USER_INFO_PATTERN = "{0}?oauth_consumer_key={1}&access_token={2}&openid={3}"; - private static final String QQ_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}&state={3}"; - private static final String QQ_OPENID_PATTERN = "{0}?access_token={1}&unionid={2}"; - - private static final String WECHAT_AUTHORIZE_PATTERN = "{0}?appid={1}&redirect_uri={2}&response_type=code&scope=snsapi_login&state={3}#wechat_redirect"; - private static final String WECHAT_ACCESS_TOKEN_PATTERN = "{0}?appid={1}&secret={2}&code={3}&grant_type=authorization_code"; - private static final String WECHAT_REFRESH_TOKEN_PATTERN = "{0}?appid={1}&grant_type=refresh_token&refresh_token={2}"; - private static final String WECHAT_USER_INFO_PATTERN = "{0}?access_token={1}&openid={2}&lang=zh_CN"; - - private static final String TAOBAO_AUTHORIZE_PATTERN = "{0}?response_type=code&client_id={1}&redirect_uri={2}&state={3}&view=web"; - private static final String TAOBAO_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&code={3}&redirect_uri={4}&grant_type=authorization_code"; - - private static final String FACEBOOK_AUTHORIZE_PATTERN = "{0}?client_id={1}&redirect_uri={2}&state={3}&response_type=code&scope="; - private static final String FACEBOOK_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&code={3}&redirect_uri={4}&grant_type=authorization_code"; - private static final String FACEBOOK_USER_INFO_PATTERN = "{0}?access_token={1}&fields=id,name,birthday,gender,hometown,email,devices,picture.width(400)"; - - private static final String DOUYIN_AUTHORIZE_PATTERN = "{0}?client_key={1}&redirect_uri={2}&state={3}&response_type=code&scope=user_info"; - private static final String DOUYIN_ACCESS_TOKEN_PATTERN = "{0}?client_key={1}&client_secret={2}&code={3}&grant_type=authorization_code"; - private static final String DOUYIN_USER_INFO_PATTERN = "{0}?access_token={1}&open_id={2}"; - private static final String DOUYIN_REFRESH_TOKEN_PATTERN = "{0}?client_key={1}&refresh_token={2}&grant_type=refresh_token"; - - private static final String LINKEDIN_AUTHORIZE_PATTERN = "{0}?client_id={1}&redirect_uri={2}&state={3}&response_type=code&scope=r_liteprofile%20r_emailaddress%20w_member_social"; - private static final String LINKEDIN_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&code={3}&redirect_uri={4}&grant_type=authorization_code"; - private static final String LINKEDIN_USER_INFO_PATTERN = "{0}?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))"; - private static final String LINKEDIN_REFRESH_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&refresh_token={3}&grant_type=refresh_token"; - - private static final String MICROSOFT_AUTHORIZE_PATTERN = "{0}?client_id={1}&response_type=code&redirect_uri={2}&response_mode=query&scope=offline_access%20user.read%20mail.read&state={3}"; - private static final String MICROSOFT_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&scope=user.read%20mail.read&redirect_uri={3}&code={4}&grant_type=authorization_code"; - private static final String MICROSOFT_USER_INFO_PATTERN = "{0}"; - private static final String MICROSOFT_REFRESH_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&scope=user.read%20mail.read&redirect_uri={3}&refresh_token={4}&grant_type=refresh_token"; - - private static final String MI_AUTHORIZE_PATTERN = "{0}?client_id={1}&redirect_uri={2}&response_type=code&scope=user/profile%20user/openIdV2%20user/phoneAndEmail&state={3}&skip_confirm=false"; - private static final String MI_ACCESS_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&redirect_uri={3}&code={4}&grant_type=authorization_code"; - private static final String MI_USER_INFO_PATTERN = "{0}?clientId={1}&token={2}"; - private static final String MI_REFRESH_TOKEN_PATTERN = "{0}?client_id={1}&client_secret={2}&redirect_uri={3}&refresh_token={4}&grant_type=refresh_token"; - - private static final String TOUTIAO_ACCESS_TOKEN_PATTERN = "{0}?client_key={1}&client_secret={2}&code={3}&grant_type=authorize_code"; - private static final String TOUTIAO_USER_INFO_PATTERN = "{0}?client_key={1}&access_token={2}"; - private static final String TOUTIAO_AUTHORIZE_PATTERN = "{0}?client_key={1}&redirect_uri={2}&state={3}&response_type=code&auth_only=1&display=0"; - - /** - * 获取state,如果为空, 则默认去当前日期的时间戳 - * - * @param state state - */ - private static Object getState(String state) { - return StringUtils.isEmpty(state) ? String.valueOf(System.currentTimeMillis()) : state; } /** - * 获取githubtoken的接口地址 - * - * @param clientId github 应用的Client ID - * @param clientSecret github 应用的Client Secret - * @param code github 授权前的code,用来换token - * @param redirectUri 待跳转的页面 - * @return full url + * @param baseUrl 基础路径 + * @return the new {@code UrlBuilder} */ - public static String getGithubAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) { - return MessageFormat.format(GITHUB_ACCESS_TOKEN_PATTERN, AuthSource.GITHUB.accessToken(), clientId, clientSecret, code, redirectUri); + public static UrlBuilder fromBaseUrl(String baseUrl) { + UrlBuilder builder = new UrlBuilder(); + builder.setBaseUrl(baseUrl); + return builder; } /** - * 获取github用户详情的接口地址 + * 添加参数 * - * @param token github 应用的token - * @return full url + * @param key 参数名称 + * @param value 参数值 + * @return this UrlBuilder */ - public static String getGithubUserInfoUrl(String token) { - return MessageFormat.format(GITHUB_USER_INFO_PATTERN, AuthSource.GITHUB.userInfo(), token); + public UrlBuilder queryParam(String key, Object value) { + Assert.notBlank(key, "参数名不能为空"); + + String valueAsString = (value != null ? value.toString() : null); + this.params.put(key, valueAsString); + + return this; } /** - * 获取github授权地址 + * 构造url * - * @param clientId github 应用的Client ID - * @param redirectUrl github 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return full url + * @return url */ - public static String getGithubAuthorizeUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(GITHUB_AUTHORIZE_PATTERN, AuthSource.GITHUB.authorize(), clientId, redirectUrl, getState(state)); + public String build() { + return this.build(false); } /** - * 获取weibo token的接口地址 + * 构造url * - * @param clientId weibo 应用的App Key - * @param clientSecret weibo 应用的App Secret - * @param code weibo 授权前的code,用来换token - * @param redirectUri 待跳转的页面 - * @return full url + * @param encode 转码 + * @return url */ - public static String getWeiboAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) { - return MessageFormat.format(WEIBO_ACCESS_TOKEN_PATTERN, AuthSource.WEIBO.accessToken(), clientId, clientSecret, code, redirectUri); - } - - /** - * 获取weibo用户详情的接口地址 - * - * @param token weibo 应用的token - * @return full url - */ - public static String getWeiboUserInfoUrl(String token) { - return MessageFormat.format(WEIBO_USER_INFO_PATTERN, AuthSource.WEIBO.userInfo(), token); - } - - /** - * 获取weibo授权地址 - * - * @param clientId weibo 应用的Client ID - * @param redirectUrl weibo 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return full url - */ - public static String getWeiboAuthorizeUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(WEIBO_AUTHORIZE_PATTERN, AuthSource.WEIBO.authorize(), clientId, redirectUrl, getState(state)); - } - - /** - * 获取gitee token的接口地址 - * - * @param clientId gitee 应用的Client ID - * @param clientSecret gitee 应用的Client Secret - * @param code gitee 授权前的code,用来换token - * @param redirectUri 待跳转的页面 - * @return full url - */ - public static String getGiteeAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) { - return MessageFormat.format(GITEE_ACCESS_TOKEN_PATTERN, AuthSource.GITEE.accessToken(), clientId, clientSecret, code, redirectUri); - } - - /** - * 获取gitee用户详情的接口地址 - * - * @param token gitee 应用的token - * @return full url - */ - public static String getGiteeUserInfoUrl(String token) { - return MessageFormat.format(GITEE_USER_INFO_PATTERN, AuthSource.GITEE.userInfo(), token); - } - - /** - * 获取gitee授权地址 - * - * @param clientId gitee 应用的Client ID - * @param redirectUrl gitee 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return json - */ - public static String getGiteeAuthorizeUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(GITEE_AUTHORIZE_PATTERN, AuthSource.GITEE.authorize(), clientId, redirectUrl, getState(state)); - } - - /** - * 获取钉钉登录二维码的地址 - * - * @param clientId 钉钉 应用的App Id - * @param redirectUrl 钉钉 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return full url - */ - public static String getDingTalkQrConnectUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(DING_TALK_QRCONNECT_PATTERN, AuthSource.DINGTALK.authorize(), clientId, redirectUrl, getState(state)); - } - - /** - * 获取钉钉用户信息的地址 - * - * @param signature 通过appSecret计算出来的签名值,签名计算方法:https://open-doc.dingtalk.com/microapp/faquestions/hxs5v9 - * @param timestamp 当前时间戳,单位是毫秒 - * @param accessKey 钉钉 应用的App Id - * @return full url - */ - public static String getDingTalkUserInfoUrl(String signature, String timestamp, String accessKey) { - return MessageFormat.format(DING_TALK_USER_INFO_PATTERN, AuthSource.DINGTALK.userInfo(), signature, timestamp, accessKey); - } - - /** - * 获取baidu token的接口地址 - * - * @param clientId baidu 应用的API Key - * @param clientSecret baidu 应用的Secret Key - * @param code baidu 授权前的code,用来换token - * @param redirectUri 待跳转的页面 - * @return full url - */ - public static String getBaiduAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) { - return MessageFormat.format(BAIDU_ACCESS_TOKEN_PATTERN, AuthSource.BAIDU.accessToken(), clientId, clientSecret, code, redirectUri); - } - - /** - * 获取baidu用户详情的接口地址 - * - * @param token baidu 应用的token - * @return full url - */ - public static String getBaiduUserInfoUrl(String token) { - return MessageFormat.format(BAIDU_USER_INFO_PATTERN, AuthSource.BAIDU.userInfo(), token); - } - - /** - * 获取baidu授权地址 - * - * @param clientId baidu 应用的API Key - * @param redirectUrl baidu 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return json - */ - public static String getBaiduAuthorizeUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(BAIDU_AUTHORIZE_PATTERN, AuthSource.BAIDU.authorize(), clientId, redirectUrl, getState(state)); - } - - /** - * 获取收回baidu授权的地址 - * - * @param accessToken baidu 授权登录后的token - * @return json - */ - public static String getBaiduRevokeUrl(String accessToken) { - return MessageFormat.format(BAIDU_REVOKE_PATTERN, AuthSource.BAIDU.revoke(), accessToken); - } - - /** - * 获取csdn token的接口地址 - * - * @param clientId csdn 应用的App Key - * @param clientSecret csdn 应用的App Secret - * @param code csdn 授权前的code,用来换token - * @param redirectUri 待跳转的页面 - * @return full url - */ - public static String getCsdnAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) { - return MessageFormat.format(CSDN_ACCESS_TOKEN_PATTERN, AuthSource.CSDN.accessToken(), clientId, clientSecret, code, redirectUri); - } - - /** - * 获取csdn用户详情的接口地址 - * - * @param token csdn 应用的token - * @return full url - */ - public static String getCsdnUserInfoUrl(String token) { - return MessageFormat.format(CSDN_USER_INFO_PATTERN, AuthSource.CSDN.userInfo(), token); - } - - /** - * 获取csdn授权地址 - * - * @param clientId csdn 应用的Client ID - * @param redirectUrl csdn 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return full url - */ - public static String getCsdnAuthorizeUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(CSDN_AUTHORIZE_PATTERN, AuthSource.CSDN.authorize(), clientId, redirectUrl, getState(state)); - } - - /** - * 获取coding token的接口地址 - * - * @param clientId coding 应用的App Key - * @param clientSecret coding 应用的App Secret - * @param code coding 授权前的code,用来换token - * @return full url - */ - public static String getCodingAccessTokenUrl(String clientId, String clientSecret, String code) { - return MessageFormat.format(CODING_ACCESS_TOKEN_PATTERN, AuthSource.CODING.accessToken(), clientId, clientSecret, code); - } - - /** - * 获取coding用户详情的接口地址 - * - * @param token coding 应用的token - * @return full url - */ - public static String getCodingUserInfoUrl(String token) { - return MessageFormat.format(CODING_USER_INFO_PATTERN, AuthSource.CODING.userInfo(), token); - } - - /** - * 获取coding授权地址 - * - * @param clientId coding 应用的Client ID - * @param redirectUrl coding 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return full url - */ - public static String getCodingAuthorizeUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(CODING_AUTHORIZE_PATTERN, AuthSource.CODING.authorize(), clientId, redirectUrl, getState(state)); - } - - /** - * 获取腾讯云开发者平台 token的接口地址 - * - * @param clientId coding 应用的App Key - * @param clientSecret coding 应用的App Secret - * @param code coding 授权前的code,用来换token - * @return full url - */ - public static String getTencentCloudAccessTokenUrl(String clientId, String clientSecret, String code) { - return MessageFormat.format(TENCENT_ACCESS_TOKEN_PATTERN, AuthSource.TENCENT_CLOUD.accessToken(), clientId, clientSecret, code); - } - - /** - * 获取腾讯云开发者平台用户详情的接口地址 - * - * @param token coding 应用的token - * @return full url - */ - public static String getTencentCloudUserInfoUrl(String token) { - return MessageFormat.format(TENCENT_USER_INFO_PATTERN, AuthSource.TENCENT_CLOUD.userInfo(), token); - } - - /** - * 获取腾讯云开发者平台授权地址 - * - * @param clientId coding 应用的Client ID - * @param redirectUrl coding 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return full url - */ - public static String getTencentCloudAuthorizeUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(TENCENT_AUTHORIZE_PATTERN, AuthSource.TENCENT_CLOUD.authorize(), clientId, redirectUrl, getState(state)); - } - - /** - * 获取oschina token的接口地址 - * - * @param clientId oschina 应用的App Key - * @param clientSecret oschina 应用的App Secret - * @param code oschina 授权前的code,用来换token - * @param redirectUri 待跳转的页面 - * @return full url - */ - public static String getOschinaAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) { - return MessageFormat.format(OSCHINA_ACCESS_TOKEN_PATTERN, AuthSource.OSCHINA.accessToken(), clientId, clientSecret, code, redirectUri); - } - - /** - * 获取oschina用户详情的接口地址 - * - * @param token oschina 应用的token - * @return full url - */ - public static String getOschinaUserInfoUrl(String token) { - return MessageFormat.format(OSCHINA_USER_INFO_PATTERN, AuthSource.OSCHINA.userInfo(), token); - } - - /** - * 获取oschina授权地址 - * - * @param clientId oschina 应用的Client ID - * @param redirectUrl oschina 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return full url - */ - public static String getOschinaAuthorizeUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(OSCHINA_AUTHORIZE_PATTERN, AuthSource.OSCHINA.authorize(), clientId, redirectUrl, getState(state)); - } - - /** - * 获取qq token的接口地址 - * - * @param clientId qq 应用的App Key - * @param clientSecret qq 应用的App Secret - * @param code qq 授权前的code,用来换token - * @param redirectUri 待跳转的页面 - * @return full url - */ - public static String getQqAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) { - return MessageFormat.format(QQ_ACCESS_TOKEN_PATTERN, AuthSource.QQ.accessToken(), clientId, clientSecret, code, redirectUri); - } - - /** - * 获取qq用户详情的接口地址 - * - * @param clientId qq 应用的clientId - * @param token qq 应用的token - * @param openId qq 应用的openId - * @return full url - */ - public static String getQqUserInfoUrl(String clientId, String token, String openId) { - return MessageFormat.format(QQ_USER_INFO_PATTERN, AuthSource.QQ.userInfo(), clientId, token, openId); - } - - /** - * 获取qq授权地址 - * - * @param clientId qq 应用的Client ID - * @param redirectUrl qq 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return full url - */ - public static String getQqAuthorizeUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(QQ_AUTHORIZE_PATTERN, AuthSource.QQ.authorize(), clientId, redirectUrl, getState(state)); - } - - /** - * 获取qq授权地址 - * - * @param url 获取qqopenid的api接口地址 - * @param token qq 应用授权的token - * @param unionid 是否需要获取unionid,默认为false。注:获取unionid需要单独发送邮件申请权限,请个人视情况而定。参考链接:http://wiki.connect.qq.com/unionid%E4%BB%8B%E7%BB%8D - * @return full url - */ - public static String getQqOpenidUrl(String url, String token, boolean unionid) { - return MessageFormat.format(QQ_OPENID_PATTERN, url, token, unionid ? 1 : 0); - } - - /** - * 获取alipay授权地址 - * - * @param clientId alipay 应用的Client ID - * @param redirectUrl alipay 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return full url - */ - public static String getAlipayAuthorizeUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(ALIPAY_AUTHORIZE_PATTERN, AuthSource.ALIPAY.authorize(), clientId, redirectUrl, getState(state)); - } - - /** - * 获取微信 授权地址 - * - * @param clientId 微信 应用的appid - * @param redirectUrl 微信 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return full url - */ - public static String getWeChatAuthorizeUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(WECHAT_AUTHORIZE_PATTERN, AuthSource.WECHAT.authorize(), clientId, redirectUrl, getState(state)); - } - - /** - * 获取微信 token的接口地址 - * - * @param clientId 微信 应用的appid - * @param clientSecret 微信 应用的secret - * @param code 微信 授权前的code,用来换token - * @return full url - */ - public static String getWeChatAccessTokenUrl(String clientId, String clientSecret, String code) { - return MessageFormat.format(WECHAT_ACCESS_TOKEN_PATTERN, AuthSource.WECHAT.accessToken(), clientId, clientSecret, code); - } - - /** - * 获取微信 用户详情的接口地址 - * - * @param token 微信 应用返回的 access token - * @param openId 微信 应用返回的openId - * @return full url - */ - public static String getWeChatUserInfoUrl(String token, String openId) { - return MessageFormat.format(WECHAT_USER_INFO_PATTERN, AuthSource.WECHAT.userInfo(), token, openId); - } - - /** - * 获取微信 刷新令牌 地址 - * - * @param clientId 微信 应用的appid - * @param refreshToken 微信 应用返回的刷新token - * @return full url - */ - public static String getWeChatRefreshUrl(String clientId, String refreshToken) { - return MessageFormat.format(WECHAT_REFRESH_TOKEN_PATTERN, AuthSource.WECHAT.refresh(), clientId, refreshToken); - } - - /** - * 获取Taobao token的接口地址: 淘宝的授权登录,在这一步就会返回用户信息 - * - * @param clientId taobao 应用的App Key - * @param clientSecret taobao 应用的App Secret - * @param code taobao 授权前的code,用来换token - * @param redirectUri 待跳转的页面 - * @return full url - */ - public static String getTaobaoAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) { - return MessageFormat.format(TAOBAO_ACCESS_TOKEN_PATTERN, AuthSource.TAOBAO.accessToken(), clientId, clientSecret, code, redirectUri); - } - - /** - * 获取Taobao授权地址 - * - * @param clientId Taobao 应用的Client ID - * @param redirectUrl Taobao 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return full url - */ - public static String getTaobaoAuthorizeUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(TAOBAO_AUTHORIZE_PATTERN, AuthSource.TAOBAO.authorize(), clientId, redirectUrl, getState(state)); - } - - /** - * 获取Google授权地址 - * - * @param clientId google 应用的Client ID - * @param redirectUrl google 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return full url - */ - public static String getGoogleAuthorizeUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(GOOGLE_AUTHORIZE_PATTERN, AuthSource.GOOGLE.authorize(), clientId, redirectUrl, getState(state)); - } - - /** - * 获取Google token的接口地址 - * - * @param clientId google 应用的Client ID - * @param clientSecret google 应用的Client Secret - * @param code google 授权前的code,用来换token - * @param redirectUri 待跳转的页面 - * @return full url - */ - public static String getGoogleAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) { - return MessageFormat.format(GOOGLE_ACCESS_TOKEN_PATTERN, AuthSource.GOOGLE.accessToken(), clientId, clientSecret, code, redirectUri); - } - - /** - * 获取Google用户详情的接口地址 - * - * @param token google 应用的token - * @return full url - */ - public static String getGoogleUserInfoUrl(String token) { - return MessageFormat.format(GOOGLE_USER_INFO_PATTERN, AuthSource.GOOGLE.userInfo(), token); - } - - /** - * 获取Facebook授权地址 - * - * @param clientId Facebook 应用的Client ID - * @param redirectUrl Facebook 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return full url - */ - public static String getFacebookAuthorizeUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(FACEBOOK_AUTHORIZE_PATTERN, AuthSource.FACEBOOK.authorize(), clientId, redirectUrl, getState(state)); - } - - /** - * 获取Facebook token的接口地址 - * - * @param clientId Facebook 应用的Client ID - * @param clientSecret Facebook 应用的Client Secret - * @param code Facebook 授权前的code,用来换token - * @param redirectUri 待跳转的页面 - * @return full url - */ - public static String getFacebookAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUri) { - return MessageFormat.format(FACEBOOK_ACCESS_TOKEN_PATTERN, AuthSource.FACEBOOK.accessToken(), clientId, clientSecret, code, redirectUri); - } - - /** - * 获取Facebook用户详情的接口地址 - * - * @param token Facebook 应用的token - * @return full url - */ - public static String getFacebookUserInfoUrl(String token) { - return MessageFormat.format(FACEBOOK_USER_INFO_PATTERN, AuthSource.FACEBOOK.userInfo(), token); - } - - /** - * 获取Douyin授权地址 - * - * @param clientId Douyin 应用的Client ID - * @param redirectUrl Douyin 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return full url - */ - public static String getDouyinAuthorizeUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(DOUYIN_AUTHORIZE_PATTERN, AuthSource.DOUYIN.authorize(), clientId, redirectUrl, getState(state)); - } - - /** - * 获取Douyin token的接口地址 - * - * @param clientId Douyin 应用的Client ID - * @param clientSecret Douyin 应用的Client Secret - * @param code Douyin 授权前的code,用来换token - * @return full url - */ - public static String getDouyinAccessTokenUrl(String clientId, String clientSecret, String code) { - return MessageFormat.format(DOUYIN_ACCESS_TOKEN_PATTERN, AuthSource.DOUYIN.accessToken(), clientId, clientSecret, code); - } - - /** - * 获取Douyin用户详情的接口地址 - * - * @param token Douyin 应用的token - * @param openId 用户在当前应用的唯一标识 通过token接口获取 - * @return full url - */ - public static String getDouyinUserInfoUrl(String token, String openId) { - return MessageFormat.format(DOUYIN_USER_INFO_PATTERN, AuthSource.DOUYIN.userInfo(), token, openId); - } - - /** - * 获取Douyin 刷新令牌 地址 - * - * @param clientId Douyin 应用的client_key - * @param refreshToken Douyin 应用返回的refresh_token - * @return full url - */ - public static String getDouyinRefreshUrl(String clientId, String refreshToken) { - return MessageFormat.format(DOUYIN_REFRESH_TOKEN_PATTERN, AuthSource.DOUYIN.refresh(), clientId, refreshToken); - } - - /** - * 获取Linkedin授权地址 - * - * @param clientId Linkedin 应用的Client ID - * @param redirectUrl Linkedin 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return full url - */ - public static String getLinkedinAuthorizeUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(LINKEDIN_AUTHORIZE_PATTERN, AuthSource.LINKEDIN.authorize(), clientId, redirectUrl, state); - } - - /** - * 获取Linkedin token的接口地址 - * - * @param clientId Linkedin 应用的Client ID - * @param clientSecret Linkedin 应用的Client Secret - * @param code Linkedin 授权前的code,用来换token - * @param redirectUrl Linkedin 应用授权成功后的回调地址 - * @return full url - */ - public static String getLinkedinAccessTokenUrl(String clientId, String clientSecret, String code, String redirectUrl) { - return MessageFormat.format(LINKEDIN_ACCESS_TOKEN_PATTERN, AuthSource.LINKEDIN.accessToken(), clientId, clientSecret, code, redirectUrl); - } - - /** - * 获取Linkedin用户详情的接口地址 - * - * @return full url - */ - public static String getLinkedinUserInfoUrl() { - return MessageFormat.format(LINKEDIN_USER_INFO_PATTERN, AuthSource.LINKEDIN.userInfo()); - } - - /** - * 获取Linkedin 刷新令牌 地址 - * - * @param clientId Linkedin 应用的client_key - * @param clientSecret Linkedin 应用的Client Secret - * @param refreshToken Linkedin 应用返回的refresh_token - * @return full url - */ - public static String getLinkedinRefreshUrl(String clientId, String clientSecret, String refreshToken) { - return MessageFormat.format(LINKEDIN_REFRESH_TOKEN_PATTERN, AuthSource.LINKEDIN.refresh(), clientId, clientSecret, refreshToken); - } - - /** - * 获取微软授权地址 - * - * @param clientId 微软 应用的Client ID - * @param redirectUrl 微软 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return full url - */ - public static String getMicrosoftAuthorizeUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(MICROSOFT_AUTHORIZE_PATTERN, AuthSource.MICROSOFT.authorize(), clientId, redirectUrl, getState(state)); - } - - /** - * 获取微软 token的接口地址 - * - * @param clientId 微软 应用的Client ID - * @param clientSecret 微软 应用的Client Secret - * @param redirectUrl 微软 应用授权成功后的回调地址 - * @param code 微软 授权前的code,用来换token - * @return full url - */ - public static String getMicrosoftAccessTokenUrl(String clientId, String clientSecret, String redirectUrl, String code) { - return MessageFormat.format(MICROSOFT_ACCESS_TOKEN_PATTERN, AuthSource.MICROSOFT.accessToken(), clientId, clientSecret, redirectUrl, code); - } - - /** - * 获取微软用户详情的接口地址 - * - * @return full url - */ - public static String getMicrosoftUserInfoUrl() { - return MessageFormat.format(MICROSOFT_USER_INFO_PATTERN, AuthSource.MICROSOFT.userInfo()); - } - - /** - * 获取微软 刷新令牌 地址 - * - * @param clientId 微软 应用的client_key - * @param clientSecret 微软 应用的Client Secret - * @param redirectUrl 微软 应用授权成功后的回调地址 - * @param refreshToken 微软 应用返回的refresh_token - * @return full url - */ - public static String getMicrosoftRefreshUrl(String clientId, String clientSecret, String redirectUrl, String refreshToken) { - return MessageFormat.format(MICROSOFT_REFRESH_TOKEN_PATTERN, AuthSource.MICROSOFT.refresh(), clientId, clientSecret, redirectUrl, refreshToken); - } - - /** - * 获取小米授权地址 - * - * @param clientId 小米 应用的Client ID - * @param redirectUrl 小米 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return full url - */ - public static String getMiAuthorizeUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(MI_AUTHORIZE_PATTERN, AuthSource.MI.authorize(), clientId, redirectUrl, getState(state)); - } - - /** - * 获取小米 token的接口地址 - * - * @param clientId 小米 应用的Client ID - * @param clientSecret 小米 应用的Client Secret - * @param redirectUrl 小米 应用授权成功后的回调地址 - * @param code 小米 授权前的code,用来换token - * @return full url - */ - public static String getMiAccessTokenUrl(String clientId, String clientSecret, String redirectUrl, String code) { - return MessageFormat.format(MI_ACCESS_TOKEN_PATTERN, AuthSource.MI.accessToken(), clientId, clientSecret, redirectUrl, code); - } - - /** - * 获取小米用户详情的接口地址 - * - * @param clientId 小米 应用的client_key - * @param token token - * @return full url - */ - public static String getMiUserInfoUrl(String clientId, String token) { - return MessageFormat.format(MI_USER_INFO_PATTERN, AuthSource.MI.userInfo(), clientId, token); - } - - /** - * 获取小米 刷新令牌 地址 - * - * @param clientId 小米 应用的client_key - * @param clientSecret 小米 应用的Client Secret - * @param redirectUrl 小米 应用授权成功后的回调地址 - * @param refreshToken 小米 应用返回的refresh_token - * @return full url - */ - public static String getMiRefreshUrl(String clientId, String clientSecret, String redirectUrl, String refreshToken) { - return MessageFormat.format(MI_REFRESH_TOKEN_PATTERN, AuthSource.MI.refresh(), clientId, clientSecret, redirectUrl, refreshToken); - } - - /** - * 获取今日头条授权地址 - * - * @param clientId 今日头条 应用的Client ID - * @param redirectUrl 今日头条 应用授权成功后的回调地址 - * @param state 随机字符串,用于保持会话状态,防止CSRF攻击 - * @return full url - */ - public static String getToutiaoAuthorizeUrl(String clientId, String redirectUrl, String state) { - return MessageFormat.format(TOUTIAO_AUTHORIZE_PATTERN, AuthSource.TOUTIAO.authorize(), clientId, redirectUrl, getState(state)); - } - - /** - * 获取今日头条 token的接口地址 - * - * @param clientId 今日头条 应用的Client ID - * @param clientSecret 今日头条 应用的Client Secret - * @param code 今日头条 授权前的code,用来换token - * @return full url - */ - public static String getToutiaoAccessTokenUrl(String clientId, String clientSecret, String code) { - return MessageFormat.format(TOUTIAO_ACCESS_TOKEN_PATTERN, AuthSource.TOUTIAO.accessToken(), clientId, clientSecret, code); - } - - /** - * 获取今日头条用户详情的接口地址 - * - * @param clientId 今日头条 应用的client_key - * @param token token - * @return full url - */ - public static String getToutiaoUserInfoUrl(String clientId, String token) { - return MessageFormat.format(TOUTIAO_USER_INFO_PATTERN, AuthSource.TOUTIAO.userInfo(), clientId, token); + public String build(boolean encode) { + if (MapUtil.isEmpty(this.params)) { + return this.baseUrl; + } + String baseUrl = StrUtil.addSuffixIfNot(this.baseUrl, "?"); + String paramString = GlobalAuthUtil.parseMapToString(this.params, encode); + return baseUrl + paramString; } } diff --git a/src/test/java/me/zhyd/oauth/utils/AuthStateTest.java b/src/test/java/me/zhyd/oauth/utils/AuthStateTest.java index f49d382..d73489d 100644 --- a/src/test/java/me/zhyd/oauth/utils/AuthStateTest.java +++ b/src/test/java/me/zhyd/oauth/utils/AuthStateTest.java @@ -26,7 +26,7 @@ public class AuthStateTest { * null */ @Test - public void test() { + public void usage() { String source = "github"; System.out.println("\nstep1 生成state: 预期创建一个新的state..."); String state = AuthState.create(source); diff --git a/src/test/java/me/zhyd/oauth/utils/CustomTest.java b/src/test/java/me/zhyd/oauth/utils/CustomTest.java new file mode 100644 index 0000000..eab7ddd --- /dev/null +++ b/src/test/java/me/zhyd/oauth/utils/CustomTest.java @@ -0,0 +1,63 @@ +package me.zhyd.oauth.utils; + +import org.junit.Test; + +/** + * @author yadong.zhang (yadong.zhang0415(a)gmail.com) + * @version 1.0 + * @website https://www.zhyd.me + * @date 2019/7/19 15:52 + * @since 1.8 + */ +public class CustomTest { + + /** + * 1000000: 23135ms + * 100000: 3016ms + * 10000: 328ms + * 1000: 26ms + */ + @Test + public void test() { + long start = System.currentTimeMillis(); + for (int i = 0; i < 1000; i++) { + callMethod(); + } + long end = System.currentTimeMillis(); + System.out.println((end - start) + "ms"); + + } + + /** + * 1000000: 19058ms + * 100000: 2772ms + * 10000: 323ms + * 1000: 29ms + */ + @Test + public void test2() { + long end = System.currentTimeMillis(); + for (int i = 0; i < 1000; i++) { + callMethod2(); + } + long end2 = System.currentTimeMillis(); + System.out.println((end2 - end) + "ms"); + + } + + public String callMethod() { + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); +// for (StackTraceElement stackTraceElement : stackTrace) { +// System.out.println(stackTraceElement.getMethodName()); +// } + return stackTrace[2].getMethodName(); + } + + public String callMethod2() { + StackTraceElement[] stackTrace = (new Throwable()).getStackTrace(); +// for (StackTraceElement stackTraceElement : stackTrace) { +// System.out.println(stackTraceElement.getMethodName()); +// } + return stackTrace[2].getMethodName(); + } +} diff --git a/src/test/java/me/zhyd/oauth/utils/UrlBuilderTest.java b/src/test/java/me/zhyd/oauth/utils/UrlBuilderTest.java new file mode 100644 index 0000000..161031e --- /dev/null +++ b/src/test/java/me/zhyd/oauth/utils/UrlBuilderTest.java @@ -0,0 +1,38 @@ +package me.zhyd.oauth.utils; + +import me.zhyd.oauth.config.AuthConfig; +import me.zhyd.oauth.config.AuthSource; +import me.zhyd.oauth.request.AuthWeChatRequest; +import org.junit.Assert; +import org.junit.Test; + +/** + *

+ * UrlBuilder测试类 + *

+ * + * @author yangkai.shen (https://xkcoding.com) + * @date Created in 2019-07-18 16:36 + */ +public class UrlBuilderTest { + @Test + public void testUrlBuilder() { + AuthConfig config = AuthConfig.builder() + .clientId("appid-110110110") + .clientSecret("secret-110110110") + .redirectUri("https://xkcoding.com") + .state(AuthState.create(AuthSource.WECHAT)) + .build(); + String build = UrlBuilder.fromBaseUrl(AuthSource.WECHAT.authorize()) + .queryParam("appid", config.getClientId()) + .queryParam("redirect_uri", config.getRedirectUri()) + .queryParam("response_type", "code") + .queryParam("scope", "snsapi_login") + .queryParam("state", config.getState().concat("#wechat_redirect")) + .build(false); + AuthWeChatRequest request = new AuthWeChatRequest(config); + String authorize = request.authorize(); + Assert.assertEquals(build, authorize); + AuthState.delete(AuthSource.WECHAT); + } +} diff --git a/update.md b/update.md index 2b04ff0..ce8152e 100644 --- a/update.md +++ b/update.md @@ -1,3 +1,32 @@ +### 2019/07/19 + +1. 合并github上[@dyc12ii](https://github.com/dyc12ii) 的[pr#25](https://github.com/zhangyd-c/JustAuth/pull/25),升级fastjson版本至1.2.58,避免安全漏洞 +2. `AuthUserGender`枚举类挪到`enums`包下 +3. 删除`AuthBaiduErrorCode`和`AuthDingTalkErrorCode`枚举类 +4. 优化百度授权流程,增加refresh token的方法 +5. 优化`AuthConfig`、`AuthResponse`类,去掉不必要的lombonk注解,减少编译后的代码量 +6. 使用lombok注解优化枚举类 +7. `AuthQqRequest`增加refresh方法 +8. 优化代码 + +### 2019/07/18 + +1. 合并github上[@pengisgood](https://github.com/pengisgood) 的[pr#19](https://github.com/zhangyd-c/JustAuth/pull/19),集成人人 +2. 合并github上[@pengisgood](https://github.com/pengisgood) 的[pr#20](https://github.com/zhangyd-c/JustAuth/pull/20),集成Pinterest +3. 合并github上[@pengisgood](https://github.com/pengisgood) 的[pr#21](https://github.com/zhangyd-c/JustAuth/pull/21),集成StackOverflow +4. 合并github上[@xkcoding](https://github.com/xkcoding) 的[pr#23](https://github.com/zhangyd-c/JustAuth/pull/23),重构代码、新增编辑器规范,规范PR代码风格 + +### 2019/07/17 +1. 优化代码 +2. 集成Teambition登录 + +### 2019/07/16 +1. 重构UrlBuilder类 +2. 将CSDN相关的类置为`Deprecated`,后续可能会删除,也可能一直保留。毕竟CSDN的openAPI已经不对外开放了。 +3. `BaseAuthRequest` 改名为 `AuthDefaultRequest` +4. `ResponseStatus` 改名为 `AuthResponseStatus` 并且移动到 `me.zhyd.oauth.model` +5. 合并github上[@xkcoding](https://github.com/xkcoding) 的[pr#18](https://github.com/zhangyd-c/JustAuth/pull/18),修复小米回调错误问题 同时 支持微信获取 + ### 2019/07/15 1. 新增 `AuthState` 类,内置默认的state生成规则和校验规则