RuoYiPlus 使用 JustAuth 进行三方登录,但是 JustAuth 不支持 Welink 登录,本文将介绍如何扩展 JustAuth 以支持 Welink 登录,并适配 RuoYiPlus 项目。
JustAuth 文档
RuoYiPlus 后端文档
RuoYiPlus 前端文档
第三方网站 Welink 登录流程 免登录时,可以由前端生成 code 传递给后端,由后端去请求 Welink 接口返回用户信息给前端。也可以通过 OAuth 方式,由后端构造三方授权地址传递给前端跳转到指定地址,授权通过后再由 Welink 携带 code 返回到重定向地址,前端再将 code 传递给后端,由后端去请求 Welink 接口返回用户信息给前端。
前台免登录 我们先来了解一下 Welink 的免登录流程 :
前端生成 code 传递给后端 后端使用 client_id 和 client_secret 请求三方得到 access_token 后端使用 code 和 access_token 请求三方得到 userId 后端使用 userId 和 access_token 请求三方得到用户详细信息 PC 端生成 code 授权码有两种形式(移动端可参考 移动端获取授权登录码 | WeLink ):
通过 Welink 打开轻应用 通过扫码登录 先说通过 Welink 打开轻应用 :
需要在 Welink 后台配置轻应用的 PC 端首页地址:
假定 PC 端打开链接为:https://www.example.com/index
,需要在链接后面添加参数:code=$(code)
,那么配置地址则为:https://www.example.com/index?code=$(code)
从 URLSearchParams 获取 code 提交给后端向三方请求用户信息:
1 2 3 4 5 6 7 8 9 10 11 <script > const getWelinkLoginCode = async ( ) => { const urlParams = new URLSearchParams (window .location .search ); const code = urlParams.get ("code" ); if (code) { const response = await loginWithCode (code); } } </script >
再说扫码登录 :
前端引入 js 文件:
1 <script src ="https://login.welink.huaweicloud.com/sso-proxy-front/public/qrcode/0.0.1/wlQrcodeLogin.js" > </script >
前端构造二维码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <div id ="welink-login-container" > </div > <script > const getWelinkLoginERCode = ( ) => { const clientId = "2025xxxxxxxxxxx1413" ; const state = Date .now ().toString (); const redirectUri = 'https://www.example.com/social-callback?source=welink' ; window .wlQrcodeLogin ({ id : "welink-login-container" , client_id : clientId, response_type : "code" , scope : "snsapi_login" , state : state, redirect_uri : redirectUri, style : "border:none;background-color:#FFFFFF;" , width : "400" , height : "300" , lang : 'cn' , self_redirect : false }); } </script >
注意 :redirectUri 的域名或 IP 需要和 Welink 后台设置的 “应用管理后台” 地址保持一致,并且不能使用 localhost,否则可能导致二维码生成失败。
从 MessageEvent 获取 code 提交给后端向三方请求用户信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <script > const getWelinkLoginCode = (event ) => { const origin = event.origin ; if (origin === "https://login.welink.huaweicloud.com" ) { console .log (event) const code = event.data as string; console .log (code); if (code) { const response = await loginWithCode (code); } } } if (typeof window .addEventListener != 'undefined' ) { window .addEventListener ('message' , getWelinkLoginCode, false ); } else if (typeof window .attachEvent != 'undefined' ) { window .attachEvent ('onmessage' , getWelinkLoginCode); } </script >
管理后台免登录 上面说的都是由前台产生 code 传递给后台,然后后台拿 code 去请求三方获取信息。
下面将介绍 Welink 中 OAuth 登录方式 流程:
前端点击按钮后向后端发送请求 后端构造三方跳转地址并返回前端 前端跳转指定三方跳转地址 三方会判断用户是否登录三方,没有则跳转三方登录页,登录后三方会生成 code 并带参跳转到 redirect_uri 地址 可以在前端判断用户是否是登录状态,请求后端对应的接口处理 JustAuth 扩展流程 根据 自定义第三方平台的OAuth | JustAuth 可以得知扩展步骤主要分为三步:
在 application.yml 配置 justauth 中第三方平台的 client_id 和 client_secret 等信息 创建一个类实现 AuthSource 接口 创建一个类继承 AuthDefaultRequest 类,并重写 getAccessToken 和 getUserInfo 方法 JustAuth 官方文档中例子的源码可见:AuthMyGitlabRequest.java | Github
理解 RuoYiPlus 中三方登录流程 结论:RuoYiPlus 中需要先绑定三方账号,才能使用三方登录。
前端在 login.vue 登录页面或登录后 thirdParty.vue 绑定应用页面都会调用 auth/binding/{source}
接口。
后端将创建 AuthRequest 调用 authorize 方法构造三方跳转地址 (会用到 yml 中配置的信息)并传递给前端(AuthController 中 authBinding 方法)。
前端跳转到指定三方地址后由三方返回到指定的重定向地址 /social-callback
(此路由对应前端 SocialCallback.vue 页面)。
前端 SocialCallback.vue 中会判断是否是登录状态,如果已登录则请求 auth/social/callback
接口,未登录则请求 auth/login
接口(AuthController 中 socialCallback 和 login 方法 )。
AuthController 中 login 方法中最关键的是 LoginVo loginVo = IAuthStrategy.login(body, client, grantType)
,点进去看一下 IAuthStrategy.login 方法:
1 2 3 4 5 6 7 8 9 static LoginVo login (String body, SysClientVo client, String grantType) { String beanName = grantType + BASE_NAME; if (!SpringUtils.containsBean(beanName)) { throw new ServiceException ("授权类型不正确!" ); } IAuthStrategy instance = SpringUtils.getBean(beanName); return instance.login(body, client); }
这里 grantType 是 “social”,BASE_NAME 就是 “AuthStrategy”,所以实际调用的是 socialAuthStrategy.login 方法。再看一下 SocialAuthStrategy 中 login 方法:
1 2 3 4 5 6 7 8 9 10 11 12 public LoginVo login (String body, SysClientVo client) { SocialLoginBody loginBody = JsonUtils.parseObject(body, SocialLoginBody.class); ValidatorUtils.validate(loginBody); AuthResponse<AuthUser> response = SocialUtils.loginAuth( loginBody.getSource(), loginBody.getSocialCode(), loginBody.getSocialState(), socialProperties); if (!response.ok()) { throw new ServiceException (response.getMsg()); } AuthUser authUserData = response.getData(); }
可以看到就是通过 SocialUtils.loginAuth 方法来调用三方接口,拿到用户信息。再看一下 SocialUtils.loginAuth 方法:
1 2 3 4 5 6 7 public static AuthResponse<AuthUser> loginAuth (String source, String code, String state, SocialProperties socialProperties) throws AuthException { AuthRequest authRequest = getAuthRequest(source, socialProperties); AuthCallback callback = new AuthCallback (); callback.setCode(code); callback.setState(state); return authRequest.login(callback); }
可以看到是先调用 getAuthRequest 获取到对应平台的 AuthRequest 类,然后调用其中的 login 方法。我们再看一下 getAuthRequest 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static AuthRequest getAuthRequest (String source, SocialProperties socialProperties) throws AuthException { SocialLoginConfigProperties obj = socialProperties.getType().get(source); if (ObjectUtil.isNull(obj)) { throw new AuthException ("不支持的第三方登录类型" ); } AuthConfig.AuthConfigBuilder builder = AuthConfig.builder() .clientId(obj.getClientId()) .clientSecret(obj.getClientSecret()) .redirectUri(obj.getRedirectUri()) .scopes(obj.getScopes()); return switch (source.toLowerCase()) { case "dingtalk" -> new AuthDingTalkV2Request (builder.build(), STATE_CACHE); case "baidu" -> new AuthBaiduRequest (builder.build(), STATE_CACHE); case "welink" -> new AuthWelinkRequest (builder.build(), STATE_CACHE); default -> throw new AuthException ("未获取到有效的Auth配置" ); }; }
没错,socialProperties.getType() 返回的是 map 类型,就是用于读取 yml 配置文件中配置内容构造对应的 AuthRequest 类。
所以总结一下:AuthController 中 login 方法就是执行对应平台的 AuthRequest 类中的 login 方法。
本文中的 AuthWelinkRequest 之所以要重写基类 AuthDefaultRequest 中的 login 方法,是因为 Welink 获取用户信息的流程不太一样,需要先获取 userId 才能获取用户信息,再重复定义一遍 responseError 也是因为基类中的该方法是 private,子类无法调用。
回到 SocialAuthStrategy 中 login 方法后面代码部分,获取到三方平台的用户信息后会去查询 sys_social 表,检查是否有此账号绑定记录,没有则则抛出异常提示需要先进行绑定。如果有记录则查询对应的系统用户 Id,然后通过 Id 获取系统用户信息,使用 SaToken 生成 token 进行登录。
AuthController 中 socialCallback 方法则先对用户登录状态判断,只有登录状态才会调用 SocialUtils.loginAuth 方法,上面分析了这实际就是调用对应平台的 AuthRequest 类中的 login 方法获取三方用户信息。然后通过 loginService.socialRegister 方法去新增或修改绑定记录。
RuoYiPlus 适配 由于在扫码登录时,Welink 的认证需要向第三方传递前端传过来 code 参数,并且需要先获取 userId 才能获取用户信息,所以本文和官方的扩展流程有些许不同。
后端改造 application-dev.yml 增加 Welink 平台配置:
1 2 3 4 5 6 7 8 justauth: address: https://www.example.com:80 type: welink: client-id: 2025xxxxxxxxxxx1413 client-secret: xxxxxxxxxxxxx redirect-uri: ${justauth.address}/social-callback?source=welink
AuthWelinkSource.java 定义接口地址:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public enum AuthWelinkSource implements AuthSource { WELINK; public String snsAuthorize () { return "https://login.welink.huaweicloud.com/sso/oauth2/sns_authorize" ; } @Override public String authorize () { return "https://login.welink.huaweicloud.com/sso/oauth2/authorize" ; } @Override public String accessToken () { return "https://open.welink.huaweicloud.com/api/auth/v2/tickets" ; } @Override public String userInfo () { return "https://open.welink.huaweicloud.com/api/contact/v2/user/detail" ; } @Override public Class<? extends AuthDefaultRequest > getTargetClass() { return AuthWelinkRequest.class; } public String userId () { return "https://open.welink.huaweicloud.com/api/auth/v2/userid" ; } }
AuthWelinkRequest.java 发送请求:
相比较官方示例代码,注意改变了以下几点::
重写的 getUserInfo(AuthToken authToken) 方法啥也没写,因为用不到 增加了 getUserInfoById(AuthToken authToken, AuthCallback authCallback) 方法,增加一个参数获取 code,即 authCallback.code 增加了 getUserId 方法 重载了 authorize 方法,分别用于扫码登录和绑定账号两个不同场景 没有使用自带的 doGetXxxxxx 或 doPostXxxxxx 方法,而是使用封装的 OkHttp 库,封装代码将放在文章末尾。因为使用 doPostAuthorizationCode 请求 access_token 时返回内容一直提示:”clientId为空”,其实请求时是有传递 client_id 的,换了之后就成功了。我猜测是因为自带的 doPostXxxxxx 并没有把请求参数放在 body 里面而是以 ?client_id=xxx&client_secret=xxx
这种方式拼接在 url 后面导致的 重写了 login 方法 和 增加了 responseError 方法(这两个方法就是仿照基类 AuthDefaultRequest 中对应方法写的)。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 @Slf4j public class AuthWelinkRequest extends AuthDefaultRequest { public AuthWelinkRequest (AuthConfig config) { super (config, AuthWelinkSource.WELINK); } public AuthWelinkRequest (AuthConfig config, AuthStateCache authStateCache) { super (config, AuthWelinkSource.WELINK, authStateCache); } @Override public AuthUser getUserInfo (AuthToken authToken) { return null ; } @Override public AuthToken getAccessToken (AuthCallback authCallback) { Map<String, String> params = new HashMap <>(); params.put("client_id" , this .config.getClientId()); params.put("client_secret" , this .config.getClientSecret()); Map<String, String> headers = new HashMap <>(); headers.put("Content-Type" , "application/json;charset=UTF-8" ); headers.put("x-wlk-gray" , "0" ); JSONObject object = null ; try { String body = OkHttpUtil.post(UrlBuilder.fromBaseUrl(this .source.accessToken()).build(), JSON.toJSONString(params), MediaType.parse("application/json" ), headers); object = JSONObject.parseObject(body); log.info("welink get access token success, access token body:[{}]" , body); } catch (Exception e) { log.error("get welink access token fail" , e); } AuthToken.AuthTokenBuilder builder = AuthToken.builder(); if (object != null && this .checkResponse(object)){ builder.accessToken(object.getString("access_token" )) .expireIn(object.getInteger("expires_in" )); } return builder.build(); } public AuthUser getUserInfoById (AuthToken authToken, AuthCallback authCallback) { String userId = this .getUserId(authToken, authCallback.getCode()); AuthUser.AuthUserBuilder builder = AuthUser.builder(); if (userId == null ){ return builder.build(); } Map<String, String> params = new HashMap <>(); params.put("userId" , userId); Map<String, String> headers = new HashMap <>(); headers.put("x-wlk-Authorization" , authToken.getAccessToken()); headers.put("Content-Type" , "application/json" ); headers.put("Accept-Charset" , "UTF-8" ); JSONObject object = null ; try { String body = OkHttpUtil.post(UrlBuilder.fromBaseUrl(AuthWelinkSource.WELINK.userInfo()).build(), JSON.toJSONString(params), MediaType.parse("application/json" ), headers); object = JSONObject.parseObject(body); log.info("welink get user info success, user info body:[{}]" , body); } catch (Exception e) { log.error("get welink user info fail" , e); } if (object != null && this .checkResponse(object)){ AuthUserGender gender = switch (object.getString("sex" )) { case "F" -> AuthUserGender.FEMALE; case "M" -> AuthUserGender.MALE; default -> AuthUserGender.UNKNOWN; }; builder.uuid(object.getString("userId" )) .username(object.getString("employeeId" )) .nickname(object.getString("userNameCn" )) .avatar(object.getString("avatar" )) .email(object.getString("userEmail" )) .company(object.getString("mainDeptCode" )) .gender(gender) .token(authToken) .source(source.toString()) .rawUserInfo(object); } return builder.build(); } public String getUserId (AuthToken authToken, String code) { Map<String, String> params = new HashMap <>(); params.put("code" , code); Map<String, String> headers = new HashMap <>(); headers.put("x-wlk-Authorization" , authToken.getAccessToken()); JSONObject object = null ; try { String body = OkHttpUtil.get(UrlBuilder.fromBaseUrl(AuthWelinkSource.WELINK.userId()).build(), headers, params); object = JSONObject.parseObject(body); log.info("welink get user id success, user id body:[{}]" , body); } catch (Exception e) { log.error("get welink user id fail" , e); } if (object != null && this .checkResponse(object)){ return object.getString("userId" ); } return null ; } public String authorize (String code, String state) { return UrlBuilder.fromBaseUrl(AuthWelinkSource.WELINK.snsAuthorize()) .queryParam("response_type" , "code" ) .queryParam("client_id" , this .config.getClientId()) .queryParam("redirect_uri" , URLEncoder.encode(this .config.getRedirectUri(), StandardCharsets.UTF_8)) .queryParam("state" , this .getRealState(state)) .queryParam("scope" , "snsapi_login" ) .queryParam("code" , code) .build(); } @Override public String authorize (String state) { return UrlBuilder.fromBaseUrl(AuthWelinkSource.WELINK.authorize()) .queryParam("response_type" , "code" ) .queryParam("client_id" , this .config.getClientId()) .queryParam("redirect_uri" , URLEncoder.encode(this .config.getRedirectUri(), StandardCharsets.UTF_8)) .queryParam("state" , this .getRealState(state)) .queryParam("scope" , "backendlogin" ) .build(); } @Override public AuthResponse<AuthUser> login (AuthCallback authCallback) { try { if (!this .config.isIgnoreCheckState()) { AuthChecker.checkState(authCallback.getState(), this .source, this .authStateCache); } AuthToken authToken = this .getAccessToken(authCallback); AuthUser user = this .getUserInfoById(authToken, authCallback); return AuthResponse.<AuthUser>builder().code(AuthResponseStatus.SUCCESS.getCode()).data(user).build(); } catch (Exception var4) { Log.error("Failed to login with oauth authorization." , var4); return this .responseError(var4); } } private boolean checkResponse (JSONObject object) { if (object == null ) { log.error("response body is null, request fail!" ); return false ; }else if (!Objects.equals(object.getString("code" ), "0" )){ log.error("response body message: {}" , object.getString("message" )); return false ; } return true ; } AuthResponse<AuthUser> responseError (Exception e) { int errorCode = AuthResponseStatus.FAILURE.getCode(); String errorMsg = e.getMessage(); if (e instanceof AuthException authException) { errorCode = authException.getErrorCode(); if (StringUtils.isNotEmpty(authException.getErrorMsg())) { errorMsg = authException.getErrorMsg(); } } return AuthResponse.<AuthUser>builder().code(errorCode).msg(errorMsg).build(); } }
AuthController 类中 authBinding 方法新增 code 参数,并对 Welink 平台扫码登录时特殊处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @GetMapping("/binding/{source}") public R<String> authBinding (@PathVariable("source") String source, @RequestParam(required = false) String code, @RequestParam String tenantId, @RequestParam String domain) { SocialLoginConfigProperties obj = socialProperties.getType().get(source); if (ObjectUtil.isNull(obj)) { return R.fail(source + "平台账号暂不支持" ); } AuthRequest authRequest = SocialUtils.getAuthRequest(source, socialProperties); Map<String, String> map = new HashMap <>(); map.put("tenantId" , tenantId); map.put("domain" , domain); map.put("state" , AuthStateUtils.createState()); String authorizeUrl; if (authRequest instanceof AuthWelinkRequest welinkRequest && !StringUtils.isBlank(code)){ authorizeUrl = welinkRequest.authorize(code, Base64.encode(JsonUtils.toJsonString(map), StandardCharsets.UTF_8)); }else { authorizeUrl = authRequest.authorize(Base64.encode(JsonUtils.toJsonString(map), StandardCharsets.UTF_8)); } log.info("authorize url: " + authorizeUrl); return R.ok("操作成功" , authorizeUrl); }
SocialUtils 类中 getAuthRequest 方法中增加 case:
1 2 3 4 5 6 7 8 9 10 11 12 public static AuthRequest getAuthRequest (String source, SocialProperties socialProperties) throws AuthException { return switch (source.toLowerCase()) { case "welink" -> new AuthWelinkRequest (builder.build(), STATE_CACHE); default -> throw new AuthException ("未获取到有效的Auth配置" ); }; }
前端改造 前端涉及到登录页和绑定应用页,需要改造 login.vue 和 thirdParty.vue 页面。
auth.ts 中 authBinding 方法增加 code 参数:
1 2 3 4 5 6 7 8 9 10 11 12 export function authBinding (source: string , tenantId: string , code: string = "" ) { return request ({ url : '/auth/binding/' + source, method : 'get' , params : { code : code, tenantId : tenantId, domain : window .location .host } }); }
index.js 引入 js 文件
1 <script src ="https://login.welink.huaweicloud.com/sso-proxy-front/public/qrcode/0.0.1/wlQrcodeLogin.js" > </script >
assets/icons/svg 放入 welink.svg 图标
lang/zh_CN.ts 和 en_US.ts 增加 login.social.welink
语言内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 login : { social : { welink : 'Welink 登录' , } } login : { social : { welink : 'Welink Login' , } }
login.vue 中使用 Element-Plus 中的 Dialog 组件来展示二维码,使用 @opened
实现打开弹窗时调用函数生成二维码。
注意 :redirectUri 的域名或 IP 需要和 Welink 后台设置的 “应用管理后台” 地址保持一致,并且不能使用 localhost,否则可能导致二维码生成失败。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 <template> <!-- Welink 扫码登录弹窗 --> <el-dialog v-model="isWelinkLogin" :title="proxy.$t('login.social.welink')" @opened="getWelinkLoginERCode" width="500" center align-center> <div class="welink-login-dialog"> <div id="welink-login-container"></div> </div> </el-dialog> </template> <script setup lang="ts"> // 是否 welink 登录 const isWelinkLogin = ref(false); // 生成二维码 const getWelinkLoginERCode = () => { const clientId = "2025xxxxxxxxxxx1413"; const state = Date.now().toString(); const redirectUri = 'https://www.example.com/social-callback?source=welink'; window.wlQrcodeLogin({ id: "welink-login-container", // 这里需要你在自己的页面定义一个 HTML 标签并设置 id,例如 <div id="welink-login-container"></div>,也可以使用 span client_id: clientId, response_type: "code", scope: "snsapi_login", state: state, redirect_uri: redirectUri, style: "border:none;background-color:#FFFFFF;", width : "400", height: "300", lang: 'cn', self_redirect: false }); } const getWelinkLoginCode = (event: MessageEvent) => { const origin = event.origin; if(origin === "https://login.welink.huaweicloud.com") { console.log(event) const code = event.data as string; console.log(code); if(code) { // 发送请求将 code 传递给后端 doSocialLogin('welink', code) } } } onMounted(() => { window.addEventListener("message", getWelinkLoginCode, false); }); onBeforeUnmount(() => { window.removeEventListener("message", getWelinkLoginCode, false); }) </script> <style lang="scss" scoped> .welink-login-dialog { display: flex; justify-content: center; align-items: center; height: 100%; } </style>
thirtyParty.vue 中在可绑定应用中增加 Welink:
1 2 3 4 5 6 7 8 9 <div id="authlist" class="user-bind"> <a class="third-app" href="#" title="使用 Welink 账号授权登录" @click="authUrl('welink')"> <div class="git-other-login-icon"> <svg-icon icon-class="welink" /> </div> <span class="app-name">Welink</span> </a> <!-- 省略其它列表 --> </div>
附录 OkHttp 封装 Maven 引入:
1 2 3 4 5 <dependency > <groupId > com.squareup.okhttp3</groupId > <artifactId > okhttp</artifactId > <version > 4.12.0</version > </dependency >
OkHttpUtil 类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 public class OkHttpUtil { private static final OkHttpClient okHttpClient = new OkHttpClient (); public static String get (String url, Map<String, String> headers, Map<String, String> urlParams) throws IOException { String result = "" ; Request.Builder builder = new Request .Builder(); if (!StringUtils.hasText(url)) { return result; } if (headers != null ) { builder.headers(setHeaders(headers)); } if (urlParams != null ) { builder.url(setUrlParams(url, urlParams)); } else { builder.url(url); } Request request = builder.build(); try (Response response = okHttpClient.newCall(request).execute()){ if (response.body() != null ){ result = response.body().string(); } } return result; } public static String post (String url, String json, MediaType mediaType) throws IOException { RequestBody body = RequestBody.create(json, mediaType); Request request = new Request .Builder() .url(url) .post(body) .build(); try (Response response = okHttpClient.newCall(request).execute()){ if (response.body() != null ){ return response.body().string(); } } return "" ; } public static String post (String url, String json, MediaType mediaType, Map<String, String> headers) throws IOException { RequestBody body = RequestBody.create(json, mediaType); Request.Builder builder = new Request .Builder() .url(url) .post(body); if (headers != null ) { builder.headers(setHeaders(headers)); } Request request = builder.build(); try (Response response = okHttpClient.newCall(request).execute()){ if (response.body() != null ){ return response.body().string(); } } return "" ; } private static Headers setHeaders (Map<String, String> headersParams) { Headers headers = null ; okhttp3.Headers.Builder headersbuilder = new okhttp3 .Headers.Builder(); if (headersParams != null ) { Iterator<String> iterator = headersParams.keySet().iterator(); String key = "" ; while (iterator.hasNext()) { key = iterator.next(); headersbuilder.add(key, headersParams.get(key)); } } headers = headersbuilder.build(); return headers; } private static String setUrlParams (String url, Map<String, String> mapParams) { StringBuilder strParams = new StringBuilder (); if (mapParams != null ) { Iterator<String> iterator = mapParams.keySet().iterator(); for (Map.Entry<String, String> entry : mapParams.entrySet()) { if (!strParams.isEmpty()) { strParams.append("&" ); } strParams.append(entry.getKey()).append("=" ).append(entry.getValue()); } if (url.endsWith("?" )) { url += strParams; } else { url += "?" + strParams; } } return url; } }
如何读取 yml 配置 首先定义一个 SocialProperties 类,使用 @ConfigurationProperties 注解将 yml 下 justauth.* 的内容映射到这个类里面
1 2 3 4 5 6 7 8 9 10 11 @Data @Component @ConfigurationProperties(prefix = "justauth") public class SocialProperties { private Map<String, SocialLoginConfigProperties> type; }
然后定义一个配置类,使用 @EnableConfigurationProperties 注解来启用
1 2 3 4 5 6 7 8 9 10 @AutoConfiguration @EnableConfigurationProperties(SocialProperties.class) public class SocialAutoConfiguration { @Bean public AuthStateCache authStateCache () { return new AuthRedisStateCache (); } }