使用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 }

 

 




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM