SpringSocial 开发 QQ 登录

SpringSocial整合QQ授权登录 QQ授权登录 QQ扫码授权登录,关键就是要走一个 阅读详情

本篇文章带着大家在自己的系统中集成 QQ 第三方登录

不管是做什么操作,官方文档与手册绝对是最靠谱的。所以我们先来看一下 QQ 互联官网的文档


QQ 互联介绍

QQ 互联官网

在 QQ 互联官网上,我们发现 QQ 互联接入分为网站接入、移动应用接入、移动游戏接入,本篇文章主要针对网站接入。通过对官网的文档的阅读,我们可以总结出以下几点:

  1. 网站应用接入 QQ 登录是基于 OAuth2 协议的
  2. 开发流程如下图,通过对其中第三步获取 Access Token 的阅读,可以确定采用的是 OAuth2 协议的时授权码模式。

oauth2.0_guid_1.png

具体步骤说明

  • 放置 QQ 登录按钮: 引导用户到 QQ 认证服务器进行认证,认证通过后会跳转到预设的回调地址上,并在 url 上携带授权码。实质上是当用户点击按钮时,引导用户跳转到 QQ 互联的认证页面。跳转认证页面时携带的参数:

    • response_type ( 必填 ) : 授权类型,固定为 code。
    • client_id ( 必填 ) : 应用在 QQ 互联上申请的 appid。
    • redirect_uri ( 必填 ) : 授权成功之后的回调地址,在 QQ 互联上要设置。
    • state ( 必填 ) : 防止 csrf 攻击,客户端的状态值,授权成功后会原值带回。Client 用来校验用户与 state 的对应关系。
    • scope ( 选填 ) : 用户授权时向用户显示可进行的授权列表 ( 对应的是 API 文档中列出的接口 ),用户可以在其中进行选择。不填默认对 get_user_info 进行授权。填写多个接口时,用逗号隔开。
    • display ( 选填 ) : PC 网站接入时使用。用户选择网站的展示样式,不传为默认样式。可选 mobile,对应为 mobile 端下的样式。
  • 获取 Access_Token : 携带上一步获取到的授权码,向 QQ 认证服务器申请 Access_Token 。如果授权码校验通过,会返回 access_token、refresh_token、expires_in。请求 Access Token 接口,参数说明:

    • grant_type ( 必填 ) : 授权类型,在本步骤中,此值为 authorization_code (授权码)。
    • client_id ( 必填 ) : 应用在 QQ 互联上申请的 appid。
    • client_secret ( 必填 ) : 应用在 QQ 互联上申请的 appkey。
    • code ( 必填 ) : 上一步获取到的授权码,从回调 url 的参数中获取。
    • redirect_uri ( 必填 ) : 与上一步中传入的 redirect_uri 保持一致。

    补充说明,Access_Token 的有效期默认是3个月,过期后需要用户重新授权才能获得新的 Access_Token。访问 Access_Token 刷新接口 可以进行刷新,程序中可以采用定时任务来进行处理,参数说明:

    • grant_type ( 必填 ) : 授权类型,在本步骤中,此值为 authorization_code (授权码)。
    • client_id ( 必填 ) : 应用在 QQ 互联上申请的 appid。
    • client_secret ( 必填 ) : 应用在 QQ 互联上申请的 appkey。
    • refresh_token ( 必填 ) : 在获取 Access_Token 时一起获取到的 Refresh_Token。
  • 获取用户的 OpenId: 拿到 Access_Token 之后,后面的操作可以说是一马平川了,根据上一步获取到的 Access_Token,调用 OpenId 获取接口 获取用户在 QQ 服务器上的唯一标识 (OpenId)。参数说明:

    • access_token ( 必填 ) : 上一步获取到的 Access_Token。
  • 调用 OpenAPI 访问/修改用户信息: QQ 登录提供了用户信息/动态同步/日志/相册/微博等 OpenAPI ( 详见 API列表 ),网站需要将请求发送到某个具体的 OpenAPI 接口,以访问或修改用户数据。

其实到了上面第三步,就已经完成目标了 ( 第三方登录 ),但是为了让用户体验更好,在自己的系统中显示用户的头像等,可以使用第四步进一步收集用户信息。


Spring Social 导航

到了这里,我们基本上对网站接入 QQ 登录的流程比较清楚了。接下来我们的问题就是如何在我们的系统中去实现这些流程。这里以 Spring 为基础来进行处理,进过一些搜索,我们可以发现 Spring 官方有一个叫做 SpringSocial 的框架,专门用来进行第三方认证的。

