Series of articles:

  • Spring Security source analysis two: Spring Security authorization process
  • Spring Security source analysis a: Spring Security authentication process

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


  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

Spring-social-core provides the social connection framework and OAuth client support spring-social-config provides Java Configuring Spring-social-Security Some support Spring-social-Web to manage connections to Web applications

! - spring - social related - > < the 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


  1. ‘API’ defines the public interface of the API binding
  2. ‘config’ Some configuration information of 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 {/** * getUserInfo * @return */ QQUserInfo getUserInfo(); }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 Return */ private String openId; /** * private ObjectMapper ObjectMapper = new ObjectMapper(); Public QQImpl(String accessToken, String appId) {// Access_token is used 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); log.info(" [QQImpl] QQ_URL_GET_OPENID={} ", 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); log.info(" [QQImpl] QQ_URL_GET_USER_INFO={} ", 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 get 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] accessestr ={}" + 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); } /** * pit, * * @return */ @override protected RestTemplate createRestTemplate() {RestTemplate  restTemplate = super.createRestTemplate(); restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8"))); return restTemplate; }}Copy the code

QQServiceProvider Connects to the service provider

Public class QQServiceProvider extends AbstractOAuth2ServiceProvider < QQ > {/ access code * * * * / private static final String QQ_URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize"; / * * * get access_token is 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); }}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), new QQAdapter()); }}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 login Configuration ** @return */ @Bean public SpringSocialConfigurer merryyouSocialSecurityConfig() { String filterProcessesUrl = SecurityConstants.DEFAULT_SOCIAL_QQ_PROCESS_URL; MerryyouSpringSocialConfigurer configurer = new MerryyouSpringSocialConfigurer(filterProcessesUrl); return configurer; } /** * The utility class that handles the registration process * @param factoryLocator * @return */ @bean public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator factoryLocator) { return new ProviderSignInUtils(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 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; }}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 () / / login using the form, No longer using the default httpBasic way. LoginPage (SecurityConstants. DEFAULT_UNAUTHENTICATION_URL) / / if the requested URL need certification the jump of the URL LoginProcessingUrl (SecurityConstants. DEFAULT_SIGN_IN_PROCESSING_URL_FORM) / / deal with 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 () / / the above request do not require certification / /. AntMatchers ("/"). The access (" hasRole (' USER ') "), and () .csrf().disable()// Disables CSRD interception; / / security module configuration authorizeConfigProvider alone. Config (HTTP) authorizeRequests ()); }Copy the code

The effect is as follows:





The code download

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

Author: longfeizheng


Links:
Spring Security source code analysis three: Spring Social QQ Social login


Disclaimer: This article comes from paradise science and Technology cooperation community, copyright belongs to the author, please indicate the author’s source, thank you!