编写顺序为:

  • Api
    获取用户信息
  • Oauth2Operations
    代表用户与服务提供者进行Oauth认证
  • ServiceProvider
    服务提供商
  • ApiAdapter
    连接统一的{@link Connection}模型和特定的提供者API模型的适配器
  • ConnectionFactory
    构造服务提供者{@link Connection}实例的工厂的基本抽象。
    创建Connection保存
创建用户信息类QQUserInfo:

按照QQ互联官方返回值获取到的用户信息进行构建:

package com.cong.security.core.social.qq.api;
import lombok.Data;
@Data
public class QQUserInfo {/* 返回码*/private String ret;/* 如果ret<0,会有相应的错误信息提示,返回数据全部用UTF-8编码。 */private String msg;/* QQ用户在系统中的唯一标识 */private String openId;/* 不知道什么东西,文档上没写,但是实际api返回里有。 */private String is_lost;/* 省(直辖市) */private String province;/* 市(直辖市区) */private String city;/* 出生年月 */private String year;/* 用户在QQ空间的昵称。*/private String nickname;/* 大小为30×30像素的QQ空间头像URL。*/private String figureurl;/*大小为50×50像素的QQ空间头像URL。*/private String figureurl_1;/* 大小为100×100像素的QQ空间头像URL。 */private String figureurl_2;/* 大小为40×40像素的QQ头像URL。 */private String figureurl_qq_1;/* 大小为100×100像素的QQ头像URL。需要注意,不是所有的用户都拥有QQ的100×100的头像,但40×40像素则是一定会有。 */private String figureurl_qq_2;/* 性别。 如果获取不到则默认返回”男” */private String gender;/* 新增参数???? */private String gender_type;/* 标识用户是否为黄钻用户(0:不是;1:是)。 */private String is_yellow_vip;/* 标识用户是否为黄钻用户(0:不是;1:是) */private String vip;/* 黄钻等级*/private String yellow_vip_level;/* 黄钻等级 */private String level;/* 标识是否为年费黄钻用户(0:不是; 1:是)*/private String is_yellow_year_vip;// 未知参数private String constellation;private String figureurl_type;private String figureurl_qq;
}

注意其中有一个gender_type参数,我以前也是没有的,但是在使用过程中突然有一次报错,查看日志发现就是多了这个参数,虽然我也不知道是干啥的。。

