Spring Social 开发QQ登录
程序员文章站
2022-05-05 15:16:50
...
可先参考
SpringSocial基本原理
Spring Social 认证流程源码详解
授权流程主要接口
代码结构
获取用户信息
Api(个性化第六步),实际上没有一个明确的接口,因为每一个服务提供商对于用户基本信息的调用都是有区别的。SpringSocial其实也提供了一个抽象类叫AbstractOauth2ApiBinding帮助我们快速开发第六步的实现。
public class QQImpl extends AbstractOAuth2ApiBinding implements QQ {
//获取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();
//获取openId
public QQImpl(String accessToken, String appId) {
super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);
this.appId = appId;
String url = String.format(URL_GET_OPENID, accessToken);
String result = getRestTemplate().getForObject(url, String.class);
System.out.println(result);
this.openId = StringUtils.substringBetween(result, "\"openid\":\"", "\"}");
}
/**
* 获取用户信息
* @see QQ#getUserInfo()
*/
@Override
public QQUserInfo getUserInfo() {
String url = String.format(URL_GET_USERINFO, appId, openId);
String result = getRestTemplate().getForObject(url, String.class);
System.out.println(result);
QQUserInfo userInfo = null;
try {
userInfo = objectMapper.readValue(result, QQUserInfo.class);
userInfo.setOpenId(openId);
return userInfo;
} catch (Exception e) {
throw new RuntimeException("获取用户信息失败", e);
}
}
}
实现Oauth2Operation接口
Oauth2Operation封装了授权流程的第一步到第五步,Spring提供了一个默认的实现叫Oauth2Template,这个类会帮助我们去完成Oauth协议的执行流程。这里自定义QQOAuth2Template,复写了OAuth2Template的一些方法,主要是为了适应QQ提供的api接口。
public class QQOAuth2Template extends OAuth2Template {
private Logger logger = LoggerFactory.getLogger(getClass());
/**
*
* @param clientId
* @param clientSecret
* @param authorizeUrl
* @param accessTokenUrl
*/
public QQOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
setUseParametersForClientAuthentication(true); //设置为true,使其携带clientId
}
/**
* 重写postForAccessGrant,因为qq获取令牌的api,返回的时字符串,不是map
* @param accessTokenUrl
* @param parameters
* @return
*/
@Override
protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {
String responseStr = getRestTemplate().postForObject(accessTokenUrl, parameters, String.class);
logger.info("获取accessToke的响应:" + 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);
}
/**
* 添加StringHttpMessageConverter,编码方式为UTF-8
* @return
*/
@Override
protected RestTemplate createRestTemplate() {
RestTemplate restTemplate = super.createRestTemplate();
restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
return restTemplate;
}
}
实现ServiceProvider接口
ServiceProvider接口,这个接口是服务提供商的一个抽象,针对每一个服务提供商(QQ、微信),都需要一个ServiceProvider接口的一个实现。
public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ> {
private String appId;
//获取授权码
private static final String URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize";
//获取accessToken
private static final String URL_ACCESS_TOKEN = "https://graph.qq.com/oauth2.0/token";
/**
*
* @param appId clientId
* @param appSecret clientSecret
*/
public QQServiceProvider(String appId, String appSecret) {
super(new QQOAuth2Template(appId, appSecret, URL_AUTHORIZE, URL_ACCESS_TOKEN));
this.appId = appId;
}
/*
* (non-Javadoc)
*
* @see
* org.springframework.social.oauth2.AbstractOAuth2ServiceProvider#getApi(java.
* lang.String)
*/
@Override
public QQ getApi(String accessToken) {
return new QQImpl(accessToken, appId);
}
}
实现ApiAdapter
ApiAdapter,将不同格式的用户信息转化为固定格式的Connection对象就是由ApiAdapter接口的实现来完成。转化成功之后就将Connection中封装进去一个用户信息
/**
* 适配器
* 把从qqApi获取的个性化信息配置到ConnectionValues中
* @author zhailiang
*
*/
public class QQAdapter implements ApiAdapter<QQ> {
@Override
public boolean test(QQ api) {
return true;
}
@Override
public void setConnectionValues(QQ api, ConnectionValues values) {
QQUserInfo userInfo = api.getUserInfo();
values.setDisplayName(userInfo.getNickname());
values.setImageUrl(userInfo.getFigureurl_qq_1());
values.setProfileUrl(null); //个人主页等
values.setProviderUserId(userInfo.getOpenId()); //
}
@Override
public UserProfile fetchUserProfile(QQ api) {
// TODO Auto-generated method stub
return null;
}
@Override
public void updateStatus(QQ api, String message) {
//do noting
}
}
实现ConnectionFactory接口
ConnectionFactory工程是为了创建Connection对象,Connection对象封装前六步执行完毕之后获取到的用户信息。
/**
* 实现QQConnectionFactory,
* 把QQServiceProvider,QQAdapter放入工厂中
*
*/
public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ> {
/**
*
* @param providerId 提供商的唯一表示
* @param appId
* @param appSecret
*/
public QQConnectionFactory(String providerId, String appId, String appSecret) {
super(providerId, new QQServiceProvider(appId, appSecret), new QQAdapter());
}
}
自动配置类
该类的目的是根据配置文件,决定是否需要注入QQConnectionFactory。
/**
* 注入 QQConnectionFactory
*/
@Configuration
@ConditionalOnProperty(prefix = "imooc.security.social.qq", name = "app-id") //当imooc.security.social.qq.app-id配置了,QQAutoConfig才生效
public class QQAutoConfig extends SocialAutoConfigurerAdapter {
@Autowired
private SecurityProperties securityProperties;
@Override
protected ConnectionFactory<?> createConnectionFactory() {
QQProperties qqConfig = securityProperties.getSocial().getQq();
return new QQConnectionFactory(qqConfig.getProviderId(), qqConfig.getAppId(), qqConfig.getAppSecret());
}
}
社交登录配置主类
该类中定义了JdbcUsersConnectionRepository 存储器,目的是将传过来的用户信息与我们系统中已经保存的用户信息去进行对应的。
并且配置类社交登录拦截的url,默认是/auth,和注册页的url,默认是/signUp
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private SecurityProperties securityProperties;
@Autowired(required = false)
private ConnectionSignUp connectionSignUp;
@Autowired(required = false)
private SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor;
//把用户信息存到数据库
@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource,
connectionFactoryLocator, Encryptors.noOpText());
repository.setTablePrefix("imooc_"); //存到imooc_userconnection表
if(connectionSignUp != null) {
repository.setConnectionSignUp(connectionSignUp);
}
return repository;
}
/**
* 社交登录配置类,供浏览器或app模块引入设计登录配置用。
* @return
*/
@Bean
public SpringSocialConfigurer imoocSocialSecurityConfig() {
String filterProcessesUrl = securityProperties.getSocial().getFilterProcessesUrl(); // 过滤的的url,默认为auth
ImoocSpringSocialConfigurer configurer = new ImoocSpringSocialConfigurer(filterProcessesUrl);
configurer.signupUrl(securityProperties.getBrowser().getSignUpUrl()); //注册跳转的url
//?
configurer.setSocialAuthenticationFilterPostProcessor(socialAuthenticationFilterPostProcessor);
return configurer;
}
/**
* 用来处理注册流程的工具类
*
* @param connectionFactoryLocator
* @return
*/
@Bean
public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator) {
return new ProviderSignInUtils(connectionFactoryLocator,
getUsersConnectionRepository(connectionFactoryLocator)) {
};
}
}
最后把SocialAuthenticationFilter添加到SpringSecurity过滤器链中
http.apply(validateCodeSecurityConfig)
.and()
.apply(smsCodeAuthenticationSecurityConfig)
.and()
.apply(imoocSocialSecurityConfig)