在前几篇文章中,我们介绍了OAuth2.0认证和授权机制讲解,并实现了基于Spring的Github第三方登录--通用化的第三方登陆实现,之后,我们基于该通用化的框架,介绍了国内的两个比较流行的第三方登录平台:基于Spring的新浪微博第三方登录实现基于Spring的QQ第三方登录实现。以上几个第三方登录平台都严格遵守了OAuth2.0协议。但是,近来作者发现微信的第三方登录确不是那么严格的遵守通用化的OAuth2.0协议,有些细节实现不太一样(例如获取AccessToken时其他OAuth平台用的字段名为client_id,但是微信中是appid),这就导致了之前的通用化框架在微信登录时遇到了很多问题,今天我们就来介绍一下微信的第三方登录,并重构一下我们的通用化框架,使之在遇到其他不那么严格遵守OAuth协议的平台依然简单实用。

申请第三方应用

接下来我们首先来看看如何在微信开放平台中申请一个第三方应用。

首先,我们需要完善开发者资质认证,在导航栏选择【账号中心】,然后选择【开发者资质认证】,我们会看到如下页面:

alter-text

从页面信息介绍中的第二点我们可以知道,通过开发者资质认证后,我们就能够获得微信第三方登录的能力。需要注意:个人是不能申请第三方认证的,必须为以下机构或者团体才能进行认证,每次审核需要花费300元:

alter-text

开发者资质认证提交并缴费后,会在两个工作日内进行审核,如果有问题会有客服人员电话沟通。

审核完成后,进入【管理中心】-> 【网站应用】。

alter-text

点击【创建网站应用】,按照其提示填写相关内容即可:

alter-text

alter-text

提交完成后,如果你的开发者资质已经通过,就可以直接进行开发上线啦。现在,让我们看看如何添加微信第三方登录的代码。

第三方登录通用架构

现在,我们将基于Spring的Github第三方登录--通用化的第三方登陆实现中的通用化架构添加到代码中来,以此为基础添加微信登录的相关功能:

alter-text

微信第三方登录服务分析

首先,让我们我们看看微信登录开发文档,看过之后,我们发现微信登录虽然是基于OAuth2.0协议,但是API的参数确不一样。之前我们做过github、微博、QQ其获取Access Token时所需的参数为client_id以及client_secret,Scribe默认的OAuthService在处理也是同样的参数,因此我们可以通过其默认的OAuth20ServiceImpl来处理OAuth的相关操作。

但是微信中client_idclient_secret两个参数统一用的是:appidsecret。这也就意味着我们需要实现自己的WeixinOAuth20Service来对这些细节进行处理。同时,添加完成后,我们还需要添加一个WeixinOAuthDeractorService来适配通用化的第三方登陆实现中的设计。

但是WeixinOAuth20Service以及WeixinOAuthDeractorService实际上功能是一样的,只是我们需要一个CustomOAuthService来对OAuthService进行管理。如果我们通过一个接口对OAuthService进行管理的话,我们就只需要添加一个类WeixinOAuthService即可,同时这也符合依赖于接口而不是实现的最佳原则。

通过接口管理OAuthService

我们首先来添加一个接口CustomOAuthService:

public interface CustomOAuthService extends OAuthService{
    
    String getoAuthType();
    String getAuthorizationUrl();
    OAuthUser getOAuthUser(Token accessToken);

}

之后,我们将之前所有对OAuthServiceDeractor修改为CustomOAuthService

@Service
public class OAuthServices {
    
    @Autowired List<CustomOAuthService> oAuthServiceDeractors;
    
    public CustomOAuthService getOAuthService(String type){
        Optional<CustomOAuthService> oAuthService = oAuthServiceDeractors.stream().filter(o -> o.getoAuthType().equals(type))
                .findFirst();
        if(oAuthService.isPresent()){
            return oAuthService.get();
        }
        return null;
    }
    
    public List<CustomOAuthService> getAllOAuthServices(){
        return oAuthServiceDeractors;
    }

}

这样,我们对于第三方框架的重构就完成了。接下来我们来具体实现微信的第三方登录。

WeixinApi

首先,添加WeixinApi,为OAuthService提供进行OAuth验证的各个地址,相关的地址可以在微信登录开发文档中得到,想了解微信第三方登录具体细节的同学可以仔细研读一下该文档。最终的WeixinApi代码如下:

public class WeixinApi extends DefaultApi20 {
    
    private static final String AUTHORIZE_URL = "https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&state=esfadsgsad34fwdef&scope=snsapi_login#wechat_redirect";
    private static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?grant_type=authorization_code";

    @Override
    public String getAuthorizationUrl(OAuthConfig config) {
        return String.format(AUTHORIZE_URL, config.getApiKey(), OAuthEncoder.encode(config.getCallback()));
    }