老规矩,先去看一下 SpringSocial 官网,在官网上,我们发现官方已经实现了 Facebook、Twitter、Linkedin 三种登录方式。可惜国内一般都用不到,在孵化箱里面有 Github、Tripit。还有很多社区项目

commuti-project

不过可惜的是,没有 QQ,看样子我们得自己撸一个了,可惜,可惜。这时候问题来了,怎么撸呢?继续找文档,根据 SpringSocial-Core 自己实现。进入 Sping Social Core 页面,发现有当前三个版本

  • 1.0.3 GA
  • 1.1.6 GA (Current)
  • 2.0.0 M4 (PRE)

可以看到是两个正式版与一个预览版,比较尴尬的时,1.1.6 GA 版的 Reference 404 打不开了…,所以决定先看下 2.0.0 M4 的 Reference,配合 Spring Social Showcase、Spring Social Quickstart、Spring Social Canvas 进行一个入门学习。

Service Provider Connect Framework

The spring-social-core module includes a Service Provider Connect Framework for managing connections to Software-as-a-Service (SaaS) providers such as Facebook and Twitter. This framework allows your application to establish connections between local user accounts and accounts those users have with external service providers. Once a connection is established, it can be be used to obtain a strongly-typed Java binding to the ServiceProvider’s API, giving your application the ability to invoke the API on behalf of a user.

Spring Social Core 包含 Service Provider Connect Framework (服务提供商连接框架),用来管理服务于服务之间的连接。

Service Provider 的概念在之前 OAuth2 的文章中有提及过。

举个例子,我们自己有一个 Application 叫做 MyApplication,要允许用户将他在微信上的朋友圈分享到 MyApplication 上,要支持这个功能,就需要在用户 MyApplication 账户 与用户的微信账户之间建立 Connection,一旦建立,就可以获取到用户在微信上的信息,并在 MyApplication 上使用,Spring Social Connection Framework 就是帮我们做到这个目的的。


Spring Social 架构介绍

structure

看框架要抓住核心重点,这里的重点是 Connection,整个框架围绕它的生成与存储,起着承上启下的作用。上图中主要包含两个步骤:

Establishing connections (建立连接)
  • Connection 由 ConnectionFactory 生成
    • ConnectionFactory
      • ServiceProvider : 第三方服务商在系统中的抽象表示
        • OAuth2Operation
          • 构建第三方授权 url ( 用户使用第三方认证时,引导用户跳转的页面地址 )
          • 根据授权码获取 AccessToken,最终封装成 AccessGant (包括请求的封装,结果的解析)
          • AccessToken 过期刷新
          • 客户端认证
        • Api
          • 第三方开放接口的封装调用
      • ApiAdapter
        • 测试第三方开放接口是否可以使用
        • 从第三方开发接口中获取用户在第三方服务商的唯一标识,设置给 ConnectionKey
        • 获取用户在第三方服务器上的基本信息 (依赖开放接口)
        • 更新用户在第三方服务器上的状态 ( 依赖开放接口 )
Persisting connections ( 持久化连接 )
  • UserConnectionRepository : 持久化 Connection,方便以后使用复用,不用每次都进行连接、绑定

接入第三方登录

上面两步解决了 Connection 的创建于持久化,但是还缺少最重要的一步,将建立的 Connection 用于我们便捷的第三方登录。SpringSocial 给我们提供了两种 Signing in with Service Provider Accounts 的方式:

  • ProviderSignInController: SpringMVC 形式的 Controller,提供认证功能
  • SocialAuthenticationFilter: 可以集成到 SpringSecurity 的过滤器,可以融入到 SpringSecurity 认证流程中

因为这是 SpringSecurity 系列文章,所以选择与 SpringSecurity 联系更紧密的方案 SocialAuthenticationFilter。

SpringSocial 结合 SpringSecurity 大体流程图

