Spring Social開發QQ第三方登錄

OAuth授權協議簡介

OAuth協議要解決的問題

解決第三方應用的訪問授權問題,用戶不需要通過密碼進行授權,只需要通過token授權即可,保護了密碼泄露的問題。

OAuth協議中的各種角色

在這裏插入圖片描述
服務提供商Provider:提供令牌Token。

  • 認證服務器:Authentication Server 認證用戶身份,產生令牌。
  • 資源服務器:Resource Server 保存用戶信息,驗證令牌。
    資源所有者Resource Owner: 用戶
    第三方應用Client:比如qq登錄、微信登錄。

OAuth協議中的授權模式有四種:

  • 授權碼模式 (authentication code)
  • 簡化模式 (implicit)
  • 密碼模式 (resource owner pwssword credentials)
  • 客戶端模式 (client credentials)

Spring Social實現QQ登錄

1、實現獲取用戶信息接口

自定義用戶信息實體類:

public class QQUserInfo {

    /**
     * ret : 0
     * msg :
     * nickname : Peter
     * figureurl : http://qzapp.qlogo.cn/qzapp/111111/942FEA70050EEAFBD4DCE2C1FC775E56/30
     * figureurl_1 : http://qzapp.qlogo.cn/qzapp/111111/942FEA70050EEAFBD4DCE2C1FC775E56/50
     * figureurl_2 : http://qzapp.qlogo.cn/qzapp/111111/942FEA70050EEAFBD4DCE2C1FC775E56/100
     * figureurl_qq_1 : http://q.qlogo.cn/qqapp/100312990/DE1931D5330620DBD07FB4A5422917B6/40
     * figureurl_qq_2 : http://q.qlogo.cn/qqapp/100312990/DE1931D5330620DBD07FB4A5422917B6/100
     * gender : 男
     * is_yellow_vip : 1
     * vip : 1
     * yellow_vip_level : 7
     * level : 7
     * is_yellow_year_vip : 1
     */

    private int ret;
    private String msg;
    private String nickname;
    private String figureurl;
    private String figureurl_1;
    private String figureurl_2;
    private String figureurl_qq_1;
    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;

    public int getRet() {
        return ret;
    }

    public void setRet(int ret) {
        this.ret = ret;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public String getFigureurl() {
        return figureurl;
    }

    public void setFigureurl(String figureurl) {
        this.figureurl = figureurl;
    }

    public String getFigureurl_1() {
        return figureurl_1;
    }

    public void setFigureurl_1(String figureurl_1) {
        this.figureurl_1 = figureurl_1;
    }

    public String getFigureurl_2() {
        return figureurl_2;
    }

    public void setFigureurl_2(String figureurl_2) {
        this.figureurl_2 = figureurl_2;
    }

    public String getFigureurl_qq_1() {
        return figureurl_qq_1;
    }

    public void setFigureurl_qq_1(String figureurl_qq_1) {
        this.figureurl_qq_1 = figureurl_qq_1;
    }

    public String getFigureurl_qq_2() {
        return figureurl_qq_2;
    }

    public void setFigureurl_qq_2(String figureurl_qq_2) {
        this.figureurl_qq_2 = figureurl_qq_2;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getIs_yellow_vip() {
        return is_yellow_vip;
    }

    public void setIs_yellow_vip(String is_yellow_vip) {
        this.is_yellow_vip = is_yellow_vip;
    }

    public String getVip() {
        return vip;
    }

    public void setVip(String vip) {
        this.vip = vip;
    }

    public String getYellow_vip_level() {
        return yellow_vip_level;
    }

    public void setYellow_vip_level(String yellow_vip_level) {
        this.yellow_vip_level = yellow_vip_level;
    }

    public String getLevel() {
        return level;
    }

    public void setLevel(String level) {
        this.level = level;
    }

    public String getIs_yellow_year_vip() {
        return is_yellow_year_vip;
    }

    public void setIs_yellow_year_vip(String is_yellow_year_vip) {
        this.is_yellow_year_vip = is_yellow_year_vip;
    }
}

自定義一個獲取用戶信息接口:

public interface QQ {

    QQUserInfo getUserInfo() throws JsonProcessingException;
}

獲取用戶信息接口實現:

/**
 *  獲取用戶信息
 */
public class QQImpl extends AbstractOAuth2ApiBinding implements QQ  {
    /**
     *  獲取openId的url
     */
    private static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s";
    /**
     *  獲取用戶信息的url
     */
    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();

    public QQImpl(String accessToken, String appId) {
        // 這句話會自動將accesstoken掛在請求上
        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.substring(result,result.indexOf("\"openid\":"), result.lastIndexOf("}"));
    }

    @Override
    public QQUserInfo getUserInfo() throws JsonProcessingException {
        String url = String.format(URL_GET_USERINFO, appId, openId);
        String result = getRestTemplate().getForObject(url,String.class);

        System.out.println(result);

        return objectMapper.readValue(result, QQUserInfo.class);
    }
    }

2、服務提供商

QQ服務提供商調用獲取用戶信息接口和獲取token服務商接口。

public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ> {

    private String appId;
    /**
     *  獲取Authorization Code 授權碼
     */
    private static final String URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize";
    /**
     *  權限自動續期,獲取Access Token
     */
    private static final String URL_ACCESS_TOKEN = "https://graph.qq.com/oauth2.0/token";

    public QQServiceProvider(String appId,String appSecret) {
        super(new OAuth2Template(appId, appSecret,URL_AUTHORIZE,URL_ACCESS_TOKEN ));
    }


    @Override
    public QQ getApi(String accessToken) {
        return new QQImpl(accessToken, appId);
    }
}

3、實現QQAdatapter

public class QQAdapter implements ApiAdapter<QQ> {

    @Override
    public boolean test(QQ qq) {
        return true;
    }

    /**
     *  設置ConnectionValues
     * @param api
     * @param connectionValues
     */
    @Override
    public void setConnectionValues(QQ api, ConnectionValues connectionValues) {
            QQUserInfo userInfo = api.getUserInfo();
            connectionValues.setDisplayName(userInfo.getNickname());
            connectionValues.setImageUrl(userInfo.getFigureurl_qq_1());
            connectionValues.setProfileUrl(null);
            connectionValues.setProviderUserId(userInfo.getOpenId());
    }

    @Override
    public UserProfile fetchUserProfile(QQ qq) {
        return null;
    }

    @Override
    public void updateStatus(QQ qq, String s) {
            // do nothing
    }
}

4、實現QQConnectinFactory

public class QQConnectinFactory extends OAuth2ConnectionFactory<QQ> {


    public QQConnectinFactory(String providerId, String appId, String appSeret) {
        super(providerId, new QQServiceProvider(appId, appSeret), new QQAdapter());
    }
}

5、數據連接配置

/**
 *  spring social 配置
 */
@Configuration
@EnableSocial
public class SocialConfig  extends SocialConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Override
    public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
        // 第三個參數對用戶信息進行加解密
        JdbcUsersConnectionRepository repository =  new JdbcUsersConnectionRepository(dataSource,connectionFactoryLocator, Encryptors.noOpText());
       // 設置表前綴
        repository.setTablePrefix("imooc_");
        return repository;
    }
}

