zoukankan      html  css  js  c++  java
  • 使用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 }


  • 相关阅读:
    12_springmvc拦截器
    11_springmvc之RESTful支持
    10_springmvc JSON数据交互
    09_springmvc图片上传
    09_springmvc异常处理
    08_springmvc数据回显和@ModelAttribute注解详解
    Eclipse-----解决调试源码不进入断点问题
    JavaScript-----截取字符串的常用方法
    排序(Sort)-----冒泡排序
    SpringMVC探究-----常用获取传递参数的方法
  • 原文地址:https://www.cnblogs.com/fanqisoft/p/10691234.html
Copyright © 2011-2022 走看看