flow

  • SocialAuthenticationFilter 拦截指定 Url,从请求中获取对应服务商的 ProviderId,根据 ProviderId 获取 SocialAuthenticationService。

  • 调用 SocialAuthenticationService 的 getToken 方法。该方法有两种触发场景:

    • 未携带授权码,尚未认证,来认证的。调用对应 ConnectionFactory 中的 ServerProvider 的 OAuth2Operations 来构造第三方服务商的授权 Url,并将用户引导到该页面进行授权。
    • 携带授权码,用户在上面一步中的第三方授权页面授权后,通过 Redirect Url 跳转回来。拿到授权码后会使用对应 ConnectionFactory 中的 ServerProvider 的 OAuth2Operations 来获取 AccessGrant。最后使用 AccessGrant 创建一个 Connection。使用该 Connection 构建 SocialAuthenticationToken 返回。
  • 拿到 SocialAuthenticationToken 后,走 SpringSecurity 的标准认证流程,通过 ProviderManger 找到 SocialAuthenticationProvider,调用其 authenticate 方法。在这个方法里面,会根据 Connection 去查找用户对应的 UserId ( 唯一标识, userId 只是一个代号 )。

    • 这里需要注意的是,这里查找的数据库中的每一条记录,代表的是当前系统中的一个账户与第三方服务商上的一个账户的绑定关系。如果通过 Connection 查找不到记录,表示该第三方账户在本系统中还没有关联过账户。这时可以做两种处理:
      • 引导用户跳转到注册页面,注册完毕,后台自动进行绑定。
      • 后台自动给用户创建一个账户,然后绑定,返回。
  • 拿到 UserId 后,调用 SocialUserDetailsService 的 loadUserByUserId 方法获取 UserDetails。最终使用之前获取到的 SocialAuthenticationToken 与 UserDetial 构造一个新的 SocialAuthenticationToken 返回,作为当前用户的认证凭证。


主要代码实现

分析了这么多,最后贴一下主要的最终的实现代码,完整代码后面会放到 GitHub 上。

ServiceProvider

服务提供商,主要提供 Api 与 OAuth2Operations。

/**
 * @author: hblolj
 * @Date: 2019/3/18 9:20
 * @Description:
 * @Version:
 **/
public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ>{

    private String appId;

    // 引导用户跳转到 QQ 上认证的链接,获取到授权码
    private static final String URL_AUTHORIZE ="https://graph.qq.com/oauth2.0/authorize";

    // 使用授权码从 QQ 上获取 accessToken 的请求链接
    private static final String URL_ACCESS_TOKEN = "https://graph.qq.com/oauth2.0/token";

    /**
     * @param appId QQ 互联平台上申请的应用的 AppId
     * @param appSecret QQ 互联平台上申请的应用的 AppSecret
     */
    public QQServiceProvider(String appId, String appSecret) {
        super(new QQOAuth2Template(appId, appSecret, URL_AUTHORIZE, URL_ACCESS_TOKEN));
        this.appId = appId;
    }

    @Override
    public QQ getApi(String accessToken) {
        return new QQImpl(accessToken, appId);
    }

}
OAuth2Operations

这里主要重写了 createRestTemplate 方法与 postForAccessGrant 方法,因为 QQ 互联 AccessToken 的返回结果与默认的格式有差别,所以进行重写自定义。

/**
 * @author: hblolj
 * @Date: 2019/3/18 14:52
 * @Description:
 * @Version:
 **/
@Slf4j
public class QQOAuth2Template extends OAuth2Template{

    public QQOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
        super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
        // 只有该值为 true 时,在使用授权码获取 accessToken 时,才会携带 client_id 与 client_secret,而 QQ 访问 accessToken 的接口是需要这两个参数的
        setUseParametersForClientAuthentication(true);
    }

    /**
     * 默认创建的 RestTemplate 不支持 html/text,继承覆盖添加,而 QQ 服务商返回是以改格式返回的,所以重写添加支持
     * @return
     */
    @Override
    protected RestTemplate createRestTemplate() {

        RestTemplate restTemplate = super.createRestTemplate();

        restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));

        return restTemplate;
    }

    /**
     * 实际上向服务商发起请求 AccessToken 的方法,包括对获取到的结果进行格式解析。
     * 默认实现是从 json 中以键值对形式获取,但是 QQ 返回的是在字符串中
     * eg: access_token=FE04************************CCE2&expires_in=7776000
     * &refresh_token=88E4************************BE14
     * 所以对该方法进行重写
     * @param accessTokenUrl
     * @param parameters
     * @return
     */
    @Override
    protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {

        String result = getRestTemplate().postForObject(accessTokenUrl, parameters, String.class);
        log.info("获取 accessToken 的响应: " + result);

        // 这里使用的 StringUtils 属于 commons.lang 包
        // 按照 & 分隔符进行切割
        String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(result, "&");

        // 取出 = 号后面的字符串
        String access_token = StringUtils.substringAfterLast(items[0], "=");
        Long expires_in = Long.parseLong(StringUtils.substringAfterLast(items[1], "="));
        String refresh_token = StringUtils.substringAfterLast(items[2], "=");

        return new AccessGrant(access_token, null, refresh_token, expires_in);
    }

    /**
     * 使用授权码获取 AccessToken 的方法,主要负责参数的拼装与设置
     * 如果默认参数与服务提供商的提供的接口参数不一致,需要重写该方法
     * @param authorizationCode
     * @param redirectUri
     * @param additionalParameters
     * @return
     */
    @Override
    public AccessGrant exchangeForAccess(String authorizationCode, String redirectUri, MultiValueMap<String, String> additionalParameters) {
        return super.exchangeForAccess(authorizationCode, redirectUri, additionalParameters);
    }
}
Api

