当前位置: 首页 > 工具软件 > Spring Social > 使用案例 >

Spring Social简明教程

莫骞仕
2023-12-01

参考文档:Spring Social Reference

本文源码:https://github.com/huangjinzhou/spring-social-wechat

一、协议流程

明确协议中的三个参与对象:

  • 用户:拥有或即将拥有第三方应用和授权提供方的账号
  • 第三方应用:使用授权提供方提供的登陆功能的服务
  • 授权提供方:提供授权的服务

准备工作:

第三方应用开发者在服务提供方注册应用,并获得:

client_id(也有叫appid或其他,如微信):第三方应用在服务提供方的id

client_secret(也有叫appSecret或其他,如微信):服务提供方给第三方应用提供的私钥,第三方应用应该把它放在安全的地方,不能对外暴露

一)、OAuth2

  1. 第三方应用将用户引导至授权提供方的授权页面,携带参数:redirect_uri(重定向地址,一般要与在服务提供方注册应用时填写的重定向地址一致),client_id,scope(可选,授权作用域),state(可选,自定义值,用来防止CSRF)
  2. 用户确认授权,服务提供方将用户重定向到第三方应用注册的redirect_uri,并携带参数:code(稍后用于换access_token),state(第一步生成的参数)
  3. 第三方应用后台使用code向服务提供方换取access_token、refresh_token等
  4. 第三方应用使用access_token向授权服务提供方获取用户相关信息或其他操作

二)、OAuth1

  1. 第三方应用后台向服务提供方获取oauth_token(request_token),携带参数:oauth_callback(回调地址,用来接收request_token),client_id,client_secret
  2. 第三方应用将用户重定向到授权提供方授权页面,携带参数:oauth_token,回调地址
  3. 用户授权后,服务提供方将用户重定向到第三方应用,可能携带一个参数 verifier code(oauth1.0a),这个时候request_token就已经被授权了
  4. 第三方应用使用已经授权的request_token向授权服务提供方换取access_token
  5. 第三方应用使用access_token向授权服务提供方获取用户相关信息或其他操作

二、Spring Social核心类:

以下的泛型参数A都只授权服务提供方提供的接口

1、Connection<A>

这是整个oauth认证过程的最终结果,得到Connection后,就可以对授权提供方获取被授权获取的资源。Connection是用户在第三方应用和授权服务提供方账号的绑定抽象,具体看org.springframework.social.connect.Connection的java doc

2、ConnectionFactory<A>

Connection<A>的工厂类,也是封装之后外部直接打交道的入口类,见org.springframework.social.connect.ConnectionFactory 的java doc。它一般通过ConnectionFactoryLocator获得(因为你可能不止使用一个授权服务提供方,比如同时支持微信、微博、qq等)

3、ServiceProvider<A>

服务提供者的标记接口,根据使用的协议有子接口OAuth1ServiceProvider<A>,OAuth2ServiceProvider<A>,它们都有getOAuthOperations和getApi方法(但是参数或返回不同),分别提供认证流程抽象和API

以上就是我们实现自己的ServiceProvider需要实现的接口或者继承的抽象类,但Spring Social为我们提供了Connection的实现(根据使用的协议不同,有OAuth2Connection<A>和OAuth1Connection<A>),所以我们不需要自己实现Connection了。

三、实现微信ServiceProvider(以微信为授权服务提供方)

关于包名以及包结构的约定:

当提供自定义ServiceProvider是实现时,Spring Social推荐的根包名为:org.springframework.social.{providerId},其中{providerId}是服务提供方的标识,比如wechat、weibo、facebook等等。然后下面提供两个子包api和connect:其中api用来定义接口,在api包下面提供包impl放接口的实现;connect用来实现连接相关的接口、抽象类

1、抽象微信提供的接口

定义接口时,一般是提供一个总接口,总接口用于提供各类子接口,比如为微信提供一个总结口WeChat,然后在通过WeChat根据Rest资源的分类获取不同的接口。比如,用户信息获取和更新等操作的接口定义为UserOperations,发送和接收消息等消息处理的接口定义为MessageOperations(微信目前没有提供,只是假设),然后使用WeChat接口获取这些子接口。以微信为例,展示结构:

总结口WeChat:

package org.springframework.social.wechat.api;

import org.springframework.social.ApiBinding;

/**
 * Interface specifying a basic set of operations for interacting with WeChat.
 *
 * @author huangjinzhou
 */
public interface WeChat extends ApiBinding {

    /**
     * Returns the portion of the WeChat API containing the user operations.
     *
     * @return user operations
     */
    UserOperations userOperations();
}

子接口UserOperations:

package org.springframework.social.wechat.api;

/**
 * Interface defining the operations for working with WeChat users.
 *
 * @author huangjinzhou
 */
public interface UserOperations {

    /**
     * Retrieves the user's WeChat profile details.
     *
     * @param openId 普通用户的标识
     * @return the user's WeChat profile
     */
    WeChatUserProfile getUserProfile(String openId);
}

2、实现微信接口

具体看github代码啦,实现的WeChat的时候继承AbstractOAuth2ApiBinding,如果你实现的服务提供方使用的是OAuth1协议,则继承AbstractOAuth1ApiBinding,里面替我们做了很多烦人的事情

3、实现ApiAdapter<A>

这个是用来适配Connection的API适配器,及在Connection实现中调用适配器来获得用户信息、测试连接是否有效等

package org.springframework.social.wechat.connect;

import org.springframework.social.connect.ApiAdapter;
import org.springframework.social.connect.ConnectionValues;
import org.springframework.social.connect.UserProfile;
import org.springframework.social.connect.UserProfileBuilder;
import org.springframework.social.wechat.api.WeChat;
import org.springframework.social.wechat.api.WeChatUserProfile;

