1. 程式人生 > >Spring Security原始碼分析三:Spring Social實現QQ社交登入

Spring Security原始碼分析三:Spring Social實現QQ社交登入

社交登入又稱作社會化登入(Social Login),是指網站的使用者可以使用騰訊QQ、人人網、開心網、新浪微博、搜狐微博、騰訊微博、淘寶、豆瓣、MSN、Google等社會化媒體賬號登入該網站。

OAuth2.0的認證流程示意圖

http://dandandeshangni.oss-cn-beijing.aliyuncs.com/github/Spring%20Security/OAuth2-Sequence.png

  1. 請求第三方應用
  2. 第三方應用將使用者請求導向服務提供商
  3. 使用者同意授權
  4. 服務提供商返回code
  5. client根據code去服務提供商換取令牌
  6. 返回令牌
  7. 獲取使用者資訊

在標準的OAuth2協議中,1-6步都是固定,只有最後一步,不通的服務提供商返回的使用者資訊是不同的。Spring Social已經為我們封裝好了1-6步。

使用Spring Social

準備工作

  1. qq互聯申請個人開發者,獲得appId和appKey或者使用 SpringForAll貢獻出來的
  2. 配置本地host 新增 127.0.0.1 www.ictgu.cn
  3. 資料庫執行以下sql
create table UserConnection (userId varchar(255) not null,
    providerId varchar(255) not null,
    providerUserId varchar(255),
    rank int not null,
    displayName varchar(255),
    profileUrl varchar
(512), imageUrl varchar(512), accessToken varchar(512) not null, secret varchar(512), refreshToken varchar(512), expireTime bigint, primary key (userId, providerId, providerUserId));
create unique index UserConnectionRank on UserConnection(userId, providerId, rank);
  1. 專案埠設定為80

引入Spring Social 模組

模組 描述
spring-social-core 提供社交連線框架和OAuth 客戶端支援
spring-social-config 提供Java 配置
spring-social-security 社交安全的一些支援
spring-social-web 管理web應用程式的連線
!--spring-social 相關-->
        <dependency>
            <groupId>org.springframework.social</groupId>
            <artifactId>spring-social-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.social</groupId>
            <artifactId>spring-social-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.social</groupId>
            <artifactId>spring-social-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.social</groupId>
            <artifactId>spring-social-web</artifactId>
        </dependency>

目錄結構

http://dandandeshangni.oss-cn-beijing.aliyuncs.com/github/Spring%20Security/spring-social_01.png

  1. ‘api’ 定義api繫結的公共介面
  2. ‘config’ qq的一些配置資訊
  3. ‘connect’與服務提供商建立連線所需的一些類。

定義返回使用者資訊介面

public interface QQ {
    /**
     * 獲取使用者資訊
     * @return
     */
    QQUserInfo getUserInfo();
}

實現返回使用者資訊介面

@Slf4j
public class QQImpl extends AbstractOAuth2ApiBinding implements QQ {

    //http://wiki.connect.qq.com/openapi%E8%B0%83%E7%94%A8%E8%AF%B4%E6%98%8E_oauth2-0
    private static final String QQ_URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s";
    //http://wiki.connect.qq.com/get_user_info(access_token由父類提供)
    private static final String QQ_URL_GET_USER_INFO = "https://graph.qq.com/user/get_user_info?oauth_consumer_key=%s&openid=%s";
    /**
     * appId 配置檔案讀取
     */
    private String appId;
    /**
     * openId 請求QQ_URL_GET_OPENID返回
     */
    private String openId;
    /**
     * 工具類
     */
    private ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 構造方法獲取openId
     */
    public QQImpl(String accessToken, String appId) {
        //access_token作為查詢引數來攜帶。
        super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);

        this.appId = appId;

        String url = String.format(QQ_URL_GET_OPENID, accessToken);
        String result = getRestTemplate().getForObject(url, String.class);

        log.info("【QQImpl】 QQ_URL_GET_OPENID={} result={}", QQ_URL_GET_OPENID, result);

        this.openId = StringUtils.substringBetween(result, "\"openid\":\"", "\"}");
    }

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

        log.info("【QQImpl】 QQ_URL_GET_USER_INFO={} result={}", QQ_URL_GET_USER_INFO, result);

        QQUserInfo userInfo = null;
        try {
            userInfo = objectMapper.readValue(result, QQUserInfo.class);
            userInfo.setOpenId(openId);
            return userInfo;
        } catch (Exception e) {
            throw new RuntimeException("獲取使用者資訊失敗", e);
        }
    }
}