QQ 互联提供的开发接口的抽象,目前只定义了一个 getUserInfo 接口。Api 接口的实现,官方约定名称最好为 xxxTemplate,所以这里最好为 QQTemplate,在构造方法里面获取用户的 OpenId,是因为后面调用开发接口的时候需要该参数,所以提前获取准备。

/**
 * @author: hblolj
 * @Date: 2019/3/16 16:11
 * @Description:
 * @Version:
 **/
public interface QQ {

    QQUserInfo getUserInfo();
}
/**
 * @author: hblolj
 * @Date: 2019/3/16 16:12
 * @Description: Api 实现类,官方推荐 Api 实现类的命名最好为 XXXTemplate
 * @Version:
 **/
@Slf4j
public class QQImpl extends AbstractOAuth2ApiBinding implements QQ{

    /**
     * 整体流程
     * accessToken 是用户授权从 QQ 服务器中获取
     * 使用 accessToken 获取用户 openId
     * 然后使用用户 openId 获取用户基本信息
     */

    // 根据 access_token 获取 openId
    private static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s";

    private static final String URL_GET_USERINFO = "https://graph.qq.com/user/get_user_info?oauth_consumer_key=%s&openid=%s";

    private String appId;

    private String openId;

    private ObjectMapper objectMapper = new ObjectMapper();

    public QQImpl(String accessToken, String appId) {
        // 这里父类一个参数的构造函数,会默认调用两个参数的构造函数,策略会自定选择 TokenStrategy.AUTHORIZATION_HEADER
        // 将请求参数放到请求头中,但是通过 QQ 互联开放平台的文档,我们发现其要求参数需要放到参数中,所以这里手动指定下策略
        super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);
        this.appId = appId;
        // 通过 accessToken 获取 openId
        String url = String.format(URL_GET_OPENID, accessToken);
        String result = getRestTemplate().getForObject(url, String.class);
        log.info("OpenId Result: {}", result);
        // eg: callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} )
        String openId = StringUtils.substringBetween(result, "\"openid\":\"", "\"}");
        log.info("OpenId Substring: {}", openId);
        this.openId = openId;

        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    @Override
    public QQUserInfo getUserInfo() {

        // 父类的构造函数已经帮我们将 accessToken 放在请求中
        String url = String.format(URL_GET_USERINFO, appId, openId);

        String result = getRestTemplate().getForObject(url, String.class);

        try {
            QQUserInfo userInfo = objectMapper.readValue(result, QQUserInfo.class);
            userInfo.setOpenId(this.openId);
            return userInfo;
        } catch (IOException e) {
            throw new RuntimeException("获取用户 QQ 基本信息失败!");
        }
    }
}
Adapter

将 Api 获取到的信息适配给 Connection。

/**
 * @author: hblolj
 * @Date: 2019/3/18 9:29
 * @Description:
 * @Version:
 **/
@Slf4j
public class QQAdapter implements ApiAdapter<QQ>{

    /**
     * 测试 Api 获取服务是否可以正常使用
     * @param api
     * @return
     */
    @Override
    public boolean test(QQ api) {
        return true;
    }

    @Override
    public void setConnectionValues(QQ api, ConnectionValues connectionValues) {

        QQUserInfo userInfo = api.getUserInfo();

        log.info("UserInfo: {}", userInfo.toString());

        connectionValues.setDisplayName(userInfo.getNickname());
        connectionValues.setImageUrl(userInfo.getFigureurl_qq_1());
        // 用户个人主页地址,qq 没有个人主页,所以这里不设置
        connectionValues.setProfileUrl(null);
        // 用户在服务商(QQ)上的唯一标示(openId),给 Connection 设置 Key
        connectionValues.setProviderUserId(userInfo.getOpenId());
    }

    @Override
    public UserProfile fetchUserProfile(QQ api) {
        // 获取用户基本信息
        return null;
    }

    @Override
    public void updateStatus(QQ api, String s) {
        // 给服务商发送消息更新用户在服务商上的状态,QQ 没有改功能 do nothing
    }
}
ConnectionFactory

每一个服务提供商都有一个唯一的 ConnectionFactory,所以这里的 ServiceProvider 与 Adapter 都是 new。

/**
 * @author: hblolj
 * @Date: 2019/3/18 10:01
 * @Description:
 * @Version:
 **/