QQ获取信息接口:
package com.cong.security.core.social.qq.api;public interface QQ {/*** 获取用户QQ信息* * @return QQUserInfo*/QQUserInfo getUserInfo();
}
QQ获取信息接口实现类:
package com.cong.security.core.social.qq.api;import org.apache.commons.lang3.StringUtils;
import org.springframework.social.oauth2.AbstractOAuth2ApiBinding;
import org.springframework.social.oauth2.TokenStrategy;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;/*** * @author single-聪* @date 2019年8月12日* @version 0.0.1*/
@Slf4j
public class QQImpl extends AbstractOAuth2ApiBinding implements QQ {// 根据用户access_token获取用户openIdprivate static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s";// 根据系统appId以及用户openId获取用户信息(access_token值的绑定已经在父类中实现)private static final String URL_GET_USERINFO = "https://graph.qq.com/user/get_user_info?oauth_consumer_key=%s&openid=%s";// accessToken由父类处理,当前类不处理// QQ互联注册到的APPID,在系统中属于唯一值private String appId;// 每一个用户QQ互联给出的唯一标识private String openId;private ObjectMapper objectMapper = new ObjectMapper();/*** 构造函数(openId根据accessToken去QQ互联获取,不需要传入)* * @param accessToken*            走完oAuth流程获取到的令牌* @param appId*            系统appId*/public QQImpl(String accessToken, String appId) {// 父类默认是放在请求头,QQ要求是将accessToken作为参数传入,此时系统会将accessToken作为参数组装到URL_GET_USERINFO里面super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);// appIdthis.appId = appId;// 使用accessToken替换URL_GET_OPENID的%sString url = String.format(URL_GET_OPENID, accessToken);String result = getRestTemplate().getForObject(url, String.class);log.info("调用腾讯QQ登录,返回值为[{}]", result);// 发送请求获取openIdthis.openId = StringUtils.substringBetween(result, "\"openid\":\"", "\"}");}@Overridepublic QQUserInfo getUserInfo() {// accessToken在类初始化时已经实现// 将appId和openId更换到URL_GET_USERINFO字符串中String url = String.format(URL_GET_USERINFO, appId, openId);String result = getRestTemplate().getForObject(url, String.class);log.info("调用腾讯QQ登录,接口为:[{}]获取用户信息为[{}]", url, result);// 将返回的用户信息转换成QQUserInfo类型,如果转换失败则代表获取用户信息失败QQUserInfo userInfo = null;try {// 将JSON格式字符串转换成QQUserInfo对象userInfo = objectMapper.readValue(result, QQUserInfo.class);userInfo.setOpenId(openId);return userInfo;} catch (Exception e) {throw new RuntimeException("获取用户信息失败", e);}}
}
编写QQServiceProvider
package com.cong.security.core.social.qq.connect;import com.cong.security.core.social.qq.api.QQ;
import com.cong.security.core.social.qq.api.QQImpl;
import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider;
import org.springframework.social.oauth2.OAuth2Template;
import lombok.extern.slf4j.Slf4j;
/*** 用来整合QQOAuth2Template和AbstractOAuth2ApiBinding*/
@Slf4j
public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ> {private String appId;// 在QQ互联上可查到,此配置不变,将用户导向认证服务器的URLprivate static final String URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize";// 在QQ互联上可查到,此配置不变,申请令牌的URLprivate static final String URL_ACCESS_TOKEN = "https://graph.qq.com/oauth2.0/token";// 默认的构造函数public QQServiceProvider(String appId, String appSecret) {/*** 在QQ互联注册时QQ互联分配的appId以及appSecret<br>* clientId---appId<br>* clientSecret---appSecret<br>* authorizeUrl---对应第一步导向的认证服务器<br>* accessTokenUrl---对应第四步申请令牌的地址<br>*/super(new OAuth2Template(appId, appSecret, URL_AUTHORIZE, URL_ACCESS_TOKEN));this.appId = appId;}@Overridepublic QQ getApi(String accessToken) {log.info("获取到用户accessToken值为:[{}]", accessToken);return new QQImpl(accessToken, appId);}
}
编写QQAdapter:
package com.cong.security.core.social.qq.connect;import com.cong.security.core.social.qq.api.QQ;
import com.cong.security.core.social.qq.api.QQUserInfo;
import org.springframework.social.connect.ApiAdapter;
import org.springframework.social.connect.ConnectionValues;
import org.springframework.social.connect.UserProfile;
import lombok.extern.slf4j.Slf4j;/*** 适配的API的类型为QQ*/
@Slf4j
public class QQAdapter implements ApiAdapter<QQ> {@Overridepublic boolean test(QQ api) {// 默认QQ永远是通的,实际上需要发送请求判断return true;}@Overridepublic void setConnectionValues(QQ api, ConnectionValues values) {// Connection数据和API数据之间实现适配QQUserInfo userInfo = api.getUserInfo();log.info("从服务商获取用户信息:[{}]", userInfo);// 显示的用户名values.setDisplayName(userInfo.getNickname());// 用户头像values.setImageUrl(userInfo.getFigureurl_qq_1());// 个人主页(QQ不存在这个信息)values.setProfileUrl(null);// 服务商的用户IDvalues.setProviderUserId(userInfo.getOpenId());}@Overridepublic UserProfile fetchUserProfile(QQ api) {// 绑定解绑return null;}@Overridepublic void updateStatus(QQ api, String message) {// QQ不存在此类方法调用,本系统中只使用QQ登录,不进行用户分享之类的}
}
构建QQConnectionFactory:
package com.cong.security.core.social.qq.connect;import com.cong.security.core.social.qq.api.QQ;
import org.springframework.social.connect.support.OAuth2ConnectionFactory;public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ> {/*** 创建连接工厂构造函数* * @param providerId*            QQ的openId* @param appId*            QQ互联AppId* @param appSecret*            QQ互联appSecret*/public QQConnectionFactory(String providerId, String appId, String appSecret) {/*** providerId---服务提供商唯一标识、配置文件配进来<br>* appId---QQ互联appId<br>* appSecret---QQ互联appSecret<br>*/super(providerId, new QQServiceProvider(appId, appSecret), new QQAdapter());}
}
配置SocialConfig:
package com.cong.security.core.social;import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.social.config.annotation.EnableSocial;
import org.springframework.social.config.annotation.SocialConfigurerAdapter;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository;/*** 社交登陆的适配器* * @author single-聪* @date 2019年8月12日* @version 0.0.1*/
@EnableSocial
@Configuration
public class SocialConfig extends SocialConfigurerAdapter {@Autowiredprivate DataSource dataSource;@Overridepublic UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {/*** dataSource---数据源<br>* connectionFactoryLocator---连接工厂,辨别<br>* Encryptors.noOpText()---暂时采用不加密的方式,方便数据库查看<br>*/JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource,connectionFactoryLocator, Encryptors.noOpText());// 设置表名前缀,数据库可以对表加前缀,需要注意windows和Linux系统的数据库区表名分大小写,如果报错找不到当前表,可以查看是否为此种情况// repository.setTablePrefix("my_");return repository;}// 过滤器链@Beanpublic SpringSocialConfigurer mySocialSecurityConfig() {return new SpringSocialConfigurer();}
}
重写用户名密码登录模块的MyUserDetailsServiceImpl实现类:
package com.cong.security.service.impl;import com.cong.security.core.social.MySocialUser;
import com.cong.security.entity.Account;
import com.cong.security.mapper.AccountMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.social.security.SocialUserDetails;
import org.springframework.social.security.SocialUserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import sun.security.util.Password;/*** @Description 自定义用户名密码登录* @Author single-聪* @Date 2020/1/7 18:42* @Version 1.0.1**/
@Slf4j
@Component
public class MyUserDetailsServiceImpl implements UserDetailsService, SocialUserDetailsService {@Autowiredprivate AccountMapper accountMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {return buildUser(username);}@Overridepublic SocialUserDetails loadUserByUserId(String id) throws UsernameNotFoundException {return buildUser(id);}private SocialUserDetails buildUser(String id) {Account account = null;// 如果id长度为11,即代表用户采用手机号方式登陆系统,手机号即为用户用户名,否则即为第三方账户登录if (id.length() == 11) {account = accountMapper.loginByPhone(id);} else {account = accountMapper.loginById(id);}if (account != null) {log.info("用户信息封装:[{}]", account);String password = account.getPassword();return new MySocialUser(account.getId(), id, password, account.getEnabled(), account.getExpired(), true,account.getLocked(), AuthorityUtils.commaSeparatedStringToAuthorityList(account.getRole()));} else {return new MySocialUser(id, id, null, false, false, false, false,AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));}}
}

其中MySocialUser为自定义类继承SocialUser:
SocialUser中没有UserId属性,提供的getUserId方法实际返回值为this.username,在本系统中对应用户手机号,但是手机号可以更换,所以此方式不符合本系统逻辑。

package com.cong.security.core.social;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.social.security.SocialUser;public class MySocialUser extends SocialUser {public MySocialUser(String id, String username, String password, boolean enabled, boolean accountNonExpired,boolean credentialsNonExpired, boolean accountNonLocked,Collection<? extends GrantedAuthority> authorities) {super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);this.id = id;}private static final long serialVersionUID = 1L;private String id;public String getId() { return id; }public void setId(String id) { this.id = id; }
}
定义三方登录常量:


配置使系统使用当前配置参数:

package com.cong.security.core.social;import javax.sql.DataSource;
import com.cong.security.core.properties.QQProperties;
import com.cong.security.core.properties.SecurityProperties;
import com.cong.security.core.social.qq.connect.QQConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.social.config.annotation.SocialConfigurerAdapter;
import org.springframework.social.connect.ConnectionFactory;/*** 如果系统中没有配置QQ的appId以及appSecret,此配置不起作用** @author single-聪*/
@Configuration
@ConditionalOnProperty(prefix = "my.security.social.qq", name = "app-id")
public class QQAutoConfig extends SocialConfigurerAdapter {@Autowiredprivate DataSource dataSource;@Autowiredprivate SecurityProperties securityProperties;protected ConnectionFactory<?> createConnectionFactory() {QQProperties qqConfig = securityProperties.getSocial().getQq();// 创建连接工厂,初始化参数return new QQConnectionFactory(securityProperties.getSocial().getQq().getProviderId(), securityProperties.getSocial().getQq().getAppId(), securityProperties.getSocial().getQq().getAppSecret());}
}

yml配置文件添加配置(自行去QQ互联申请账户)
将上述配置加入浏览器过滤器链:

编写登录页面:
<h2>社交登录</h2>
<a href="/auth/qq">QQ登录</a>

其中/authSocialAuthenticationFilter类中定义的常量DEFAULT_FILTER_PROCESSES_URL的值,/qq是QQProperties中定义的providerId值。
进入登录页面点击QQ登录链接应该会跳转到以下页面:

注意,如果报错/auth/qq接口权限不足,跳转到自定义授权接口时,大概率是SpringBoot升级到2.X以上版本的问题,具体解决办法查看文章:SpringBoot2.X版本SocialAutoConfigurerAdapter缺失造成第三方登录接口授权失败

还有两个问题需要解决:

  • 域名配置
    参考文章域名解析

  • href参数可配置
    自定义MySpringSocialConfigurer:

    package com.cong.security.core.social;import org.springframework.social.security.SocialAuthenticationFilter;
    import org.springframework.social.security.SpringSocialConfigurer;public class MySpringSocialConfigurer extends SpringSocialConfigurer {// 拦截请求private String filterProcessesUrl;//object---需要放到过滤器链上的Filter@Overrideprotected <T> T postProcess(T object) {SocialAuthenticationFilter filter = (SocialAuthenticationFilter) super.postProcess(object);filter.setFilterProcessesUrl(filterProcessesUrl);return (T) filter;}// 构造函数设置拦截请求public MySpringSocialConfigurer(String filterProcessesUrl) {this.filterProcessesUrl = filterProcessesUrl;}
    }
    

    修改SocialConfig配置:

    package com.cong.security.core.social;import javax.sql.DataSource;
    import com.cong.security.core.properties.SecurityProperties;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.crypto.encrypt.Encryptors;
    import org.springframework.social.UserIdSource;
    import org.springframework.social.config.annotation.EnableSocial;
    import org.springframework.social.config.annotation.SocialConfigurerAdapter;
    import org.springframework.social.connect.ConnectionFactoryLocator;
    import org.springframework.social.connect.UsersConnectionRepository;
    import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository;
    import org.springframework.social.security.AuthenticationNameUserIdSource;
    import org.springframework.social.security.SpringSocialConfigurer;/*** 社交登陆的适配器*/
    @EnableSocial
    @Configuration
    public class SocialConfig extends SocialConfigurerAdapter {@Autowiredprivate DataSource dataSource;@Overridepublic UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {/*** dataSource---数据源<br>* connectionFactoryLocator---连接工厂,辨别<br>* Encryptors.noOpText()---暂时采用不加密的方式,方便数据库查看<br>*/// TODO 数据存储加密方式JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource,connectionFactoryLocator, Encryptors.noOpText());// 设置表名前缀,数据库可以对表加前缀,需要注意windows和Linux系统的数据库区表名分大小写,如果报错找不到当前表,可以查看是否为此种情况// repository.setTablePrefix("my_");return repository;}@Autowiredprivate SecurityProperties securityProperties;// 过滤器链@Beanpublic SpringSocialConfigurer mySocialSecurityConfig() {return new MySpringSocialConfigurer(securityProperties.getSocial().getFilterProcessesUrl());}@Overridepublic UserIdSource getUserIdSource() {return new AuthenticationNameUserIdSource();}
    }
    