QQOAuth2Template處理qq返回的令牌資訊

@Slf4j
public class QQOAuth2Template extends OAuth2Template {
    public QQOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
        super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
        setUseParametersForClientAuthentication(true);
    }

    @Override
    protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {
        String responseStr = getRestTemplate().postForObject(accessTokenUrl, parameters, String.class);

        log.info("【QQOAuth2Template】獲取accessToke的響應:responseStr={}" + responseStr);

        String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(responseStr, "&");
        //http://wiki.connect.qq.com/使用authorization_code獲取access_token
        //access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14
        String accessToken = StringUtils.substringAfterLast(items[0], "=");
        Long expiresIn = new Long(StringUtils.substringAfterLast(items[1], "="));
        String refreshToken = StringUtils.substringAfterLast(items[2], "=");

        return new AccessGrant(accessToken, null, refreshToken, expiresIn);
    }


    /**
     * 坑,日誌debug模式才打印出來 處理qq返回的text/html 型別資料
     *
     * @return
     */
    @Override
    protected RestTemplate createRestTemplate() {
        RestTemplate restTemplate = super.createRestTemplate();
        restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
        return restTemplate;
    }
}

QQServiceProvider連線服務提供商

public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ> {

    /**
     * 獲取code
     */
    private static final String QQ_URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize";
    /**
     * 獲取access_token 也就是令牌
     */
    private static final String QQ_URL_ACCESS_TOKEN = "https://graph.qq.com/oauth2.0/token";
    private String appId;

    public QQServiceProvider(String appId, String appSecret) {
        super(new QQOAuth2Template(appId, appSecret, QQ_URL_AUTHORIZE, QQ_URL_ACCESS_TOKEN));
        this.appId = appId;
    }

    @Override
    public QQ getApi(String accessToken) {

        return new QQImpl(accessToken, appId);
    }
}

QQConnectionFactory連線服務提供商的工廠類

public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ> {

    public QQConnectionFactory(String providerId, String appId, String appSecret) {
        super(providerId, new QQServiceProvider(appId, appSecret), new QQAdapter());
    }
}

QQAdapter 適配spring Social預設的返回資訊

public class QQAdapter implements ApiAdapter<QQ> {
    @Override
    public boolean test(QQ api) {
        return true;
    }

    @Override
    public void setConnectionValues(QQ api, ConnectionValues values) {
        QQUserInfo userInfo = api.getUserInfo();

        values.setProviderUserId(userInfo.getOpenId());//openId 唯一標識
        values.setDisplayName(userInfo.getNickname());
        values.setImageUrl(userInfo.getFigureurl_qq_1());
        values.setProfileUrl(null);
    }

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

    @Override
    public void updateStatus(QQ api, String message) {

    }
}

SocialConfig 社交配置主類

@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {

    /**
     * 社交登入配類
     *
     * @return
     */
    @Bean
    public SpringSocialConfigurer merryyouSocialSecurityConfig() {
        String filterProcessesUrl = SecurityConstants.DEFAULT_SOCIAL_QQ_PROCESS_URL;
        MerryyouSpringSocialConfigurer configurer = new MerryyouSpringSocialConfigurer(filterProcessesUrl);
        return configurer;
    }

    /**
     * 處理註冊流程的工具類
     * @param factoryLocator
     * @return
     */
    @Bean
    public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator factoryLocator) {
        return new ProviderSignInUtils(factoryLocator, getUsersConnectionRepository(factoryLocator));
    }

}
QQAuthConfig 針對qq返回結果的一些操作
@Configuration
public class QQAuthConfig extends SocialAutoConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private ConnectionSignUp myConnectionSignUp;

    @Override
    protected ConnectionFactory<?> createConnectionFactory() {
        return new QQConnectionFactory(SecurityConstants.DEFAULT_SOCIAL_QQ_PROVIDER_ID, SecurityConstants.DEFAULT_SOCIAL_QQ_APP_ID, SecurityConstants.DEFAULT_SOCIAL_QQ_APP_SECRET);
    }

    @Override
    public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
        JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource,
                connectionFactoryLocator, Encryptors.noOpText());
        if (myConnectionSignUp != null) {
            repository.setConnectionSignUp(myConnectionSignUp);
        }
        return repository;
    }
}

MerryyouSpringSocialConfigurer自定義登入和註冊連線