public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ>{

    /**
     * @param providerId 服务提供商唯一标识
     * @param appId 本服务在服务提供商上注册的 appId
     * @param appSecret 本服务在服务提供商上注册的 appSecret
     */
    public QQConnectionFactory(String providerId, String appId, String appSecret) {
        // serviceProvider 自定义服务提供商代理,获取从服务提供商获取数据的 Api 实现
        // apiAdapter 数据适配器,适配从服务商获取到用户基本信息到 Connection
        super(providerId, new QQServiceProvider(appId, appSecret), new QQAdapter());
    }
}
Config

自定义的 SocialConfig 与 QQAutoConfig 都是 SocialConfigurer 的实现类。他们主要完成了两个功能:

  • 添加 ConnectionFactory
  • 返回 UsersConnectionRepository

CustomSpringSocialConfigurer 的作用是将 SocialAuthenticationFilter 挂载到 SpringSecurity 的过滤器链上。

/**
 * @author: hblolj
 * @Date: 2019/3/18 10:10
 * @Description: SocialConfigurerAdapter 需要添加 spring-social-config 依赖
 * @Version:
 **/
@Order(1)
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter{

    @Resource(name = "socialDataSource")
    private DataSource dataSource;

    @Resource(name = "customSecurityProperties")
    private SecurityProperties securityProperties;

    @Autowired(required = false)
    private ConnectionSignUp connectionSignUp;

    /**
     * @param connectionFactoryLocator ConnectionFactory 定位器,系统种可能存在多种 ConnectionFactory( qq、微信、微博...),
     *                                 选择一种来生成 Connection
     * @return
     */
    @Override
    public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
        // dataSource 数据源
        // Encryptors.noOpText() 加密方式 - 不加密
        // 默认建表 sql 与 JdbcUsersConnectionRepository 类在用一个目录下
        JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
        // 设置数据库表前缀,默认为 UserConnection,可以在这个基础上自定义前缀
        repository.setTablePrefix("yszn_");
        // 自定义默认注册逻辑,社交登录后在数据库中(按服务商类型 + 用户在服务商的唯一标识)没有找到对应的用户信息,
        // 调用自定义的 ConnectionSignUp 方法为用户在系统中注册一个账号
        if (connectionSignUp != null){
            repository.setConnectionSignUp(connectionSignUp);
        }
        return repository;
    }

    /**
     * SpringSocial 认证配置
     * 将该 Config 添加到 WebSecurityConfig 中
     * @return
     */
    @Bean
    public SpringSocialConfigurer customSocialSecurityConfig(){
//        return new SpringSocialConfigurer();
        CustomSpringSocialConfigurer configurer = new CustomSpringSocialConfigurer(securityProperties.getSocial().getFilterProcessesUrl());
        // 配置注册页面,当找不到用户时,会跳转到该页面
        configurer.signupUrl(securityProperties.getBrowser().getSignUpUrl());
        // 设置登录成功跳转地址
        configurer.postLoginUrl("/hello");
        return configurer;
    }

    @Bean
    public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator){
        return new ProviderSignInUtils(connectionFactoryLocator, getUsersConnectionRepository(connectionFactoryLocator));
    }

}
/**
 * @author: hblolj
 * @Date: 2019/3/18 11:08
 * @Description: 只有配置了 appId,该配置项才会生效
 * @Version:
 **/
@Order(2)
@Configuration
@ConditionalOnProperty(prefix = "hblolj.security.social.qq", name = "appId")
public class QQAutoConfig extends SocialAutoConfigurerAdapter{

    /**
     * 这里用 Resource 指定 name,因为在 SpringSocial 把下面有一个同名的 SecurityProperties
     */
    @Resource(name = "customSecurityProperties")
    private SecurityProperties securityProperties;

    @Override
    protected ConnectionFactory<?> createConnectionFactory() {

        QQProperties qqProperties = securityProperties.getSocial().getQq();

        return new QQConnectionFactory(qqProperties.getProviderId(), qqProperties.getAppId(), qqProperties.getAppSecret());
    }

    /**
     * 可以自定义一个名称为 qqConnectedView 的 Bean 来覆盖此默认实现
     * callbackConnect 是解绑成功
     * @return
     */
    @Bean({"connect/callbackConnect"})
    @ConditionalOnMissingBean(name = "qqConnectedView")
    public View qqConnectView(){
        return new CustomConnectionView();
    }

    /**
     * callbackConnected 是绑定成功
     * @return
     */
    @Bean({"connect/callbackConnected"})
    @ConditionalOnMissingBean(name = "qqConnectedView")
    public View qqConnectedView(){
        return new CustomConnectionView();
    }
}
/**
 * @author: hblolj
 * @Date: 2019/3/18 14:07
 * @Description:
 * @Version:
 **/