    @Override
    public String getAccessTokenEndpoint() {
        return ACCESS_TOKEN_URL;
    }
    
    @Override
    public OAuthService createService(OAuthConfig config){
      return new WeixinOAuthService(this, config);
    }
}

WeixinOAuthService

最后是WeixinOAuthService的实现,除了CustomOAuthService的所定位的方法外,我们还需要重写getAccessToken

public class WeixinOAuthService extends OAuth20ServiceImpl implements CustomOAuthService {
    
    private final DefaultApi20 api;
    private final OAuthConfig config;
    private final String authorizationUrl;
    
    public WeixinOAuthService(DefaultApi20 api, OAuthConfig config) {
        super(api, config);
        this.api = api;
        this.config = config;
        this.authorizationUrl = getAuthorizationUrl(null);
    }

    @Override
    public Token getAccessToken(Token requestToken, Verifier verifier){
      OAuthRequest request = new OAuthRequest(api.getAccessTokenVerb(), api.getAccessTokenEndpoint());
      request.addQuerystringParameter("appid", config.getApiKey());
      request.addQuerystringParameter("secret", config.getApiSecret());
      request.addQuerystringParameter(OAuthConstants.CODE, verifier.getValue());
      if(config.hasScope()) request.addQuerystringParameter(OAuthConstants.SCOPE, config.getScope());
      Response response = request.send();
      String responceBody = response.getBody();
      Object result = JSON.parse(responceBody);
      return new Token(JSONPath.eval(result, "$.access_token").toString(), "", responceBody);
    }

    @Override
    public OAuthUser getOAuthUser(Token accessToken) {
        OAuthUser oAuthUser = new OAuthUser();
        oAuthUser.setoAuthType(getoAuthType());
        Object result = JSON.parse(accessToken.getRawResponse());
        oAuthUser.setoAuthId(JSONPath.eval(result, "$.openid").toString());
        oAuthUser.setUser(new User());
        return oAuthUser;
    }

    @Override
    public String getoAuthType() {
        return OAuthTypes.WEIXIN;
    }

    @Override
    public String getAuthorizationUrl() {
        return authorizationUrl;
    }

}

配置WeixinOAuthService

最后添加WeixinOAuthService的相关配置:

@Configuration
public class OAuthConfig {
    
    private static final String CALLBACK_URL = "http://tianmaying.com/oauth/%s/callback";
    
    @Value("${oAuth.weixin.appId}") String weixinAppId;
    @Value("${oAuth.weixin.appSecret}") String weixinAppSecret;
    
    @Bean
    public CustomOAuthService getSinaOAuthService(){
        return (CustomOAuthService) new ServiceBuilder()
            .provider(WeixinApi.class)
            .apiKey(weixinAppId)
            .apiSecret(weixinAppSecret)
            .scope("snsapi_login")
            .callback(String.format(CALLBACK_URL, OAuthTypes.WEIXIN))
            .build();
    }
    

}

修改hosts

与新浪微博一样,修改hosts一遍进行本地调试。

windows系统hosts文件一般在C:\WINDOWS\system32\drivers\etc

mac系统hosts文件地址一般为:/etc/hosts

在hosts文件添加以下一行:

127.0.0.1       tianmaying.com

调试

进入根目录,运行sudo mvn spring-boot:run命令,访问http://tianmaying.com (之前填写应用信息以及修改hosts时所填写的域名,这三个域名必须一致),由于必须通过域名进行访问,所以我们需要监听80端口,运行时需要超级管理员权限。


进一步阅读

登录发表评论 注册

qq904837992

OAuthRequst 这个类 源码里边怎么没有啊?

mozun123

@Cliff  在学习中,谢谢了

Cliff

@mozun123



Optional<CustomOAuthService> oAuthService = oAuthServiceDeractors.stream().filter(o -> o.getoAuthType().equals(type)) .findFirst(); if(oAuthService.isPresent()){ return oAuthService.get(); } return null;

只有这一段是用java8的语法写的,目的是从oAuthServiceDeractors中找到一个oAuthService.getoAuthType()与传入的type找匹配的oAuthService。可以用下面的语句代替:

for(CustomOAuthService oAuthService : oAuthServiceDeractors) {
    if(oAuthService.getoAuthType().equals(type)) {
        return oAuthService;
    }
}
return null;

这个逻辑是很基础的,感觉你的java基础确实有一些薄弱啊,可以看看我们的课程,有收费课程也有免费课程,都很不错哦 

mozun123

如何使用jdk7改写OAuthServices这个类?菜鸟一个,求解答,谢谢

littlemesie

能给一下源码吗?谢谢

nanhaidetianzhi

很实用,(*^__^*) 嘻嘻……,感谢分享。blush

反馈意见