public class MerryyouSpringSocialConfigurer extends SpringSocialConfigurer {

    private String filterProcessesUrl;

    public MerryyouSpringSocialConfigurer(String filterProcessesUrl) {
        this.filterProcessesUrl = filterProcessesUrl;
    }

    @Override
    protected <T> T postProcess(T object) {
        SocialAuthenticationFilter filter = (SocialAuthenticationFilter) super.postProcess(object);
        filter.setFilterProcessesUrl(filterProcessesUrl);
        filter.setSignupUrl("/register");
        return (T) filter;
    }
}

開啟SocialAuthenticationFilter過濾器

@Autowired
    private SpringSocialConfigurer merryyouSpringSocialConfigurer;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                .formLogin()//使用表單登入,不再使用預設httpBasic方式
                .loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)//如果請求的URL需要認證則跳轉的URL
                .loginProcessingUrl(SecurityConstants.DEFAULT_SIGN_IN_PROCESSING_URL_FORM)//處理表單中自定義的登入URL
                .and()
                .apply(merryyouSpringSocialConfigurer)
                .and()
                .authorizeRequests().antMatchers(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
                SecurityConstants.DEFAULT_SIGN_IN_PROCESSING_URL_FORM,
                SecurityConstants.DEFAULT_REGISTER_URL,
                "/register",
                "/social/info",
                "/**/*.js",
                "/**/*.css",
                "/**/*.jpg",
                "/**/*.png",
                "/**/*.woff2",
                "/code/image")
                .permitAll()//以上的請求都不需要認證
                //.antMatchers("/").access("hasRole('USER')")
                .and()
                .csrf().disable()//關閉csrd攔截
        ;
        //安全模組單獨配置
        authorizeConfigProvider.config(http.authorizeRequests());
    }

效果如下:
http://dandandeshangni.oss-cn-beijing.aliyuncs.com/github/Spring%20Security/demo.gif

程式碼下載

相關推薦

Spring Security原始碼分析Spring Social實現QQ社交登入

社交登入又稱作社會化登入(Social Login),是指網站的使用者可以使用騰訊QQ、人人網、開心網、新浪微博、搜狐微博、騰訊微博、淘寶、豆瓣、MSN、Google等社會化媒體賬號登入該網站。 OAuth2.0的認證流程示意圖 請求第三方應用

Spring Security原始碼分析Spring Social實現微信社交登入

社交登入又稱作社會化登入(Social Login),是指網站的使用者可以使用騰訊QQ、人人網、開心網、新浪微博、搜狐微博、騰訊微博、淘寶、豆瓣、MSN、Google等社會化媒體賬號登入該網站。 前言 在上一章Spring-Security原始碼分析

Spring Security原始碼分析Spring Social社交登入原始碼解析

在Spring Security原始碼分析三:Spring Social實現QQ社交登入和Spring Security原始碼分析四:Spring Social實現微信社交登入這兩章中,我們使用Spring Social已經實現了國內最常用的QQ和微信社交

Spring Core Container 原始碼分析Spring Beans 初始化流程分析

前言 本文是筆者所著的 Spring Core Container 原始碼分析系列之一; 本篇文章主要試圖梳理出 Spring Beans 的初始化主流程和相關核心程式碼邏輯; 本文轉載自本人的部落格,傷神的部落格 http://www.shangyang.me/2017/

Spring Security原始碼分析十四Spring Social 社交登入的繫結與解綁

社交登入又稱作社會化登入(Social Login),是指網站的使用者可以使用騰訊QQ、人人網、開心網、新浪微博、搜狐微博、騰訊微博、淘寶、豆瓣、MSN、Google等社會化媒體賬號登入該網站。 前言 在之前的Spring Social系列中,我

Spring Security原始碼分析十一Spring Security OAuth2整合JWT

Json web token (JWT), 是為了在網路應用環境間傳遞宣告而執行的一種基於JSON的開放標準(RFC 7519).該token被設計為緊湊且安全的,特別適用於分散式站點的單點登入(SSO)場景。JWT的宣告一般被用來在身份提供者和服務提供者

Spring Security原始碼分析十六Spring Security專案實戰

Spring Security是一個能夠為基於Spring的企業應用系統提供宣告式的安全訪問控制解決方案的安全框架。它提供了一組可以在Spring應用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反轉Inversion of Contr

Spring Security原始碼分析十五Spring Security 頁面許可權控制

