最近业务需要,对接了微信,QQ,的第三方登录,下面以微信为例,总结下第三方登录的流程与使用到的技术
一.用的核心的技术和规范:
- SpringBoot 2.2.6.RELEASE
- SpringCloud Nacos(由于整个项目是微服务项目,所以有用到其中的很多组件)
- 第三方依赖 JustAuth 1.15.9 (这个轮子很好用,整合了市面上大多数的第三方认证授权)链接: https://github.com/justauth/JustAuth?utm_source=gold_browser_extension
- Oauth2.0认证标准规范
Oauth2.0认证流程(以微信 网站应用 为例),简单介绍一下Oauth2.0流程
- 文档地址: https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html
- 微信Oauth2.0认证时序图:
从时序图中可以看到第4步用户扫码确认后,第5步是微信开放平台回调到网站应用,回调域名配置在微信开放平台 授权回调域,表示用户扫码后只能回调到当前配置的域名下面
二.后端代码
1.获取微信二维码链接的接口:
package com.leigod.center.user.nnpc.config; import lombok.Data; import me.zhyd.oauth.config.AuthConfig; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * 第三方平台AppId ,AppSecret配置类 * @author Sam.yang * @since 2021/1/29 17:38 */ @Data @Component @ConfigurationProperties(prefix = "oauth2") public class OAuthProperties { /** * QQ 配置 */ private AuthConfig qq; /** * github 配置 */ private AuthConfig github; /** * 微信 配置 */ private AuthConfig wechatOpen; /** * Google 配置 */ private AuthConfig google; /** * Microsoft 配置 */ private AuthConfig microsoft; /** * Mi 配置 */ private AuthConfig mi; }
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package me.zhyd.oauth.config; import com.xkcoding.http.config.HttpConfig; import java.util.List; /** * 配置类JustAuth 自带的 */ public class AuthConfig { private String clientId; private String clientSecret; private String redirectUri; private String alipayPublicKey; private boolean unionId; private String stackOverflowKey; private String agentId; private String codingGroupName; private HttpConfig httpConfig; private boolean ignoreCheckState; private List<String> scopes; private String deviceId; private Integer clientOsType; private String packId; public static AuthConfig.AuthConfigBuilder builder() { return new AuthConfig.AuthConfigBuilder(); } public String getClientId() { return this.clientId; } public String getClientSecret() { return this.clientSecret; } public String getRedirectUri() { return this.redirectUri; } public String getAlipayPublicKey() { return this.alipayPublicKey; } public boolean isUnionId() { return this.unionId; } public String getStackOverflowKey() { return this.stackOverflowKey; } public String getAgentId() { return this.agentId; } public String getCodingGroupName() { return this.codingGroupName; } public HttpConfig getHttpConfig() { return this.httpConfig; } public boolean isIgnoreCheckState() { return this.ignoreCheckState; } public List<String> getScopes() { return this.scopes; } public String getDeviceId() { return this.deviceId; } public Integer getClientOsType() { return this.clientOsType; } public String getPackId() { return this.packId; } public void setClientId(String clientId) { this.clientId = clientId; } public void setClientSecret(String clientSecret) { this.clientSecret = clientSecret; } public void setRedirectUri(String redirectUri) { this.redirectUri = redirectUri; } public void setAlipayPublicKey(String alipayPublicKey) { this.alipayPublicKey = alipayPublicKey; } public void setUnionId(boolean unionId) { this.unionId = unionId; } public void setStackOverflowKey(String stackOverflowKey) { this.stackOverflowKey = stackOverflowKey; } public void setAgentId(String agentId) { this.agentId = agentId; } public void setCodingGroupName(String codingGroupName) { this.codingGroupName = codingGroupName; } public void setHttpConfig(HttpConfig httpConfig) { this.httpConfig = httpConfig; } public void setIgnoreCheckState(boolean ignoreCheckState) { this.ignoreCheckState = ignoreCheckState; } public void setScopes(List<String> scopes) { this.scopes = scopes; } public void setDeviceId(String deviceId) { this.deviceId = deviceId; } public void setClientOsType(Integer clientOsType) { this.clientOsType = clientOsType; } public void setPackId(String packId) { this.packId = packId; } public AuthConfig() { } public AuthConfig(String clientId, String clientSecret, String redirectUri, String alipayPublicKey, boolean unionId, String stackOverflowKey, String agentId, String codingGroupName, HttpConfig httpConfig, boolean ignoreCheckState, List<String> scopes, String deviceId, Integer clientOsType, String packId) { this.clientId = clientId; this.clientSecret = clientSecret; this.redirectUri = redirectUri; this.alipayPublicKey = alipayPublicKey; this.unionId = unionId; this.stackOverflowKey = stackOverflowKey; this.agentId = agentId; this.codingGroupName = codingGroupName; this.httpConfig = httpConfig; this.ignoreCheckState = ignoreCheckState; this.scopes = scopes; this.deviceId = deviceId; this.clientOsType = clientOsType; this.packId = packId; } public static class AuthConfigBuilder { private String clientId; private String clientSecret; private String redirectUri; private String alipayPublicKey; private boolean unionId; private String stackOverflowKey; private String agentId; private String codingGroupName; private HttpConfig httpConfig; private boolean ignoreCheckState; private List<String> scopes; private String deviceId; private Integer clientOsType; private String packId; AuthConfigBuilder() { } public AuthConfig.AuthConfigBuilder clientId(String clientId) { this.clientId = clientId; return this; } public AuthConfig.AuthConfigBuilder clientSecret(String clientSecret) { this.clientSecret = clientSecret; return this; } public AuthConfig.AuthConfigBuilder redirectUri(String redirectUri) { this.redirectUri = redirectUri; return this; } public AuthConfig.AuthConfigBuilder alipayPublicKey(String alipayPublicKey) { this.alipayPublicKey = alipayPublicKey; return this; } public AuthConfig.AuthConfigBuilder unionId(boolean unionId) { this.unionId = unionId; return this; } public AuthConfig.AuthConfigBuilder stackOverflowKey(String stackOverflowKey) { this.stackOverflowKey = stackOverflowKey; return this; } public AuthConfig.AuthConfigBuilder agentId(String agentId) { this.agentId = agentId; return this; } public AuthConfig.AuthConfigBuilder codingGroupName(String codingGroupName) { this.codingGroupName = codingGroupName; return this; } public AuthConfig.AuthConfigBuilder httpConfig(HttpConfig httpConfig) { this.httpConfig = httpConfig; return this; } public AuthConfig.AuthConfigBuilder ignoreCheckState(boolean ignoreCheckState) { this.ignoreCheckState = ignoreCheckState; return this; } public AuthConfig.AuthConfigBuilder scopes(List<String> scopes) { this.scopes = scopes; return this; } public AuthConfig.AuthConfigBuilder deviceId(String deviceId) { this.deviceId = deviceId; return this; } public AuthConfig.AuthConfigBuilder clientOsType(Integer clientOsType) { this.clientOsType = clientOsType; return this; } public AuthConfig.AuthConfigBuilder packId(String packId) { this.packId = packId; return this; } public AuthConfig build() { return new AuthConfig(this.clientId, this.clientSecret, this.redirectUri, this.alipayPublicKey, this.unionId, this.stackOverflowKey, this.agentId, this.codingGroupName, this.httpConfig, this.ignoreCheckState, this.scopes, this.deviceId, this.clientOsType, this.packId); } public String toString() { return "AuthConfig.AuthConfigBuilder(clientId=" + this.clientId + ", clientSecret=" + this.clientSecret + ", redirectUri=" + this.redirectUri + ", alipayPublicKey=" + this.alipayPublicKey + ", unionId=" + this.unionId + ", stackOverflowKey=" + this.stackOverflowKey + ", agentId=" + this.agentId + ", codingGroupName=" + this.codingGroupName + ", httpConfig=" + this.httpConfig + ", ignoreCheckState=" + this.ignoreCheckState + ", scopes=" + this.scopes + ", deviceId=" + this.deviceId + ", clientOsType=" + this.clientOsType + ", packId=" + this.packId + ")"; } } }
/** * 网页应用第三方登录 * * @author Sam.yang * @since 2021/1/29 16:28 */ @Slf4j @RestController @RequestMapping(value = "/api/oauth") public class ApiAuthOpenController { @Autowired private OAuthProperties properties; /** * 重定向到微信 * * @param oauthType 认证类型 * @throws IOException */ @PostMapping("/render/{oauthType}") @ApiOperation(value = "第三方账号登录") public BaseOutput<String> renderAuth(@PathVariable("oauthType") String oauthType) throws IOException { AuthRequest authRequest = this.getAuthRequest(oauthType); String authorizeUrl = authRequest.authorize(AuthStateUtils.createState()); log.info("第三方登录 url:[{}]", authorizeUrl); return BaseOutput.success(authorizeUrl); } /** * 构建请求对象 * * @param oauthType 第三方登录类型 * @return {@link AuthRequest} */ private AuthRequest getAuthRequest(String oauthType) { AuthDefaultSource authSource = AuthDefaultSource.valueOf(oauthType.toUpperCase()); switch (authSource) { case QQ: return this.getQqAuthRequest(); case WECHAT_OPEN: return this.getWechatAuthRequest(); default: throw new RuntimeException("暂不支持的第三方登录"); } } /** * 构建QQ 认证请求对象 * * @return {@link AuthRequest} */ private AuthRequest getQqAuthRequest() { AuthConfig authConfig = properties.getQq(); return new AuthQqRequest(authConfig); } /** * 构建微信 认证请求对象 * * @return {@link AuthRequest} */ private AuthRequest getWechatAuthRequest() { AuthConfig authConfig = properties.getWechatOpen(); return new AuthWeChatOpenRequest(authConfig); } }
/** * 获取第三方UnionID * * @param userOpen {@link UserOpen} * @return UnionID */ private UserOpen getUnionID(UserOpen userOpen) { AuthCallback callback = AuthCallback.builder().code(userOpen.getCode()).state(userOpen.getState()) .build(); AuthRequest authRequest = this.getAuthRequest(AuthEnum.valueOf(userOpen.getOpenType().toUpperCase()) .getDesc().toUpperCase()); log.info("第三方登录请求参数:[{}]", JSON.toJSONString(authRequest)); AuthResponse response = authRequest.login(callback); if (!response.ok()) { log.info("获取用户信息失败 response:[{}]", JSON.toJSONString(response)); throw new BaseException(RetCode.ERROR); } log.info("第三方登录结果:[[}]", JSON.toJSONString(response)); AuthUser user = (AuthUser) response.getData(); AuthToken token = user.getToken(); userOpen.setUnionId(token.getUnionId()); return userOpen; }