Social Login, also known as Social Login, means that users of a website can use Tencent QQ, renren, kaixin001, sina weibo, sohu weibo, Tencent weibo, taobao, douban, MSN, Google and other Social media accounts to Login to the website.

Schematic diagram of OAuth2.0 authentication process

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/1/9/160da83977cbd95d~tplv-t2oaga2asx-image.image

  1. Requesting a third-party application
  2. Third-party applications direct user requests to service providers
  3. User consent authorization
  4. The service provider returns the code
  5. The client goes to the service provider for a token according to the code
  6. Returns the token
  7. Obtaining User information

In the standard OAuth2 protocol, steps 1 to 6 are fixed, except for the last step, the user information returned by the disconnected service provider is different. Spring Social has encapsulated steps 1-6 for us.

Use Spring Social

The preparatory work

  1. Apply for individual developers on QQ Internet, get appId and appKey or use SpringForAll to contribute
  2. Configure local host to be added127.0.0.1 www.ictgu.cn
  3. The database executes the following 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);
Copy the code
  1. Project port is set to80port

Introduce the Spring Social module

The module describe
spring-social-core Provides social connection framework and OAuth client support
spring-social-config Providing Java configuration
spring-social-security Some support for social safety
spring-social-web Manage connections to Web applications
! - spring - social related -- -- ><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>
Copy the code

The directory structure

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/1/9/160da839741309a3~tplv-t2oaga2asx-image.image

  1. ‘API’ defines the public interface for the API binding
  2. ‘config’ some configuration information about QQ
  3. ‘connect’ provides the classes needed to establish a connection with the service provider.

Define the interface that returns user information

public interface QQ {
    /** * Get user information *@return* /
    QQUserInfo getUserInfo(a);
}
Copy the code

Realize the interface to return user information

@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 is provided by the parent class)
    private static final String QQ_URL_GET_USER_INFO = "https://graph.qq.com/user/get_user_info?oauth_consumer_key=%s&openid=%s";
    /** * appId configuration file read */
    private String appId;
    /** * openId request QQ_URL_GET_OPENID returns */
    private String openId;
    /** * tool class */
    private ObjectMapper objectMapper = new ObjectMapper();

    /** * the constructor gets openId */
    public QQImpl(String accessToken, String appId) {
        //access_token is carried as a query parameter.
        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(a) {
        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("Failed to obtain user information", e); }}}Copy the code

QQOAuth2Template processes the token information returned by 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] accesstok: responseStr={}" + responseStr);

        String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(responseStr, "&");
        / / http://wiki.connect.qq.com/ using 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);
    }


    /** * error, log debug mode is printed to process text/ HTML data returned by QQ **@return* /
    @Override
    protected RestTemplate createRestTemplate(a) {
        RestTemplate restTemplate = super.createRestTemplate();
        restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
        returnrestTemplate; }}Copy the code

QQServiceProvider Connects to the service provider

public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ> {

    /** * get code */
    private static final String QQ_URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize";
    /** * gets the access_token, which is the 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 newQQImpl(accessToken, appId); }}Copy the code

QQConnectionFactory Factory class for the connection service provider

public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ> {

    public QQConnectionFactory(String providerId, String appId, String appSecret) {
        super(providerId, new QQServiceProvider(appId, appSecret), newQQAdapter()); }}Copy the code

The QQAdapter ADAPTS to spring Social default return information

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 unique identifier
        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) {}}Copy the code

SocialConfig Main class of social configuration

@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {

    /** * Social logins **@return* /
    @Bean
    public SpringSocialConfigurer merryyouSocialSecurityConfig(a) {
        String filterProcessesUrl = SecurityConstants.DEFAULT_SOCIAL_QQ_PROCESS_URL;
        MerryyouSpringSocialConfigurer configurer = new MerryyouSpringSocialConfigurer(filterProcessesUrl);
        return configurer;
    }

    /** * Tools to handle the registration process *@param factoryLocator
     * @return* /
    @Bean
    public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator factoryLocator) {
        return newProviderSignInUtils(factoryLocator, getUsersConnectionRepository(factoryLocator)); }}Copy the code
QQAuthConfig for qq return results of some operations
@Configuration
public class QQAuthConfig extends SocialAutoConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private ConnectionSignUp myConnectionSignUp;

    @Override
    protectedConnectionFactory<? > 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);
        }
        returnrepository; }}Copy the code

MerryyouSpringSocialConfigurer custom login and registration links

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; }}Copy the code

Open SocialAuthenticationFilter filter

@Autowired
    private SpringSocialConfigurer merryyouSpringSocialConfigurer;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                .formLogin()// Use form login instead of default httpBasic
                .loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)// The URL to jump to if the requested URL requires authentication
                .loginProcessingUrl(SecurityConstants.DEFAULT_SIGN_IN_PROCESSING_URL_FORM)// Handle the custom login URL in the form
                .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()// None of the above requests require authentication
                //.antMatchers("/").access("hasRole('USER')")
                .and()
                .csrf().disable()// Disable CSRD interception
        ;
        // The security module is configured separately
        authorizeConfigProvider.config(http.authorizeRequests());
    }
Copy the code

The effect is as follows:

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/1/9/160da83974d9557b~tplv-t2oaga2asx-image.image

The code download

Download it from my Github, github.com/longfeizhen…