使用SpringSocial开发QQ登录

⒈编写QQ用户对应的数据结构

  1 package cn.coreqi.social.qq.entities;
  2 
  3 /**
  4  * 封装QQ的用户信息
  5  */
  6 public class QQUserInfo {
  7 
  8     /**
  9      * 返回码
 10      */
 11     private String ret;
 12     /**
 13      * 如果ret<0,会有相应的错误信息提示,返回数据全部用UTF-8编码。
 14      */
 15     private String msg;
 16     /**
 17      *
 18      */
 19     private String openId;
 20     /**
 21      * 不知道什么东西,文档上没写,但是实际api返回里有。
 22      */
 23     private String is_lost;
 24     /**
 25      * 省(直辖市)
 26      */
 27     private String province;
 28     /**
 29      * 市(直辖市区)
 30      */
 31     private String city;
 32     /**
 33      * 出生年月
 34      */
 35     private String year;
 36     /**
 37      * 用户在QQ空间的昵称。
 38      */
 39     private String nickname;
 40     /**
 41      * 大小为30×30像素的QQ空间头像URL。
 42      */
 43     private String figureurl;
 44     /**
 45      * 大小为50×50像素的QQ空间头像URL。
 46      */
 47     private String figureurl_1;
 48     /**
 49      * 大小为100×100像素的QQ空间头像URL。
 50      */
 51     private String figureurl_2;
 52     /**
 53      * 大小为40×40像素的QQ头像URL。
 54      */
 55     private String figureurl_qq_1;
 56     /**
 57      * 大小为100×100像素的QQ头像URL。需要注意,不是所有的用户都拥有QQ的100×100的头像,但40×40像素则是一定会有。
 58      */
 59     private String figureurl_qq_2;
 60     /**
 61      * 性别。 如果获取不到则默认返回”男”
 62      */
 63     private String gender;
 64     /**
 65      * 标识用户是否为黄钻用户(0:不是;1:是)。
 66      */
 67     private String is_yellow_vip;
 68     /**
 69      * 标识用户是否为黄钻用户(0:不是;1:是)
 70      */
 71     private String vip;
 72     /**
 73      * 黄钻等级
 74      */
 75     private String yellow_vip_level;
 76     /**
 77      * 黄钻等级
 78      */
 79     private String level;
 80     /**
 81      * 标识是否为年费黄钻用户(0:不是; 1:是)
 82      */
 83     private String is_yellow_year_vip;
 84 
 85 
 86     public String getRet() {
 87         return ret;
 88     }
 89 
 90     public void setRet(String ret) {
 91         this.ret = ret;
 92     }
 93 
 94     public String getMsg() {
 95         return msg;
 96     }
 97 
 98     public void setMsg(String msg) {
 99         this.msg = msg;
100     }
101 
102     public String getOpenId() {
103         return openId;
104     }
105 
106     public void setOpenId(String openId) {
107         this.openId = openId;
108     }
109 
110     public String getIs_lost() {
111         return is_lost;
112     }
113 
114     public void setIs_lost(String is_lost) {
115         this.is_lost = is_lost;
116     }
117 
118     public String getProvince() {
119         return province;
120     }
121 
122     public void setProvince(String province) {
123         this.province = province;
124     }
125 
126     public String getCity() {
127         return city;
128     }
129 
130     public void setCity(String city) {
131         this.city = city;
132     }
133 
134     public String getYear() {
135         return year;
136     }
137 
138     public void setYear(String year) {
139         this.year = year;
140     }
141 
142     public String getNickname() {
143         return nickname;
144     }
145 
146     public void setNickname(String nickname) {
147         this.nickname = nickname;
148     }
149 
150     public String getFigureurl() {
151         return figureurl;
152     }
153 
154     public void setFigureurl(String figureurl) {
155         this.figureurl = figureurl;
156     }
157 
158     public String getFigureurl_1() {
159         return figureurl_1;
160     }
161 
162     public void setFigureurl_1(String figureurl_1) {
163         this.figureurl_1 = figureurl_1;
164     }
165 
166     public String getFigureurl_2() {
167         return figureurl_2;
168     }
169 
170     public void setFigureurl_2(String figureurl_2) {
171         this.figureurl_2 = figureurl_2;
172     }
173 
174     public String getFigureurl_qq_1() {
175         return figureurl_qq_1;
176     }
177 
178     public void setFigureurl_qq_1(String figureurl_qq_1) {
179         this.figureurl_qq_1 = figureurl_qq_1;
180     }
181 
182     public String getFigureurl_qq_2() {
183         return figureurl_qq_2;
184     }
185 
186     public void setFigureurl_qq_2(String figureurl_qq_2) {
187         this.figureurl_qq_2 = figureurl_qq_2;
188     }
189 
190     public String getGender() {
191         return gender;
192     }
193 
194     public void setGender(String gender) {
195         this.gender = gender;
196     }
197 
198     public String getIs_yellow_vip() {
199         return is_yellow_vip;
200     }
201 
202     public void setIs_yellow_vip(String is_yellow_vip) {
203         this.is_yellow_vip = is_yellow_vip;
204     }
205 
206     public String getVip() {
207         return vip;
208     }
209 
210     public void setVip(String vip) {
211         this.vip = vip;
212     }
213 
214     public String getYellow_vip_level() {
215         return yellow_vip_level;
216     }
217 
218     public void setYellow_vip_level(String yellow_vip_level) {
219         this.yellow_vip_level = yellow_vip_level;
220     }
221 
222     public String getLevel() {
223         return level;
224     }
225 
226     public void setLevel(String level) {
227         this.level = level;
228     }
229 
230     public String getIs_yellow_year_vip() {
231         return is_yellow_year_vip;
232     }
233 
234     public void setIs_yellow_year_vip(String is_yellow_year_vip) {
235         this.is_yellow_year_vip = is_yellow_year_vip;
236     }
237 }