public class CustomSpringSocialConfigurer extends SpringSocialConfigurer{

    // 自定义 SocialAuthenticationFilter 拦截的 Url
    private String filterProcessesUrl;

    public CustomSpringSocialConfigurer(String filterProcessesUrl) {
        this.filterProcessesUrl = filterProcessesUrl;
    }

    @Override
    protected <T> T postProcess(T object) {

        SocialAuthenticationFilter filter = (SocialAuthenticationFilter) super.postProcess(object);

        filter.setFilterProcessesUrl(filterProcessesUrl);

        return (T) filter;
    }
}
绑定、解绑结果页面设置

自定义绑定、解绑的处理结果,解绑、绑定的主要逻辑在 ConnectController 中。

/**
 * @author: hblolj
 * @Date: 2019/3/19 17:31
 * @Description:
 * @Version:
 **/
@Slf4j
public class CustomConnectionView extends AbstractView{

    @Override
    protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {

        httpServletResponse.setContentType("text/html;charset=UTF-8");

        if (model.get("connections") == null){
            // 解绑
            httpServletResponse.getWriter().write("<h3>解绑成功</h3>");
        }else {
            httpServletResponse.getWriter().write("<h3>绑定成功</h3>");
        }
    }
}
SpringSocial登录后重复跳转注册界面 这个问题呢是比较恶心的,先简单描述一下症状,首先代码SpringBoot整合SpringSecurity+SpringSocial是没问题的,使用QQ登录在没有绑定账号的时候会跳转注册界面,这是没问题的,但是第一次跳转到注册界面后注册完毕且和QQ账号绑定后,数据库中也是有QQ授权的数据和注册的账号相绑定的,再次访问手保护的资源时跳转到登录页面,这个没问题,然后选择QQ登录。这是使用QQ登录后又TM跳转带注册界面,那么这就是这个问题的症状。于是我打断点调试了下代码,问题定位在SocialAuthenticat 阅读详情

相关推荐

【Spring Security技术栈开发企业级认证与授权】----使用Spring social开发第三方登录:(QQ 与微信登录)

博客主要分享如何实现QQ与微信登录

知识改变命运,ヾ(◍°∇°◍)ノ゙【求关注】 910

spring oauth2 social实现QQ登录

文章目录介绍实现功能开发步骤开发流程图引入jar包数据库sqlQQ用户信息实体类QQapi重写OAuth2Template编写自己的QQServiceProvider编写自己的QQAdapterQQ连接工厂QQConnectionFactory自定义QQ的服务提供商ID自定义生成自己的处理的URL创建UsersConnectionRepository自定义处理注册实现编写配置applicaion....

一个正在崛起的小码农 5191

socialLogin:第三方登陆库

SocialLogin 一个简单易用的跨框架第三方登陆库 | An easy to use cross-frame social login library SocialLogin 是一个简单易用的跨框架第三方登陆库,专注和致力于解决第三方登陆的问题,无需再看官方手册,一个一个服务商去接入登陆服务,现在只需要使用SocialLogin库,把接入的服务商appkey 和 appsecret填好,立即就可以使用,让你无需关心细节实现,专注实现业务逻辑。 如何安装 | Installation composer require barbery/social-login:dev-master 如果你还不懂如何使用composer,请看: 如何使用 | Usage 在框架的控制器中,建立一个Action来统一处理第三方登陆,以下的例子是假设在user的控制器中添加一个socialLogin的Acti

Spring Security源码分析三:Spring Social实现QQ社交登录

社交登录又称作社会化登录(Social Login),是指网站的用户可以使用腾讯QQ、人人网、开心网、新浪微博、搜狐微博、腾讯微博、淘宝、豆瓣、MSN、Google等社会化媒体账号登录该网站。 OAuth2.0的认证流程示意图 请求第三方应用 第三方应用将用户请求导向服务提供商 用户同意授权 服务提供商返回code client根据code去服务提供商换取令牌 返回令牌 获取用户信息 在标准的OAu

郑龙飞 4025

基于Java的高校校园点餐系统

本文介绍了一个基于Java技术开发的高校校园点餐系统,该系统以B/S结构为基础,采用MySQL作为数据库,实现了一个高效、便捷的在线点餐平台。系统主要面向三类用户:管理员、用户和食堂工作人员,提供包括用户管理、食堂管理、菜单管理、订单管理等在内的多项功能。通过系统的具体测试,验证了其可行性和有效性,为高校师生提供了一个科学化、系统化的点餐服务。高校校园点餐系统的开发和实现,不仅提高了校园餐饮服务的效率和质量,也为师生提供了一个更加便捷的点餐体验。

a632341031的博客 841