    最后配置参数使QQ登录的链接值为QQ互联设置的参数后缀。(尽量为二级,三方登录还有微信支付宝微博等)
    启动项目,点击QQ登录即可跳转到正确的页面,使用QQ扫描即可显示你在QQ互联申请的应用信息。点击确认后端接收到的请求时signin,在SocialAuthenticationFilter类中DEFAULT_FAILURE_URL值,未授权,最终跳转到授权接口。
    造成上述原因是:
    OAuth2AuthenticationService在去换取令牌时获取到的返回值是text/html模式,但是在构建与提供者进行API通信的RestTemplate时未添加text/html类型的转换器

    流程图

    解决上述问题:
    自定义QQOAuth2Template:

    package com.cong.security.core.social.qq.connect;import java.nio.charset.Charset;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.http.converter.StringHttpMessageConverter;
    import org.springframework.social.oauth2.AccessGrant;
    import org.springframework.social.oauth2.OAuth2Template;
    import org.springframework.util.MultiValueMap;
    import org.springframework.web.client.RestTemplate;
    import lombok.extern.slf4j.Slf4j;@Slf4j
    public class QQOAuth2Template extends OAuth2Template {public QQOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {// clientId和clientSecret对应APP的appId以及appSecretsuper(clientId, clientSecret, authorizeUrl, accessTokenUrl);// 默认是false,必须设置为true才会设置clientId以及clientSecretsetUseParametersForClientAuthentication(true);}/*** AccessGrant---访问令牌信息的封装*/@Overrideprotected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {// 返回的不是json数据,返回的是字符串,需要对字符串切割,截取数据String responseStr = getRestTemplate().postForObject(accessTokenUrl, parameters, String.class);log.info("获取accessToken的响应:[{}]", responseStr);String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(responseStr, "&");// 令牌String accessToken = StringUtils.substringAfterLast(items[0], "=");// 过期时间Long expiresIn = new Long(StringUtils.substringAfterLast(items[1], "="));// 刷新令牌String refreshToken = StringUtils.substringAfterLast(items[2], "=");return new AccessGrant(accessToken, null, refreshToken, expiresIn);}@Overrideprotected RestTemplate createRestTemplate() {// 拿到父类的创建结果,此时父类的三种转换器已经添加完成已经完成RestTemplate restTemplate = super.createRestTemplate();// 添加新的转换器,处理text/html类型restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));return restTemplate;}
    }
    