⒉编写一个QQ API接口用于获取QQ用户信息

 1 package cn.coreqi.social.qq.api;
 2 
 3 import cn.coreqi.social.qq.entities.QQUserInfo;
 4 
 5 public interface QQ {
 6     /**
 7      * 返回QQ中的用户信息
 8      * @return
 9      */
10     QQUserInfo getUserInfo();
11 }

⒊编写一个QQ API接口实现

 1 package cn.coreqi.social.qq.api.impl;
 2 
 3 import cn.coreqi.social.qq.api.QQ;
 4 import cn.coreqi.social.qq.entities.QQUserInfo;
 5 import com.fasterxml.jackson.databind.ObjectMapper;
 6 import org.apache.commons.lang.StringUtils;
 7 import org.springframework.social.oauth2.AbstractOAuth2ApiBinding;
 8 import org.springframework.social.oauth2.TokenStrategy;
 9 
10 import java.io.IOException;
11 
12 /**
13  * 获取用户信息
14  * 不能声明为单例,因为每个用户的验证是不同的
15  */
16 public class QQImpl extends AbstractOAuth2ApiBinding implements QQ {
17 
18     private static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s";    //获取openid的请求地址
19     private static final String URL_GET_USERINFO = "https://graph.qq.com/user/get_user_info?oauth_consumer_key=%s&openid=%s";   //获取用户信息的请求地址
20 
21     private String appid;   //申请QQ登录成功后,分配给应用的appid
22     private String openid;  //用户的ID,与QQ号码一一对应。
23 
24     private ObjectMapper objectMapper = new ObjectMapper(); //用于序列化Json数据
25 
26     public QQImpl(String accessToken,String appid){
27         super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);   //将token作为查询参数
28         this.appid = appid;
29 
30         String url = String.format(URL_GET_OPENID,accessToken); //拼接成最终的openid的请求地址
31         String result = getRestTemplate().getForObject(url,String.class);
32 
33         System.out.println(result);
34 
35         this.openid = StringUtils.substringBetween(result,"\"openid\":\"","\"}");
36 
37     }
38 
39     @Override
40     public QQUserInfo getUserInfo() {
41         String url = String.format(URL_GET_USERINFO,appid,openid);  ////拼接成最终的获取用户信息的请求地址
42         String result = getRestTemplate().getForObject(url,String.class);
43         System.out.println(result);
44         QQUserInfo userInfo = null;
45         try {
46             userInfo =  objectMapper.readValue(result,QQUserInfo.class);
47             userInfo.setOpenId(openid);
48             return userInfo;
49         } catch (Exception e) {
50             throw new RuntimeException("获取用户信息失败",e);
51         }
52     }
53 }

⒋编写QQ OAuth2认证流程模板类。

 1 package cn.coreqi.social.qq.connect;
 2 
 3 import org.apache.commons.lang.StringUtils;
 4 import org.slf4j.Logger;
 5 import org.slf4j.LoggerFactory;
 6 import org.springframework.http.converter.StringHttpMessageConverter;
 7 import org.springframework.social.oauth2.AccessGrant;
 8 import org.springframework.social.oauth2.OAuth2Template;
 9 import org.springframework.util.MultiValueMap;