Python爬虫实战:5分钟抓取东方财富股票数据并保存为CSV

在投资分析与量化研究的日常工作中,获取结构化、可用的金融数据往往是第一步。对于个人投资者和量化入门者而言,如何高效获取A股全市场行情,常常面临网页动态加载、反爬机制与数据整理繁琐等挑战。本文从数据采集的基础概念出发,介绍如何利用Python这一通用编程语言,通过requests库请求财经网站公开的HTTP接口,获取返回的JSON数据,再借助pandas进行字段映射、数据清洗与表格化处理。这种方式相比传统页面解析具有响应快、结构稳定、易于批量操作的显著优势,尤其适合构建个人股票数据池、验证选股策略或进行市场情

weixin_33146151的博客 251

SpringSocial--QQ实现

SpringSocial实现第三方社交登陆---QQ。 根据SpringSocial提供的相关接口,我们只需要从服务提供商那里获取到用户的相关信息,然后交给SpringSecurity就可以轻松的实现QQ登陆。 相关涉及到的类/接口: 1. AbstractOAuth2ServiceProvider 2.OAuth2Template 3.OAuth2Connectio...

喝醉的咕咕鸟 1212

DCM、PLL以及DLL等概念及详情

介绍DLL、PLL以及DCM的概念,总结其模块构成、工作原理、相互之间的差异与各自优势。

CLL_caicai的博客 8339

Spring Social实现QQ社交登录、注册

文章目录第一章 OAuth协议简介1.1 OAuth协议要解决的问题是什么1.2 OAuth协议中的各种角色1.3 OAuth协议运行的流程1.3.1 授权码模式的特点第二章 Spring Social的基本原理第三章 实现QQ登陆实现的流程:3.1 Api3.1.1实现类QQImpl:3.1.2 实现类AbstractOAuth2ApiBinding:3.2 QQServiceProvider3...

weixin_38927257的博客 1688

告别时钟树平衡噩梦:在Innovus中巧妙设置stop/ignore pin来优化分段时钟网络

本文深入探讨了在Innovus工具中利用stop/ignore pin优化分段时钟树网络的高阶技巧。针对数字IC后端设计中的时钟树平衡难题,详细解析了特殊引脚属性的战略应用场景和配置方法,帮助工程师有效解决SoC设计中复杂的时钟结构问题,显著提升时序收敛效率。

weixin_30836759的博客 294

保姆级教程:用CellOracle 0.10.13从单细胞数据构建基因调控网络(附完整代码)

本文提供了一份详细的CellOracle 0.10.13使用指南,帮助用户从单细胞数据构建基因调控网络。通过Python接口,即使是生物信息学新手也能快速掌握数据准备、KNN插补、GRN计算及可视化等关键步骤。文章包含完整代码和实用技巧,助力单细胞转录组数据分析。

weixin_42563415的博客 727

使用SpringSocial开发QQ登录