    QQServiceProvider的构造方法new QQOAuth2Template替换OAuth2Template。
    此时再次启动项目扫码授权可以获取到当前QQ信息,路径跳转到signup上。

处理QQ注册

  • 跳转到signup的原因是:

    查询不到userId,抛出BadCredentialsException异常,SocialAuthenticationFilter捕获到BadCredentialsException异常之后跳转到注册页:

    signupUrl的默认值为/signup

    解决上述问题
    在BrowserProperties设置默认注册页属性private String signUpUrl = "/regist.html";
    yml文件进行配置。

    配置BrowserSecurityConfig对自定义的注册页面授权。
    配置使SocialAuthenticationFilter使用自定义的注册页面跳转(SocialConfig.java):

    默认的注册页代码:

    <!DOCTYPE html>
    <html lang="en">
    <head><meta charset="UTF-8"><title>默认注册页面</title>
    </head>
    <body>
    <h3>注册页面</h3>
    <form action="/user/bind" method="post"><table><tr><td>手机号</td><td><input type="text" name="phone"></td></tr><tr><td>验证码</td><td><input type="password" name="code"></td></tr><tr><td><button type="submit">登录</button></td></tr></table>
    </form>
    </body>
    </html>
    
  • 用户绑定逻辑是:

    • 输入手机号,发送短信验证码(目前写死为手机号后六位,实际中发送在验证码校验接口校验即可)
    • 点击登录,如果用户不存在,那么为用户根据手机号创建默认账号,并和Session会话中的QQ信息绑定,加入UserConnection表,如果用户存在,那么直接绑定。(目前大多数应用都是短信验证码登录默认注册,没几个用户会记得住那么多用户名密码,所以基本可以省略设置密码步骤,但是如果设置密码需要注意密码加密方式与用户名密码登录接口加密方式一致。)

    编写表单提交的user/bind接口(安全配置中加入本接口权限)

    package com.cong.security.controller;import com.cong.security.service.AccountService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import javax.servlet.http.HttpServletRequest;@Slf4j
    @RestController
    @RequestMapping("user")
    public class UserController {@Autowiredprivate AccountService accountService;@RequestMapping("bind")public String bind(HttpServletRequest request) {log.info("执行用户绑定操作,获取用户手机号即短信校验码进行校验");String phone = request.getParameter("phone");// 手机号String code = request.getParameter("code");// 验证码// 验短信验证码返回该手机号对应的用户标识String userId = accountService.testSms(phone, code);// TODO 将系统userId和第三方账号进行绑定return userId;}@RequestMapping("me")public Object me(@AuthenticationPrincipal  UserDetails user) {return user;}
    }
    
  • 使用Spring工具类ProviderSignInUtils:
    在SocialConfig中添加如下配置:

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

    1、在注册过程中拿到Spring Social信息
    SocialUser为自定义类,存储第三方信息

    @RequestMapping("social/user")
    public SocialUser getSocialUserInfo(HttpServletRequest request) {SocialUser socialUser = new SocialUser();Connection connection = providerSignInUtils.getConnectionFromSession(new ServletWebRequest(request));socialUser.setProviderId(connection.getKey().getProviderId());socialUser.setProviderUserId(connection.getKey().getProviderUserId());socialUser.setNickName(connection.getDisplayName());socialUser.setHead(connection.getImageUrl());return socialUser;
    }
    

    2、将业务系统userId传给Spring Social进行第三方账号绑定:

    @Autowired
    private AccountService accountService;@Autowired
    private ProviderSignInUtils providerSignInUtils;@RequestMapping("bind")
    public String bind(HttpServletRequest request) {log.info("执行用户绑定操作,获取用户手机号即短信校验码进行校验");String phone = request.getParameter("phone");// 手机号String code = request.getParameter("code");// 验证码// 验验证码(隐式注册,只要验证码正确就会返回唯一用户标识)String userId = accountService.testSms(phone, code);// TODO 将系统userId和第三方账号进行绑定providerSignInUtils.doPostSignUp(userId, new ServletWebRequest(request));return userId;
    }
    

    此时启动项目,QQ登录会跳转到注册页,输入手机号及短信即可实现账户绑定,此时回到登录页再次执行QQ登录,理论上而言会跳转到默认展示页,但是如果依然跳回注册页,按照前面的分析问题出现在SocialAuthenticationProvider中,断点调试:

    我们使用的三方登录数据存储在数据库中,所以使用的ConnectionRepository应该是JdbcUsersConnectionRepository,解决:
    在QQAutoConfig配置中重写getUsersConnectionRepository方法:

    @Override
    public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {// 将微信登陆的实现类设置为jdbc实现JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource,connectionFactoryLocator, Encryptors.noOpText());return repository;
    }
    

    重新注册,第一次输入手机号绑定,第二次使用QQ登录即跳转到系统默认展示页。

隐式注册

默认注册账户,和手机号验证码默认注册逻辑一致(但是目前90%应用需要实名认证,个人认为隐式注册注定要被抛弃)
实现ConnectionSignUp接口:

package com.cong.security.service.impl;import com.cong.security.entity.Account;
import com.cong.security.mapper.AccountMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionSignUp;
import org.springframework.stereotype.Component;/*** @Description TODO* @Author single-聪* @Date 2020/1/19 19:53* @Version 1.0.1**/
@Slf4j
@Component
public class MyConnectionSignUp implements ConnectionSignUp {@Autowiredprivate AccountMapper accountMapper;@Overridepublic String execute(Connection<?> connection) {// TODO 根据社交账户信息默认创建用户log.info("第三方数据来源为:[{}]", connection.getKey().getProviderId());log.info("第三方数据openId为:[{}]", connection.getKey().getProviderUserId());log.info("第三方数据昵称为:[{}]", connection.getDisplayName());// 昵称,可以当做本系统用户昵称,但是不能作为登录用户名log.info("第三方数据头像路径为:[{}]", connection.getImageUrl());// 需要将该图片传至自身服务器// 1、构建系统用户信息(使用三方账户数据)Account account = new Account("18812345678");// 2、加入本系统表中accountMapper.insert(account);// 3、返回创建的用户唯一标识(UUID)return account.getId();}
}

QQAutoConfig中添加配置:

package com.cong.security.core.social;import javax.sql.DataSource;
import com.cong.security.core.properties.SecurityProperties;
import com.cong.security.core.social.qq.connect.QQConnectionFactory;
import com.cong.security.core.social.support.SocialAutoConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.social.connect.ConnectionFactory;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.ConnectionSignUp;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository;/*** 如果系统中没有配置QQ的appId以及appSecret,此配置不起作用** @author single-聪*/
@Configuration
@ConditionalOnProperty(prefix = "my.security.social.qq", name = "app-id")
public class QQAutoConfig extends SocialAutoConfigurerAdapter {@Autowiredprivate DataSource dataSource;@Autowiredprivate SecurityProperties securityProperties;@Autowired(required = false)private ConnectionSignUp connectionSignUp;protected ConnectionFactory<?> createConnectionFactory() {// 创建连接工厂,初始化参数return new QQConnectionFactory(securityProperties.getSocial().getQq().getProviderId(), securityProperties.getSocial().getQq().getAppId(), securityProperties.getSocial().getQq().getAppSecret());}@Overridepublic UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource,connectionFactoryLocator, Encryptors.noOpText());if (connectionSignUp != null) {repository.setConnectionSignUp(connectionSignUp);}return repository;}
}

至此,关于QQ三方登录笔记完成。

SpringSocial之QQ登录相关推荐

  1. SpringSocial 开发 QQ 登录

    本篇文章带着大家在自己的系统中集成 QQ 第三方登录 不管是做什么操作,官方文档与手册绝对是最靠谱的.所以我们先来看一下 QQ 互联官网的文档 QQ 互联介绍 QQ 互联官网 在 QQ 互联官网上,我 ...