Spring Security是一個能夠為基於Spring的企業應用系統提供宣告式的安全訪問控制解決方案的安全框架。它提供了一組可以在Spring應用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反轉Inversion of Contr

Spring Security原始碼分析十二Spring Security OAuth2基於JWT實現單點登入

單點登入(英語:Single sign-on,縮寫為 SSO),又譯為單一簽入,一種對於許多相互關連,但是又是各自獨立的軟體系統,提供訪問控制的屬性。當擁有這項屬性時,當用戶登入時,就可以獲取所有

Spring Core Container 源碼分析Spring Beans 初始化流程分析

turn raw time -c rri add 步驟 引用 lin 前言 本文是筆者所著的 Spring Core Container 源碼分析系列之一; 本篇文章主要試圖梳理出 Spring Beans 的初始化主流程和相關核心代碼邏輯; 本文轉載自本人的私人博客,傷神

Spring Boot+Spring Security專案開發()實現簡訊驗證碼登入

說在前面 博主最近會有很多專案跟大家一起分享,做完後會上傳github上的,希望讀友們能給博主提提意見哈哈 這個專案是第三方登入和安全方面的,關於後臺與app和網站的登入連線操作的實戰專案 各位如果可以就給我star哈哈謝謝啦 實

spring security 原始碼分析

表單登入 UsernamePasswordAuthenticationFilter /login Post 請求 會被這個過濾器攔截 如果為/login時 會經過 if (!this.requiresAuthentication(request, respo

5.2 spring5原始碼--spring AOP原始碼分析---切面原始碼分析

一. AOP切面原始碼分析 原始碼分析分為三部分 1. 解析切面 2. 建立動態代理 3. 呼叫   原始碼的入口 原始碼分析的入口, 從註解開始: 元件的入口是一個註解, 比如啟用AOP的註解@EnableAspectJAutoProxy. 在註解的實現類裡面, 會有一個@Import("

Spring Boot 2.0()Spring Boot 開源軟件都有哪些?

Spring Boot 開源 2016年 Spring Boot 還沒有被廣泛使用,在網上查找相關開源軟件的時候沒有發現幾個,到了現在經過2年的發展,很多互聯網公司已經將 Spring Boot 搬上了生產,而使用 Spring Boot 的開源軟件在 Github/碼雲 上面已有不少,這篇文章就給大

spring boot 系列之spring boot 整合JdbcTemplate

closed com context boot pin pan url wired ace 前面兩篇文章我們講了兩件事情: 通過一個簡單實例進行spring boot 入門 修改spring boot 默認的服務端口號和默認context path 這篇文章我們來看下怎

Spring Boot 系統之Spring Boot 整合JdbcTemplate

前面兩篇文章我們講了兩件事情: 通過一個簡單例項進行Spring Boot 入門 修改Spring Boot 預設的服務埠號和預設context path 這篇文章我們來看下怎麼通過JdbcTemplate進行資料的持久化。 一、程式碼實現 1、修改pom.xml檔案

Spring Boot 2.0()Spring Boot 開源軟體都有哪些?

2016年 Spring Boot 還沒有被廣泛使用,在網上查詢相關開源軟體的時候沒有發現幾個,到了現在經過2年的發展,很多網際網路公司已經將 Spring Boot 搬上了生產,而使用 Spring Boot 的開源軟體在 Github/碼雲 上面已有不少,這篇文章就給大家介紹一下 Github/碼雲 上面

WebRTC原始碼分析視訊處理流程

 文字介紹視訊的處理流程。圖1中顯示了兩路視訊會話視訊訊號流過程。 圖1 視訊流程示意圖 以一路視訊會話為例,主要分為以下幾個執行緒: 1)視訊源產生執行緒:Camera生產視訊畫面,封裝成視訊幀,以一定幀率投遞到下一個模組。; 2)採集執行緒:由Capturer負責採集視訊幀,並對視訊幀進行一定處理,如

ABP原始碼分析ABP Module

Abp是一種基於模組化設計的思想構建的。開發人員可以將自定義的功能以模組(module)的形式整合到ABP中。具體的功能都可以設計成一個單獨的Module。Abp底層框架提供便捷的方法整合每個Module.下圖是所有Abp自帶的module.AbpModule是所有Module的基類,其已經擁有了IIocMa

Spring Boot系列教程使用devtools實現熱部署

一.前言 Eclipse下使用spring-tool-suite外掛建立一個spring boot 工程,通過右鍵“Run As”--->"Spring Boot App"來啟動工程,這時當我們