⒈编写QQ用户对应的数据结构 1 package cn.coreqi.social.qq.entities; 2 3 /** 4 * 封装QQ的用户信息 5 */ 6 public class QQUserInfo { 7 8 /** 9 * 返回码 10 */ 11 priva...

weixin_30767921的博客 230

Etcd教程 — 第三章 Etcd集群动态发现、DNS发现

Etcd教程 — 第三章 Etcd集群动态发现、DNS发现

Mr_XiMu的博客 2825

SpringSecurity学习总结-4 第五章 使用Spring Social开发第三方登录

SpringSecurity学习总结-4 第五章 使用Spring Social开发第三方登录

Mr_XiMu的博客 1712

探索Three.js:利用ShaderMaterial实现动态着色器特效

本文深入探讨了Three.js中ShaderMaterial的应用,指导开发者利用GLSL编写自定义着色器实现动态特效。文章从核心概念GLSL、Uniforms与Attributes讲起,通过创建动态颜色变化平面的实战案例,逐步进阶到实现波纹扩散与动态光影等高级效果,并提供了性能优化与调试技巧,帮助读者掌握创建高性能WebGL视觉特效的关键技能。

weixin_29250403的博客 201

新大陆NVH200-AP(U)扫码枪在上位机软件开发中的应用

由于本次使用的是USB接口的扫码枪。

qq_34059233的博客 2368

Spring Security技术栈学习笔记(十四)使用Spring Social集成QQ登录验证方式

上一篇文章《Spring Security技术栈开发企业级认证与授权(十三)Spring Social集成第三方登录验证开发流程介绍》主要是介绍了OAuth2协议的基本内容以及Spring Social集成第三方登录验证的基本流程。那么在前篇文章的基础上,我们在本篇文章中将介绍Spring Social集成QQ登录的一个完整的开发流程。 ...

脚踏实地,慢慢来 8421

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

spring-security进阶1---第三方登陆原理2【springsocial】

上篇文章已经比较细致地讲解了第三方登陆的基本原理,但是具体该如何去真正落实到代码上呢?这里就不得不提springsocial,

nrsc 928

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

springsecurity oauth2_SpringSecurity,oauth2.0,SpringSocial第三方登录及jwt验证(4)

上一篇跳转地址看知道:SpringSecurity,oauth2.0,SpringSocial第三方登录及jwt验证(3)​zhuanlan.zhihu.com使用springSocial实现第三方qq登录1、在 QQ互联 申请成为开发者,并创建应用,得到APP ID 和 APP Key。2、了解QQ登录时的 网站应用接入流程。(必须看完看懂)创建数据库,用于存储登录用户的绑定信息CREATE 首...

weixin_39552286的博客 110

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

SpringSocial请求connect查三方绑定情况失败JdbcUsersConnectionRepository不生效,默认使用InMemoryUsersConnectionRepository

一,基于SpringSocial进行QQ,微信等第三方社交账号登陆开发,在进行开发查询社交账号绑定情况测试时,发现无法查询到本账号在系统内的绑定情况,前景提要,该用户已完成微信绑定。 二,数据落库情况,绑定成功 三, SpringSocia查询用户社交账号绑定情况是访问“/connec”接口,测试结果,无数据,失败 四,SpringSocia默认处理该请求的接口是ConnectControll...

aaa_bbb_ccc_123_456的博客 2706

膜拜,阿里爆款的SpringSecurity教程,太详细了

Spring Security 是一个基于 Spring AOP 和 Servlet 过滤器的安全框架,它提供了安全性方面的解决方案Spring Security 作为非常强大的框架,作为程序员是非常热爱的,我这里整理了四份Spring Security手写笔记及实战手册分享给大家不多说,看干货。

m0_69305074的博客 324

全新演绎!终于有人整理出Spring Security全栈进阶笔记了!理论+源码+实战全有!

本篇文章包含Spring Security Java配置、Spring Security安全防护和源码导读。详细讲解OAuth2实战,并深入地剖析OAuth2核心源码! 这份PDF可以分为一下四个部分: 第1部分:Spring Security的基本配置。 第2部分:剖析Web项目可能遇到的安全问题,同时讲解如何使用Spring Security进行有效防护。 第3部分:详细介绍OAuth,并使用Spring Social整合Spring Security,实现QQ快捷登录。 第4部分:重点介绍S

weixin_50205273的博客 124

十年阿里巴巴资深架构师整理分享的SpringSecurity实战文档

前言 SpringSecurity是一个强大且高度可定制的安全框架,致力于为Java应用提供身份认证和授权。 Spring Security 的前身是 Acegi Security,在被收纳为Spring子项目后正式更名为Spring Security。在笔者成书时,Spring Security已经升级到5.1.3.RELEASE版本,不仅新增了原生OAuth框架,还支持更加现代化的密码加密方式。可以预见,在 Java 应用安全领域,Spring Security会成为首先被推崇的安全解决方案。

m0_49496327的博客 421

Spring Security渐入佳境(四) -- 第三方应用授权登录

单点登录(Single Sign On,简称SSO),它的用途在于,不管多么复杂的应用群,只要在用户权限范围内,那么就可以做到,用户只需要登录一次就可以访问权限范围内的所有应用子系统。对于用户而言,访问多个应用子系统只需要登录一次,同样在需要注销的时候也只需要注销一次。①各应用间的关系:OAuth2.0授权服务端和第三方客户端不属于一个互相信任的应用群(通常都不是同一个公司提供的服务),第三方客户端的用户不属于OAuth2.0授权服务端的官方用户;而单点登录的服务端和接入的客户端都在一个互相信任的应用群(通

分享技术,共同进步! 1702

SpringSecurity和SpringSocial实现QQ登录

siki学院课程: http://www.sikiedu.com/course/366 (SpringSecurity和SpringSocial认证授权) http://www.sikiedu.com/course/383(个人博客项目实战) 一、简单的第三方登录 二、Oauth协议介绍 目的是让用户在不将用户名和密码提供给第三方软件的情况下,让第三方软件可以有权限访问该用户在服务器提供商上的的资源。 三、OAuth协议流程 四、授权码模式 用户同意授权的模式 授权码模式 密码模式 客户.

有人的地方,就是江湖 800
上一篇: OAuth2 协议及其四种授权模式
下一篇: SpringSocial 源码分析
Earth_Programer
博客等级 码龄11年 18粉丝 17原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值