/**
 * WeChat ApiAdapter implementation
 *
 * @author huangjinzhou
 * @date 2019-05-30 10:13
 */
public class WeChatApiAdapter implements ApiAdapter<WeChat> {

    private final String openId;

    public WeChatApiAdapter(String openId) {
        this.openId = openId;
    }

    @Override
    public boolean test(WeChat api) {
        try {
            api.userOperations().getUserProfile(openId);
            return true;
        } catch (Exception ignored) {
        }

        return false;
    }

    @Override
    public void setConnectionValues(WeChat api, ConnectionValues values) {
        WeChatUserProfile userProfile = api.userOperations().getUserProfile(openId);
        if (userProfile == null) {
            return;
        }

        values.setDisplayName(userProfile.getNickname());
        values.setProviderUserId(userProfile.getOpenId());
        values.setImageUrl(userProfile.getHeadImageUrl());
        values.setProfileUrl(userProfile.getHeadImageUrl());
    }

    @Override
    public UserProfile fetchUserProfile(WeChat api) {
        WeChatUserProfile userProfile = api.userOperations().getUserProfile(openId);

        return new UserProfileBuilder()
                .setName(userProfile.getNickname())
                .setId(userProfile.getOpenId())
                .setUsername(userProfile.getNickname())
                .setFirstName(userProfile.getNickname())
                .build();
    }

    @Override
    public void updateStatus(WeChat api, String message) {
    }
}

4、实现ServiceProvider<A>

package org.springframework.social.wechat.connect;

import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider;
import org.springframework.social.oauth2.OAuth2ServiceProvider;
import org.springframework.social.wechat.api.WeChat;
import org.springframework.social.wechat.api.impl.WeChatTemplate;

/**
 * @author huangjinzhou
 * @date 2019/5/29 21:04
 */
public class WeChatServiceProvider extends AbstractOAuth2ServiceProvider<WeChat> {

    /**
     * Create a new {@link OAuth2ServiceProvider}.
     *
     * @param appId     appId
     * @param appSecret appSecret
     */
    public WeChatServiceProvider(String appId, String appSecret) {
        super(new WeChatOAuth2Template(appId, appSecret, "https://open.weixin.qq.com/connect/qrconnect",
                "https://api.weixin.qq.com/sns/oauth2/access_token",
                "https://api.weixin.qq.com/sns/oauth2/refresh_token"));
    }

    @Override
    public WeChat getApi(String accessToken) {
        return new WeChatTemplate(accessToken);
    }
}

5、实现ConnectionFactory<A>

package org.springframework.social.wechat.connect;

import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionData;
import org.springframework.social.connect.support.OAuth2Connection;
import org.springframework.social.connect.support.OAuth2ConnectionFactory;
import org.springframework.social.oauth2.AccessGrant;
import org.springframework.social.oauth2.OAuth2Operations;
import org.springframework.social.oauth2.OAuth2ServiceProvider;
import org.springframework.social.wechat.api.WeChat;

/**
 * WeChat ConnectionFactory Implementation
 *
 * @author huangjinzhou
 * @date 2019-05-30 10:48
 */
public class WeChatConnectionFactory extends OAuth2ConnectionFactory<WeChat> {

    /**
     * Create a {@link OAuth2ConnectionFactory}.
     *
     * @param appId     the application id in WeChat of the application.
     * @param appSecret the application secret in WeChat of the application.
     */
    public WeChatConnectionFactory(String appId, String appSecret) {
        super("wechat", new WeChatServiceProvider(appId, appSecret), null);
    }

    /**
     * Create a OAuth2-based {@link Connection} from the {@link AccessGrant} returned after {@link #getOAuthOperations() completing the OAuth2 flow}.
     *
     * @param accessGrant the access grant
     * @return the new service provider connection
     * @see OAuth2Operations#exchangeForAccess(String, String, org.springframework.util.MultiValueMap)
     */
    @Override
    public Connection<WeChat> createConnection(AccessGrant accessGrant) {
        return new OAuth2Connection<>(getProviderId(),
                extractProviderUserId(accessGrant),
                accessGrant.getAccessToken(),
                accessGrant.getRefreshToken(),
                accessGrant.getExpireTime(),
                (OAuth2ServiceProvider<WeChat>) getServiceProvider(),
                new WeChatApiAdapter(((WeChatAccessGrant) accessGrant).getOpenId()));
    }

    /**
     * Create a OAuth2-based {@link Connection} from the connection data.
     *
     * @param data connection data from which to create the connection
     */
    @Override
    public Connection<WeChat> createConnection(ConnectionData data) {
        return new OAuth2Connection<>(data, (OAuth2ServiceProvider<WeChat>) getServiceProvider(),
                new WeChatApiAdapter(data.getProviderUserId()));
    }

    @Override
    protected String extractProviderUserId(AccessGrant accessGrant) {
        return ((WeChatAccessGrant) accessGrant).getOpenId();
    }
}

到此,自定ServiceProvider就实现了

四、微信的坑

1、它的API调用是需要openid作参数的,所以WeChatApiAdapter还需要一个openid做构造函数入参,而其他一些标准平台是不需要的,比如facebook、linkin等。因而WeChatConnectionFactory就需要复写一些涉及ApiAdapter的方法,比如createConnection

2、认证流程发送的参数不是标准的client_id和client_secret而是appid和secret,所以需要提供自己的OAuthOperations实现(其实只要继承OAuth2Template,然后复写关于client_id和client_secret构造部分就好了),github里的WeChatTemplate是抄别人的

我挖的坑:github里的代码没有测试过!

啊! 烂尾了,虽然整篇都烂,算自己的笔记吧

 

 类似资料: