@@ -20,12 +20,53 @@ | |||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> | <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> | ||||
<java.version>1.8</java.version> | <java.version>1.8</java.version> | ||||
<spring.social.version>1.1.6.RELEASE</spring.social.version> | |||||
</properties> | </properties> | ||||
<dependencies> | <dependencies> | ||||
<dependency> | <dependency> | ||||
<groupId>org.springframework.boot</groupId> | <groupId>org.springframework.boot</groupId> | ||||
<artifactId>spring-boot-starter</artifactId> | |||||
<artifactId>spring-boot-starter-web</artifactId> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-jdbc</artifactId> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-security</artifactId> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.springframework.social</groupId> | |||||
<artifactId>spring-social-config</artifactId> | |||||
<version>${spring.social.version}</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.springframework.social</groupId> | |||||
<artifactId>spring-social-core</artifactId> | |||||
<version>${spring.social.version}</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.springframework.social</groupId> | |||||
<artifactId>spring-social-security</artifactId> | |||||
<version>${spring.social.version}</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.springframework.social</groupId> | |||||
<artifactId>spring-social-web</artifactId> | |||||
<version>${spring.social.version}</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-configuration-processor</artifactId> | |||||
<optional>true</optional> | |||||
</dependency> | </dependency> | ||||
<dependency> | <dependency> | ||||
@@ -33,6 +74,27 @@ | |||||
<artifactId>spring-boot-starter-test</artifactId> | <artifactId>spring-boot-starter-test</artifactId> | ||||
<scope>test</scope> | <scope>test</scope> | ||||
</dependency> | </dependency> | ||||
<dependency> | |||||
<groupId>mysql</groupId> | |||||
<artifactId>mysql-connector-java</artifactId> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.projectlombok</groupId> | |||||
<artifactId>lombok</artifactId> | |||||
<optional>true</optional> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.google.guava</groupId> | |||||
<artifactId>guava</artifactId> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>cn.hutool</groupId> | |||||
<artifactId>hutool-all</artifactId> | |||||
</dependency> | |||||
</dependencies> | </dependencies> | ||||
<build> | <build> | ||||
@@ -1,7 +1,9 @@ | |||||
package com.xkcoding.social; | package com.xkcoding.social; | ||||
import com.xkcoding.social.config.SocialProperties; | |||||
import org.springframework.boot.SpringApplication; | import org.springframework.boot.SpringApplication; | ||||
import org.springframework.boot.autoconfigure.SpringBootApplication; | import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||
import org.springframework.boot.context.properties.EnableConfigurationProperties; | |||||
/** | /** | ||||
* <p> | * <p> | ||||
@@ -0,0 +1,32 @@ | |||||
package com.xkcoding.social.config; | |||||
import org.springframework.social.security.SocialAuthenticationFilter; | |||||
import org.springframework.social.security.SpringSocialConfigurer; | |||||
/** | |||||
* <p> | |||||
* 继承默认的社交登录配置,加入自定义的后处理逻辑 | |||||
* </p> | |||||
* | |||||
* @package: com.xkcoding.social.config | |||||
* @description: 继承默认的社交登录配置,加入自定义的后处理逻辑 | |||||
* @author: yangkai.shen | |||||
* @date: Created in 2019-02-21 14:01 | |||||
* @copyright: Copyright (c) 2019 | |||||
* @version: V1.0 | |||||
* @modified: yangkai.shen | |||||
*/ | |||||
public class DemoSocialConfigure extends SpringSocialConfigurer { | |||||
private String filterProcessUrl; | |||||
public DemoSocialConfigure(String filterProcessUrl) { | |||||
this.filterProcessUrl = filterProcessUrl; | |||||
} | |||||
@Override | |||||
protected <T> T postProcess(T object) { | |||||
SocialAuthenticationFilter socialAuthenticationFilter = (SocialAuthenticationFilter) super.postProcess(object); | |||||
socialAuthenticationFilter.setFilterProcessesUrl(filterProcessUrl); | |||||
return (T) socialAuthenticationFilter; | |||||
} | |||||
} |
@@ -0,0 +1,34 @@ | |||||
package com.xkcoding.social.config; | |||||
import org.springframework.beans.factory.annotation.Autowired; | |||||
import org.springframework.security.core.authority.AuthorityUtils; | |||||
import org.springframework.security.core.userdetails.UsernameNotFoundException; | |||||
import org.springframework.security.crypto.password.PasswordEncoder; | |||||
import org.springframework.social.security.SocialUser; | |||||
import org.springframework.social.security.SocialUserDetails; | |||||
import org.springframework.social.security.SocialUserDetailsService; | |||||
import org.springframework.stereotype.Service; | |||||
/** | |||||
* <p> | |||||
* 根据userId获取用户信息 | |||||
* </p> | |||||
* | |||||
* @package: com.xkcoding.social.config | |||||
* @description: 根据userId获取用户信息 | |||||
* @author: yangkai.shen | |||||
* @date: Created in 2019-02-21 14:41 | |||||
* @copyright: Copyright (c) 2019 | |||||
* @version: V1.0 | |||||
* @modified: yangkai.shen | |||||
*/ | |||||
@Service | |||||
public class DemoUserDetailService implements SocialUserDetailsService { | |||||
@Autowired | |||||
private PasswordEncoder passwordEncoder; | |||||
@Override | |||||
public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException { | |||||
return new SocialUser(userId, passwordEncoder.encode("123456"), AuthorityUtils.commaSeparatedStringToAuthorityList("xxx")); | |||||
} | |||||
} |
@@ -0,0 +1,34 @@ | |||||
package com.xkcoding.social.config; | |||||
import lombok.Data; | |||||
/** | |||||
* <p> | |||||
* QQ 配置 | |||||
* </p> | |||||
* | |||||
* @package: com.xkcoding.social.config | |||||
* @description: QQ 配置 | |||||
* @author: yangkai.shen | |||||
* @date: Created in 2019-02-21 14:20 | |||||
* @copyright: Copyright (c) 2019 | |||||
* @version: V1.0 | |||||
* @modified: yangkai.shen | |||||
*/ | |||||
@Data | |||||
public class QQProperties { | |||||
/** | |||||
* 第三方应用标识 | |||||
*/ | |||||
private String providerId = "qq"; | |||||
/** | |||||
* clientId | |||||
*/ | |||||
private String clientId; | |||||
/** | |||||
* clientSecret | |||||
*/ | |||||
private String clientSecret; | |||||
} |
@@ -0,0 +1,67 @@ | |||||
package com.xkcoding.social.config; | |||||
import org.springframework.beans.factory.annotation.Autowired; | |||||
import org.springframework.boot.context.properties.EnableConfigurationProperties; | |||||
import org.springframework.context.annotation.Bean; | |||||
import org.springframework.context.annotation.Configuration; | |||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | |||||
import org.springframework.security.crypto.encrypt.Encryptors; | |||||
import org.springframework.security.crypto.password.PasswordEncoder; | |||||
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; | |||||
import javax.sql.DataSource; | |||||
/** | |||||
* <p> | |||||
* 社交登录配置 | |||||
* </p> | |||||
* | |||||
* @package: com.xkcoding.social.config | |||||
* @description: 社交登录配置 | |||||
* @author: yangkai.shen | |||||
* @date: Created in 2019-02-21 13:52 | |||||
* @copyright: Copyright (c) 2019 | |||||
* @version: V1.0 | |||||
* @modified: yangkai.shen | |||||
*/ | |||||
@Configuration | |||||
@EnableSocial | |||||
@EnableConfigurationProperties({SocialProperties.class}) | |||||
public class SocialConfig extends SocialConfigurerAdapter { | |||||
@Autowired | |||||
private DataSource dataSource; | |||||
@Autowired | |||||
private SocialProperties socialProperties; | |||||
@Override | |||||
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) { | |||||
JdbcUsersConnectionRepository jdbcUsersConnectionRepository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors | |||||
.noOpText()); | |||||
jdbcUsersConnectionRepository.setTablePrefix("demo_"); | |||||
return jdbcUsersConnectionRepository; | |||||
} | |||||
@Bean | |||||
public SpringSocialConfigurer springSocialConfigurer() { | |||||
DemoSocialConfigure demoSocialConfigure = new DemoSocialConfigure(socialProperties.getFilterProcessUrl()); | |||||
return demoSocialConfigure; | |||||
} | |||||
@Bean | |||||
public PasswordEncoder passwordEncoder() { | |||||
return new BCryptPasswordEncoder(); | |||||
} | |||||
@Override | |||||
public UserIdSource getUserIdSource() { | |||||
return new AuthenticationNameUserIdSource(); | |||||
} | |||||
} |
@@ -0,0 +1,25 @@ | |||||
package com.xkcoding.social.config; | |||||
import lombok.Data; | |||||
import org.springframework.boot.context.properties.ConfigurationProperties; | |||||
/** | |||||
* <p> | |||||
* 社交登录配置类 | |||||
* </p> | |||||
* | |||||
* @package: com.xkcoding.social.config | |||||
* @description: 社交登录配置类 | |||||
* @author: yangkai.shen | |||||
* @date: Created in 2019-02-21 14:12 | |||||
* @copyright: Copyright (c) 2019 | |||||
* @version: V1.0 | |||||
* @modified: yangkai.shen | |||||
*/ | |||||
@Data | |||||
@ConfigurationProperties(prefix = "demo.social") | |||||
public class SocialProperties { | |||||
private String filterProcessUrl = "/auth"; | |||||
private QQProperties qq = new QQProperties(); | |||||
} |
@@ -0,0 +1,24 @@ | |||||
package com.xkcoding.social.qq.api; | |||||
/** | |||||
* <p> | |||||
* 获取QQ用户信息接口 | |||||
* </p> | |||||
* | |||||
* @package: com.xkcoding.social.qq.api | |||||
* @description: 获取QQ用户信息接口 | |||||
* @author: yangkai.shen | |||||
* @date: Created in 2019-02-21 10:52 | |||||
* @copyright: Copyright (c) 2019 | |||||
* @version: V1.0 | |||||
* @modified: yangkai.shen | |||||
*/ | |||||
public interface QQ { | |||||
/** | |||||
* 获取QQ用户信息 | |||||
* @return QQ用户信息 | |||||
*/ | |||||
QQUserInfo getUserInfo(); | |||||
} |
@@ -0,0 +1,82 @@ | |||||
package com.xkcoding.social.qq.api; | |||||
import lombok.Data; | |||||
/** | |||||
* <p> | |||||
* QQ用户信息 | |||||
* </p> | |||||
* | |||||
* @package: com.xkcoding.social.qq.api | |||||
* @description: QQ用户信息 | |||||
* @author: yangkai.shen | |||||
* @date: Created in 2019-02-21 10:52 | |||||
* @copyright: Copyright (c) 2019 | |||||
* @version: V1.0 | |||||
* @modified: yangkai.shen | |||||
*/ | |||||
@Data | |||||
public class QQUserInfo { | |||||
/** | |||||
* QQ唯一标识 | |||||
*/ | |||||
private String openId; | |||||
/** | |||||
* 返回码 | |||||
*/ | |||||
private String ret; | |||||
/** | |||||
* 如果ret<0,会有相应的错误信息提示,返回数据全部用UTF-8编码。 | |||||
*/ | |||||
private String msg; | |||||
/** | |||||
* 用户在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的100x100的头像,但40x40像素则是一定会有。 | |||||
*/ | |||||
private String figureurl_qq_2; | |||||
/** | |||||
* 性别。 如果获取不到则默认返回"男" | |||||
*/ | |||||
private String gender; | |||||
/** | |||||
* 是否黄钻 | |||||
*/ | |||||
private String is_yellow_vip; | |||||
/** | |||||
* 是否会员 | |||||
*/ | |||||
private String vip; | |||||
/** | |||||
* 黄钻等级 | |||||
*/ | |||||
private String yellow_vip_level; | |||||
/** | |||||
* 会员等级 | |||||
*/ | |||||
private String level; | |||||
/** | |||||
* 是否年费黄钻 | |||||
*/ | |||||
private String is_yellow_year_vip; | |||||
} |
@@ -0,0 +1,78 @@ | |||||
package com.xkcoding.social.qq.api.impl; | |||||
import cn.hutool.core.util.StrUtil; | |||||
import cn.hutool.json.JSONUtil; | |||||
import com.xkcoding.social.qq.api.QQ; | |||||
import com.xkcoding.social.qq.api.QQUserInfo; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import org.springframework.social.oauth2.AbstractOAuth2ApiBinding; | |||||
import org.springframework.social.oauth2.TokenStrategy; | |||||
import org.springframework.util.MultiValueMap; | |||||
/** | |||||
* <p> | |||||
* 获取QQ用户信息接口实现 | |||||
* </p> | |||||
* | |||||
* @package: com.xkcoding.social.qq.api.impl | |||||
* @description: 获取QQ用户信息接口实现 | |||||
* @author: yangkai.shen | |||||
* @date: Created in 2019-02-21 10:57 | |||||
* @copyright: Copyright (c) 2019 | |||||
* @version: V1.0 | |||||
* @modified: yangkai.shen | |||||
*/ | |||||
@Slf4j | |||||
public class QQImpl extends AbstractOAuth2ApiBinding implements QQ { | |||||
private String clientId; | |||||
private String openId; | |||||
/** | |||||
* 获取 openId,文档:http://wiki.connect.qq.com/%E8%8E%B7%E5%8F%96%E7%94%A8%E6%88%B7openid_oauth2-0 | |||||
*/ | |||||
private static final String URL_FOR_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s"; | |||||
/** | |||||
* 获取用户信息 URL,文档:http://wiki.connect.qq.com/get_user_info | |||||
*/ | |||||
private static final String URL_FOR_USER_INFO = "https://graph.qq.com/user/get_user_info?oauth_consumer_key=%sopenid=%s"; | |||||
/** | |||||
* {@link TokenStrategy#ACCESS_TOKEN_PARAMETER} 将 token 封装为参数传递,key为 access_token <br/> | |||||
* {@link TokenStrategy#OAUTH_TOKEN_PARAMETER} 将 token 封装为参数传递,key为 oauth_token <br/> | |||||
* {@link TokenStrategy#AUTHORIZATION_HEADER} 将 token 封装为header传递,key为 Authorization,value为 "Bearer token" 格式<br/> | |||||
* | |||||
* @param accessToken token 由{@link org.springframework.social.oauth2.OAuth2Template#exchangeForAccess(String, String, MultiValueMap)} 返回 | |||||
* @param clientId 从配置文件获取 | |||||
*/ | |||||
public QQImpl(String accessToken, String clientId) { | |||||
super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER); | |||||
this.clientId = clientId; | |||||
// 根据 token 获取 openId | |||||
String url = String.format(URL_FOR_OPENID, accessToken); | |||||
String result = getRestTemplate().getForObject(url, String.class); | |||||
// result 格式:callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} ); 文档:http://wiki.connect.qq.com/%E8%8E%B7%E5%8F%96%E7%94%A8%E6%88%B7openid_oauth2-0 | |||||
log.error("【QQImpl】url = {} ,result = {}", url, result); | |||||
this.openId = StrUtil.subBetween(result, "\"openid\":\"", "\"}"); | |||||
} | |||||
/** | |||||
* 获取QQ用户信息 | |||||
* | |||||
* @return QQ用户信息 | |||||
*/ | |||||
@Override | |||||
public QQUserInfo getUserInfo() { | |||||
String url = String.format(URL_FOR_USER_INFO, clientId, openId); | |||||
String result = getRestTemplate().getForObject(url, String.class); | |||||
log.error("【QQImpl】url = {} ,result = {}", url, result); | |||||
QQUserInfo userInfo = JSONUtil.toBean(result, QQUserInfo.class, true); | |||||
userInfo.setOpenId(openId); | |||||
return userInfo; | |||||
} | |||||
} |
@@ -0,0 +1,39 @@ | |||||
package com.xkcoding.social.qq.config; | |||||
import com.xkcoding.social.config.QQProperties; | |||||
import com.xkcoding.social.config.SocialProperties; | |||||
import com.xkcoding.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.core.env.Environment; | |||||
import org.springframework.social.config.annotation.ConnectionFactoryConfigurer; | |||||
import org.springframework.social.config.annotation.SocialConfigurerAdapter; | |||||
/** | |||||
* <p> | |||||
* QQ社交登录自动装配类 | |||||
* </p> | |||||
* | |||||
* @package: com.xkcoding.social.qq.config | |||||
* @description: QQ社交登录自动装配类 | |||||
* @author: yangkai.shen | |||||
* @date: Created in 2019-02-21 14:08 | |||||
* @copyright: Copyright (c) 2019 | |||||
* @version: V1.0 | |||||
* @modified: yangkai.shen | |||||
*/ | |||||
@Configuration | |||||
@ConditionalOnProperty(prefix = "demo.social.qq", name = "clientId") | |||||
public class QQAutoConfig extends SocialConfigurerAdapter { | |||||
@Autowired | |||||
private SocialProperties socialProperties; | |||||
@Override | |||||
public void addConnectionFactories(ConnectionFactoryConfigurer connectionFactoryConfigurer, Environment environment) { | |||||
QQProperties qq = socialProperties.getQq(); | |||||
QQConnectionFactory qqConnectionFactory = new QQConnectionFactory(qq.getProviderId(), qq.getClientId(), qq.getClientSecret()); | |||||
connectionFactoryConfigurer.addConnectionFactory(qqConnectionFactory); | |||||
super.addConnectionFactories(connectionFactoryConfigurer, environment); | |||||
} | |||||
} |
@@ -0,0 +1,59 @@ | |||||
package com.xkcoding.social.qq.connect; | |||||
import com.xkcoding.social.qq.api.QQ; | |||||
import com.xkcoding.social.qq.api.QQUserInfo; | |||||
import org.springframework.social.connect.ApiAdapter; | |||||
import org.springframework.social.connect.ConnectionValues; | |||||
import org.springframework.social.connect.UserProfile; | |||||
/** | |||||
* <p> | |||||
* QQ 用户适配器 | |||||
* </p> | |||||
* | |||||
* @package: com.xkcoding.social.qq.connect | |||||
* @description: QQ 用户适配器 | |||||
* @author: yangkai.shen | |||||
* @date: Created in 2019-02-21 13:46 | |||||
* @copyright: Copyright (c) 2019 | |||||
* @version: V1.0 | |||||
* @modified: yangkai.shen | |||||
*/ | |||||
public class QQAdapter implements ApiAdapter<QQ> { | |||||
/** | |||||
* 测试api是否可用,默认返回true | |||||
*/ | |||||
@Override | |||||
public boolean test(QQ api) { | |||||
return true; | |||||
} | |||||
/** | |||||
* 将自己实现的获取用户信息,转换为spring social返回的用户信息 | |||||
*/ | |||||
@Override | |||||
public void setConnectionValues(QQ api, ConnectionValues values) { | |||||
QQUserInfo userInfo = api.getUserInfo(); | |||||
values.setDisplayName(userInfo.getNickname()); | |||||
values.setImageUrl(userInfo.getFigureurl_qq_1()); | |||||
values.setProviderUserId(userInfo.getOpenId()); | |||||
values.setProfileUrl(null); | |||||
} | |||||
/** | |||||
* 用户主页,QQ默认没有用户主页 | |||||
*/ | |||||
@Override | |||||
public UserProfile fetchUserProfile(QQ api) { | |||||
return UserProfile.EMPTY; | |||||
} | |||||
/** | |||||
* 更新用户状态,空实现 | |||||
*/ | |||||
@Override | |||||
public void updateStatus(QQ api, String message) { | |||||
// do nothing | |||||
} | |||||
} |
@@ -0,0 +1,24 @@ | |||||
package com.xkcoding.social.qq.connect; | |||||
import com.xkcoding.social.qq.api.QQ; | |||||
import org.springframework.social.connect.support.OAuth2ConnectionFactory; | |||||
/** | |||||
* <p> | |||||
* QQ 连接工厂 | |||||
* </p> | |||||
* | |||||
* @package: com.xkcoding.social.qq.connect | |||||
* @description: QQ 连接工厂 | |||||
* @author: yangkai.shen | |||||
* @date: Created in 2019-02-21 13:50 | |||||
* @copyright: Copyright (c) 2019 | |||||
* @version: V1.0 | |||||
* @modified: yangkai.shen | |||||
*/ | |||||
public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ> { | |||||
public QQConnectionFactory(String providerId, String clientId, String clientSecret) { | |||||
super(providerId, new QQServiceProvider(clientId, clientSecret), new QQAdapter()); | |||||
} | |||||
} |
@@ -0,0 +1,68 @@ | |||||
package com.xkcoding.social.qq.connect; | |||||
import com.google.common.base.Charsets; | |||||
import com.google.common.base.Splitter; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
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 java.util.Map; | |||||
/** | |||||
* <p> | |||||
* ①获取授权码code ②根据code换取令牌accessToken | |||||
* </p> | |||||
* | |||||
* @package: com.xkcoding.social.qq.connect | |||||
* @description: ①获取授权码code ②根据code换取令牌accessToken | |||||
* @author: yangkai.shen | |||||
* @date: Created in 2019-02-21 11:22 | |||||
* @copyright: Copyright (c) 2019 | |||||
* @version: V1.0 | |||||
* @modified: yangkai.shen | |||||
*/ | |||||
@Slf4j | |||||
public class QQOauth2Template extends OAuth2Template { | |||||
public QQOauth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) { | |||||
super(clientId, clientSecret, authorizeUrl, accessTokenUrl); | |||||
// 默认不携带 client_id 和 client_secret,根据文档需要携带这两个参数 | |||||
setUseParametersForClientAuthentication(true); | |||||
} | |||||
/** | |||||
* 根据code换取access_token,父类默认处理的是json格式,但是根据QQ文档,格式不是json格式,需要我们重写,自行处理 | |||||
* | |||||
* @param accessTokenUrl 换取 access_token 的 URL 地址 | |||||
* @param parameters 参数 | |||||
* @return {@link AccessGrant} | |||||
*/ | |||||
@Override | |||||
protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) { | |||||
String result = getRestTemplate().getForObject(accessTokenUrl, String.class, parameters); | |||||
// result 格式:access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14,文档:http://wiki.connect.qq.com/%E4%BD%BF%E7%94%A8authorization_code%E8%8E%B7%E5%8F%96access_token | |||||
log.error("【QQOauth2Template】url = {} ,result = {}", accessTokenUrl, result); | |||||
Map<String, String> data = Splitter.on("&").withKeyValueSeparator("=").split(result); | |||||
String accessToken = data.get("access_token"); | |||||
Long expiresIn = Long.valueOf(data.get("expires_in")); | |||||
String refreshToken = data.get("refresh_token"); | |||||
return new AccessGrant(accessToken, null, refreshToken, expiresIn); | |||||
} | |||||
/** | |||||
* 父类提供的方法消息转化器没有提供 String 格式的转换,所以这里需要额外加一个 | |||||
*/ | |||||
@Override | |||||
protected RestTemplate createRestTemplate() { | |||||
RestTemplate restTemplate = super.createRestTemplate(); | |||||
restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charsets.UTF_8)); | |||||
return restTemplate; | |||||
} | |||||
} |
@@ -0,0 +1,42 @@ | |||||
package com.xkcoding.social.qq.connect; | |||||
import com.xkcoding.social.qq.api.QQ; | |||||
import com.xkcoding.social.qq.api.impl.QQImpl; | |||||
import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider; | |||||
/** | |||||
* <p> | |||||
* 定义获取授权码地址和获取令牌地址,同时定义QQImpl | |||||
* </p> | |||||
* | |||||
* @package: com.xkcoding.social.qq.connect | |||||
* @description: 定义获取授权码地址和获取令牌地址,同时定义QQImpl | |||||
* @author: yangkai.shen | |||||
* @date: Created in 2019-02-21 13:28 | |||||
* @copyright: Copyright (c) 2019 | |||||
* @version: V1.0 | |||||
* @modified: yangkai.shen | |||||
*/ | |||||
public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ> { | |||||
private String clientId; | |||||
/** | |||||
* 获取授权码的地址,文档:http://wiki.connect.qq.com/%E4%BD%BF%E7%94%A8authorization_code%E8%8E%B7%E5%8F%96access_token | |||||
*/ | |||||
private static final String URL_FOR_CODE = "https://graph.qq.com/oauth2.0/authorize"; | |||||
/** | |||||
* 获取token的地址,文档:http://wiki.connect.qq.com/%E4%BD%BF%E7%94%A8authorization_code%E8%8E%B7%E5%8F%96access_token | |||||
*/ | |||||
private static final String URL_FOR_ACCESS_TOKEN = "https://graph.qq.com/oauth2.0/token"; | |||||
public QQServiceProvider(String clientId, String clientSecret) { | |||||
super(new QQOauth2Template(clientId, clientSecret, URL_FOR_CODE, URL_FOR_ACCESS_TOKEN)); | |||||
this.clientId = clientId; | |||||
} | |||||
@Override | |||||
public QQ getApi(String accessToken) { | |||||
return new QQImpl(accessToken, clientId); | |||||
} | |||||
} |
@@ -0,0 +1,17 @@ | |||||
server: | |||||
port: 8080 | |||||
servlet: | |||||
context-path: /demo | |||||
demo: | |||||
social: | |||||
filterProcessUrl: /auth | |||||
qq: | |||||
providerId: qq | |||||
clientId: | |||||
clientSecret: | |||||
spring: | |||||
datasource: | |||||
url: jdbc:mysql://127.0.0.1:3306/spring-boot-demo?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8 | |||||
username: root | |||||
password: root | |||||
driver-class-name: com.mysql.cj.jdbc.Driver |