6、在JdbcUsersConnectionRepository類中找到sql文件,在數據庫中創建表。

7、登錄驗證

在用戶密碼登錄時,實現了UserDetailsService,進行用於密碼登錄驗證。使用SpringSocial實現登錄驗證需要例外實現SocialUserDetailsService接口的loadUserByUserId方法:

/**
 *  用戶認證
 */
@Component
public class MyUserDetailsService implements UserDetailsService, SocialUserDetailsService {
   private static final Logger logger = LoggerFactory.getLogger(MyUserDetailsService.class);

   @Autowired
   private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
       logger.info("登錄用戶名:" + username);
       // 根據用戶名查找用戶信息
        // 根據查找的用戶信息判斷用戶是否被凍結
        /**
         *  第三個參數是權限集合 ,開發中可以將系統的user類,實現UserDetails進行用戶信息驗證
         */
        return new User(username,passwordEncoder.encode("123456"),
                true,true,true,true,
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }

    @Override
    public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException {
        logger.info("社交登錄用戶登錄id:" + userId);
        // 根據用戶名查找用戶信息
        // 根據查找的用戶信息判斷用戶是否被凍結
        /**
         *  第三個參數是權限集合 ,開發中可以將系統的user類,實現UserDetails進行用戶信息驗證
         */
        return (SocialUserDetails) new User(userId,passwordEncoder.encode("123456"),
                true,true,true,true,
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

8、配置QQ登錄所需的appId,appSecret,ProviderId

9、配置QQConnectionFactory

@Configuration
@ConditionalOnProperty(prefix = "imooc.security.social.qq",name = "app-id")
public class QQAutoConfig extends SocialAutoConfigurerAdapter {


    @Autowired
    private SecurityProperties securityProperties;

    @Override
    protected ConnectionFactory<?> createConnectionFactory() {
        QQProperties qqConfig = securityProperties.getSocial().getQq();
        return new QQConnectinFactory(qqConfig.getProviderId(), qqConfig.getAppId(),qqConfig.getAppSecret());
    }
}

10 、將SpringSocialConfigurer添加到Security的過濾器鏈上。

實例化bean


    @Bean
    public SpringSocialConfigurer imoocSocialSecurityConfig() {
        return new SpringSocialConfigurer();
    }

通過httSecurity的apply方法進行配置

   .apply(imoocSocialSecurityConfig)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章