  2. 使用SpringSocial开发QQ登录

    ⒈编写QQ用户对应的数据结构 1 package cn.coreqi.social.qq.entities; 2 3 /** 4 * 封装QQ的用户信息 5 */ 6 public class QQU ...

  3. SpringSecurity和SpringSocial实现QQ登录

    siki学院课程: http://www.sikiedu.com/course/366 (SpringSecurity和SpringSocial认证授权) http://www.sikiedu.com ...

  4. SpringSocial之微信登录

    编写顺序同SpringSocial之QQ登录 创建用户信息类WeiXinUserInfo: package com.cong.security.core.social.weixin.api;impor ...

  5. spring-security学习(七)——QQ登录(上篇)

    文章目录 OAuth简介 OAuth协议要解决的问题 spring-social简介 QQ登录(实例) 1.实现Api与ServiceProvider 2.获取到用户信息之后的处理 3.一些需要的配置 ...

  6. java项目关联Q登陆,前后端分离项目 — SpringSocial 社交账号登录与注册

    1.前言 今天我们就来讲解下最后一篇如何使用SpringSocial来处理类似微信.QQ社交账号登录自己的平台,也就是大家说的第三方登录,获取社交账户所在平台的用户信息,与自己平台信息做个绑定的操作, ...

  7. Spring Boot整合Security系列步骤及问题排查(十一)—— 集成QQ登录

    工具类准备: Repository: Connection: ConnectionFactory(ServiceProvider.ApiAdapter): ServiceProvider(OAuth2 ...

  8. android qq第三方登录,Android调用第三方QQ登录代码分享

    本文为大家分享了调用QQ登录的相关代码,希望对大家有帮助,减少项目开发的时间,具体内容如下 1.去QQ开放平台注册帐号(http://open.qq.com/),为应用申请QQ的APP_ID , 并下 ...

  9. PHP 接入(第三方登录)QQ 登录 OAuth2.0 过程中遇到的坑

    前言 绝大多数网站都集成了第三方登录,降低了注册门槛,增强了用户体验.最近看了看 QQ 互联上 QQ 登录的接口文档.接入 QQ 登录的一般流程是这样的:先申请开发者 -> 然后创建应用(拿到一 ...

最新文章

  1. ICML 2021:矩阵乘法无需相乘,速度提升100倍,MIT开源最新近似算法
  2. Qt 不再使用 LGPLv2.1 授权
  3. 归并排序树状数组求逆序数
  4. java 页面传输中文乱码解决方式
  5. GRIDVIEW控件删除数据前如何给予客户端提示
  6. 【数理知识】《矩阵论》方保镕老师-第6章-广义逆矩阵及其应用
  7. times(NULL) Segmentation fault
  8. 回顾 | 进击吧! Blazor!系列
  9. pass样本量_年度质量回顾-样本量
  10. 《ArcGIS Runtime SDK for Android开发笔记》
  11. bzoj 1202: [HNOI2005]狡猾的商人(带权并查集)
  12. 打造最快的Hash表
  13. 开发、运维过程中解决问题的通用步骤
  14. 目录_视觉SLAM十四讲_0
  15. String.format()方法使用
  16. SQL Server 2019重新安装失败的处理方法
  17. java 软件流程图使用什么_流程图怎么画,教你正确使用流程图模板
  18. JavaWeb登录案例带验证码(mysql+servlet+jsp+idea)
  19. osm 搭建离线地图_搭建开源地图服务 - 利用OSMGIS和iD
  20. 小水管也要有尊严 网络限速优化实际案例

热门文章

  1. 使用John the ripper破解密码
  2. 江西省中职计算机简答题,江西省中等职业学校第八届技能竞赛节计算机类专业竞赛模拟试题(CAD、CAM软件应用)...
  3. 爱因斯坦犯了什么错?
  4. android面试(个人)简历
  5. vue3写个超级简单的音乐网站,保姆级教程
  6. 计算机清理垃圾代码,让你电脑快速开机清除系统垃圾运行批处理源代码
  7. java生成电子印章的方法
  8. 应用概率统计-第二章 离散型随机变量
  9. CentOS是什么服务器系统
  10. winscp连接nas root账户拒绝访问_树莓派 3B 结合 NextCloud PI 搭建皮米级 NAS 家庭储存...