10 import org.springframework.web.client.RestTemplate;
11 import java.nio.charset.Charset;
12 
13 public class QQOAuth2Template extends OAuth2Template {
14 
15     private Logger logger = LoggerFactory.getLogger(getClass());
16 
17     public QQOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
18         super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
19         setUseParametersForClientAuthentication(true);
20     }
21 
22     @Override
23     protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {
24         String responseStr = getRestTemplate().postForObject(accessTokenUrl, parameters, String.class);
25 
26         logger.info("获取accessToke的响应:"+responseStr);
27 
28         String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(responseStr, "&");
29 
30         String accessToken = StringUtils.substringAfterLast(items[0], "=");
31         Long expiresIn = new Long(StringUtils.substringAfterLast(items[1], "="));
32         String refreshToken = StringUtils.substringAfterLast(items[2], "=");
33 
34         return new AccessGrant(accessToken, null, refreshToken, expiresIn);
35     }
36 
37     @Override
38     protected RestTemplate createRestTemplate() {
39         RestTemplate restTemplate = super.createRestTemplate();
40         restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
41         return restTemplate;
42     }
43 }

⒌编写QQ的OAuth2流程处理器的提供器

 1 package cn.coreqi.social.qq.connect;
 2 
 3 import cn.coreqi.social.qq.api.QQ;
 4 import cn.coreqi.social.qq.api.impl.QQImpl;
 5 import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider;
 6 
 7 /**
 8  * 泛型是API接口的类型
 9  */
10 public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ> {
11 
12     private static final String URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize";  //获取授权码地址
13     private static final String URL_ACCESS_TOKEN = "https://graph.qq.com/oauth2.0/token";   //获取用户令牌地址
14 
15     private String appId;
16 
17 
18     public QQServiceProvider(String appId,String appSecret) {
19         super(new QQOAuth2Template(appId,appSecret,URL_AUTHORIZE,URL_ACCESS_TOKEN));
20         this.appId = appId;
21     }
22 
23     @Override
24     public QQ getApi(String accessToken) {
25         return new QQImpl(accessToken,appId);
26     }
27 }

⒍编写QQ API适配器,将从QQ API拿到的用户数据模型转换为Spring Social的标准用户数据模型。

 1 package cn.coreqi.social.qq.connect;
 2 
 3 import cn.coreqi.social.qq.api.QQ;
 4 import cn.coreqi.social.qq.entities.QQUserInfo;
 5 import org.springframework.social.connect.ApiAdapter;
 6 import org.springframework.social.connect.ConnectionValues;
 7 import org.springframework.social.connect.UserProfile;
 8 
 9 import java.io.IOException;
10 
11 /**
12  *  泛型是指当前API适配器适配API的类型是什么
13  */
14 public class QQAdapter implements ApiAdapter<QQ> {
15 
16     /**
17      * 用来测试当前的API是否可用
18      * @param qq
19      * @return
20      */
21     @Override
22     public boolean test(QQ qq) {
23         return true;
24     }
25 
26     /**
27      * 将服务提供商个性化的用户信息映射到ConnectionValues标准的数据化结构上
28      * @param qq
29      * @param connectionValues
30      */
31     @Override
32     public void setConnectionValues(QQ qq, ConnectionValues connectionValues) {
33         QQUserInfo userInfo = qq.getUserInfo();
34         connectionValues.setDisplayName(userInfo.getNickname());  //显示的用户名称
35         connectionValues.setImageUrl(userInfo.getFigureurl_qq_1()); //用户的头像
36         connectionValues.setProfileUrl(null);   //个人主页
37         connectionValues.setProviderUserId(userInfo.getOpenId());   //QQ的唯一标识
38     }
39 
40     /**
41      * 和上面的方法类似
42      * @param qq
43      * @return
44      */
45     @Override
46     public UserProfile fetchUserProfile(QQ qq) {
47         return null;
48     }
49 
50     /**
51      *
52      * @param qq
53      * @param s
54      */
55     @Override
56     public void updateStatus(QQ qq, String s) {
57 
58     }
59 }

⒎创建QQ连接工厂

 1 package cn.coreqi.social.qq.connect;
 2 
 3 import cn.coreqi.social.qq.api.QQ;
 4 import org.springframework.social.connect.support.OAuth2ConnectionFactory;
 5 
 6 public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ> {
 7 
 8     /**
 9      *
10      * @param providerId    我们给服务提供商的唯一标识
11      * @param appId 服务提供商给的AppId
12      * @param appSecret 服务提供商给的App密码
13      */
14     public QQConnectionFactory(String providerId,String appId,String appSecret) {
15         super(providerId, new QQServiceProvider(appId,appSecret), new QQAdapter());
16     }
17 }

⒏创建UserConnection数据表

 1 create table UserConnection (userId varchar(255) not null,
 2     providerId varchar(255) not null,
 3     providerUserId varchar(255),
 4     `rank` int not null,
 5     displayName varchar(255),
 6     profileUrl varchar(512),
 7     imageUrl varchar(512),
 8     accessToken varchar(512) not null,
 9     secret varchar(512),
10     refreshToken varchar(512),
11     expireTime bigint,
12     primary key (userId, providerId, providerUserId));
13 create unique index UserConnectionRank on UserConnection(userId, providerId, `rank`);

⒐为用户服务类实现SocialUserDetailsService ,用于从数据库中通过QQ Id 拿到业务系统用户

 1 /**
 2  * 
 3  */
 4 package cn.coreqi.security;
 5 
 6 import org.slf4j.Logger;
 7 import org.slf4j.LoggerFactory;
 8 import org.springframework.beans.factory.annotation.Autowired;
 9 import org.springframework.security.core.authority.AuthorityUtils;
10 import org.springframework.security.core.userdetails.UserDetails;
11 import org.springframework.security.core.userdetails.UserDetailsService;
12 import org.springframework.security.core.userdetails.UsernameNotFoundException;
13 import org.springframework.security.crypto.password.PasswordEncoder;
14 import org.springframework.social.security.SocialUser;
15 import org.springframework.social.security.SocialUserDetails;
16 import org.springframework.social.security.SocialUserDetailsService;
17 import org.springframework.stereotype.Component;
18 
19 /**
20  * @author fanqi
21  *
22  */
23 @Component
24 public class MyUserDetailsService implements UserDetailsService, SocialUserDetailsService {
25 
26     private Logger logger = LoggerFactory.getLogger(getClass());
27     
28     @Autowired
29     private PasswordEncoder passwordEncoder;
30 
31     /*
32      * (non-Javadoc)
33      * 
34      * @see org.springframework.security.core.userdetails.UserDetailsService#
35      * loadUserByUsername(java.lang.String)
36      */
37     @Override
38     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
39         logger.info("表单登录用户名:" + username);
40         return buildUser(username);
41     }
42 
43     @Override
44     public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException {
45         logger.info("设计登录用户Id:" + userId);
46         return buildUser(userId);
47     }
48 
49     private SocialUserDetails buildUser(String userId) {
50         // 根据用户名查找用户信息
51         //根据查找到的用户信息判断用户是否被冻结
52         String password = passwordEncoder.encode("123456");
53         logger.info("数据库密码是:"+password);
54         return new SocialUser(userId, password,
55                 true, true, true, true,
56                 AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
57     }
58 
59 }

⒑创建QQ登陆配置类

 1 package cn.coreqi.social.qq.connect;
 2 
 3 import org.springframework.boot.autoconfigure.social.SocialAutoConfigurerAdapter;
 4 import org.springframework.context.annotation.Configuration;
 5 import org.springframework.social.connect.ConnectionFactory;
 6 
 7 /**
 8  * QQ登录配置
 9  */
10 @Configuration
11 public class QQAutoConfig extends SocialAutoConfigurerAdapter {
12     @Override
13     protected ConnectionFactory<?> createConnectionFactory() {
14         String providerId = "qq";   //第三方id,用来决定发起第三方登录的url,默认是weixin
15         String appId = "";
16         String appSecret = "";
17         return new QQConnectionFactory(providerId, appId, appSecret);
18     }
19 }

 ⒒自定义我们自己的SpringSocial配置

 1 package cn.coreqi.social.config;
 2 
 3 import org.springframework.social.security.SocialAuthenticationFilter;
 4 import org.springframework.social.security.SpringSocialConfigurer;
 5 
 6 public class CoreqiSpringSocialConfig extends SpringSocialConfigurer {
 7 
 8     /**
 9      *
10      * @param object
11      * @param <T>
12      * @return
13      */
14     @Override
15     protected <T> T postProcess(T object) {
16         SocialAuthenticationFilter filter = (SocialAuthenticationFilter)super.postProcess(object);
17         filter.setFilterProcessesUrl("/coreqi/auth");
18         return (T) filter;
19     }
20 }
SpringSocialConfigurer 会在 configure方法中声明一个 SocialAuthenticationFilter,我们可以继承SpringSocialConfigurer达到自定义我们的SpringSocial配置需求。

⒓声明一个SpringSocial的配置类
 1 package cn.coreqi.social.config;
 2 
 3 import org.springframework.beans.factory.annotation.Autowired;
 4 import org.springframework.context.annotation.Bean;
 5 import org.springframework.context.annotation.Configuration;
 6 import org.springframework.security.crypto.encrypt.Encryptors;
 7 import org.springframework.social.config.annotation.EnableSocial;
 8 import org.springframework.social.config.annotation.SocialConfigurerAdapter;
 9 import org.springframework.social.connect.ConnectionFactoryLocator;
10 import org.springframework.social.connect.ConnectionSignUp;
11 import org.springframework.social.connect.UsersConnectionRepository;
12 import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository;
13 import org.springframework.social.connect.web.ProviderSignInUtils;
14 import org.springframework.social.security.SpringSocialConfigurer;
15 
16 import javax.sql.DataSource;
17 
18 @Configuration
19 @EnableSocial
20 public class SocialConfig extends SocialConfigurerAdapter {
21 
22     @Autowired
23     private DataSource dataSource;
24 
25     @Autowired(required = false)
26     private ConnectionSignUp connectionSignUp;
27 
28     /**
29      *
30      * @param connectionFactoryLocator  作用是去根据条件去查找应该用那个connectionFactory,因为系统中可能有很多的connectionFactory。
31      * @return
32      */
33     @Override
34     public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
35         //第三个参数的作用是把插入到数据库的数据进行加解密
36         JdbcUsersConnectionRepository jdbcUsersConnectionRepository = new JdbcUsersConnectionRepository(dataSource,connectionFactoryLocator, Encryptors.noOpText());
37         //jdbcUsersConnectionRepository.setTablePrefix(); //设置数据表的前缀
38         if(connectionSignUp != null){
39             jdbcUsersConnectionRepository.setConnectionSignUp(connectionSignUp);
40         }
41         return jdbcUsersConnectionRepository;
42     }
43 
44     /**
45      * 声明后还需要加在SpringSecurity过滤器链上
46      * @return
47      */
48     @Bean
49     public SpringSocialConfigurer coreqiSocialSecurityConfig(){
50         CoreqiSpringSocialConfig config = new CoreqiSpringSocialConfig();
51         config.signupUrl("/registry");  //当从业务系统中无法找到OAuth快捷登陆的用户,那么将用户引导到注册页面中
52         return config;
53     }
54 
55     //1.注册过程中如何拿到SpringSocial信息
56     //2.注册完成后如何把业务系统的用户ID传给SpringSocial
57     @Bean
58     public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator){
59         return new ProviderSignInUtils(connectionFactoryLocator,getUsersConnectionRepository(connectionFactoryLocator));
60     }
61 }

 

⒔应用我们的过滤器配置
 1 package cn.coreqi.config;
 2 
 3 import org.springframework.beans.factory.annotation.Autowired;
 4 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 5 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 6 import org.springframework.social.security.SpringSocialConfigurer;
 7 
 8 public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 9     @Autowired
10     private SpringSocialConfigurer coreqiSocialSecurityConfig;
11     @Override
12     protected void configure(HttpSecurity http) throws Exception {
13         http.apply(coreqiSocialSecurityConfig);
14     }
15 }

 ⒕

 1 package cn.coreqi.social.qq.connect;
 2 
 3 import org.springframework.social.connect.Connection;
 4 import org.springframework.social.connect.ConnectionSignUp;
 5 import org.springframework.stereotype.Component;
 6 
 7 /**
 8  * 当没有从数据库中查找到第三方登录的用户,那么将执行ConnectionSignUp的execute方法生成新的用户id并存储到数据库中
 9  */
10 @Component
11 public class CoreqiConnectionSignUp implements ConnectionSignUp {
12     @Override
13     public String execute(Connection<?> connection) {
14         return connection.getDisplayName();
15     }
16 }

 

 



posted @ 2019-04-11 17:45  SpringCore  阅读(1388)  评论(1编辑  收藏  举报