该类执行获取code,token,创建connection,由SocailAuthenticationFilter调用
public class OAuth2AuthenticationService<S> extends AbstractSocialAuthenticationService<S> {
public SocialAuthenticationToken getAuthToken(HttpServletRequest request, HttpServletResponse response) throws SocialAuthenticationRedirectException {
String code = request.getParameter("code");
if (!StringUtils.hasText(code)) {
OAuth2Parameters params = new OAuth2Parameters();
//拼接redirectUrl
params.setRedirectUri(this.buildReturnToUrl(request));
this.setScope(request, params);
params.add("state", this.generateState(this.connectionFactory, request));
this.addCustomParameters(params);
throw new SocialAuthenticationRedirectException(this.getConnectionFactory().getOAuthOperations().buildAuthenticateUrl(params));
} else if (StringUtils.hasText(code)) {
try {
String returnToUrl = this.buildReturnToUrl(request);
//获取token,并返回AccessGrant
AccessGrant accessGrant = this.getConnectionFactory().getOAuthOperations().exchangeForAccess(code, returnToUrl, (MultiValueMap)null);
//创建connection
Connection<S> connection = this.getConnectionFactory().createConnection(accessGrant);
return new SocialAuthenticationToken(connection, (Map)null);
} catch (RestClientException var7) {
this.logger.debug("failed to exchange for access", var7);
return null;
}
} else {
return null;
}
}
}
为第三方登录添加一些组件到容器,比如SpringSocialConfigurer(只是添加到容器中,需在WebSecurityConfigurerAdapter应用才可生效),JdbcUsersConnectionRepository等
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {
@Autowired
DataSource dataSource;
@Autowired
SecurityProperties securityProperties;
@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
return new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
}
//配置将 SpringSocialConfigurer添加到容器中,
//SpringSocialConfigurer的构造方法会在FilterChain上添加AutenticationFilter
//配置自定义SpringSocialConfigurer,需要在
@Bean
public SpringSocialConfigurer imoocSocialSecurityConfig(){
String filterProcessesUrl = securityProperties.getSocial().getFilterProcessesUrl();
SpringSocialConfigurer springSocialConfigurer = new ImoocSpringSocialConfigurer(filterProcessesUrl);
return springSocialConfigurer;
}
}
该类为SpringSecurity filterchain上添加SocialAuthenticationFilter,需要在WebSecurityConfigurerAdapter上应用
public void configure(HttpSecurity http) throws Exception {
ApplicationContext applicationContext = (ApplicationContext)http.getSharedObject(ApplicationContext.class);
UsersConnectionRepository usersConnectionRepository = (UsersConnectionRepository)this.getDependency(applicationContext, UsersConnectionRepository.class);
SocialAuthenticationServiceLocator authServiceLocator = (SocialAuthenticationServiceLocator)this.getDependency(applicationContext, SocialAuthenticationServiceLocator.class);
SocialUserDetailsService socialUsersDetailsService = (SocialUserDetailsService)this.getDependency(applicationContext, SocialUserDetailsService.class);
//filter
SocialAuthenticationFilter filter = new SocialAuthenticationFilter((AuthenticationManager)http.getSharedObject(AuthenticationManager.class), (UserIdSource)(this.userIdSource != null ? this.userIdSource : new AuthenticationNameUserIdSource()), usersConnectionRepository, authServiceLocator);
//.......
http.authenticationProvider(new SocialAuthenticationProvider(usersConnectionRepository, socialUsersDetailsService))
//添加filter,可重写**postProcess**方法定制filter
.addFilterBefore((Filter)this.postProcess(filter), AbstractPreAuthenticatedProcessingFilter.class);
}
配置SpringSecurity,包括添加filter,定义拦截路径等
@Override
protected void configure(HttpSecurity http) throws Exception {
validetCodeFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);
validetCodeFilter.setSecurityProperties(securityProperties);
validetCodeFilter.afterPropertiesSet();
log.info("authentication");
http.addFilterBefore(validetCodeFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin()
.loginPage("/authentication/requrie")
.loginProcessingUrl("/authentication/form")
.successHandler(imoocAuthenticationSuccessHandler)
.failureHandler(imoocAuthenticationFailureHandler)
.and()
//**应用SpringSocialConfigurer**
.apply(imoocSocialSecurityConfig)
.and()
.authorizeRequests()
.antMatchers("/authentication/requrie","/code/image","/login",securityProperties.getBrowser().getLoginPage()).permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable();
}
@Configuration
@ConfigurationProperties(prefix = "imooc.sercurity.social.qq", value = "appId")
public class QQAutoConfig extends SocialConfigurerAdapter {
@Autowired
private SecurityProperties securityProperties;
//自动配置
@Override
public void addConnectionFactories(ConnectionFactoryConfigurer connectionFactoryConfigurer, Environment environment) {
connectionFactoryConfigurer.addConnectionFactory(creatConnectionFactroy());
}
@Override
public UserIdSource getUserIdSource() {
return super.getUserIdSource();
}
//项目中返回为null,UsersConnectionRepository是配置在另一个SocialConfigurerAdapter中的,因为第三方登录可以为多个,而UsersConnectionRepository只需要一个
@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
return new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
}
private ConnectionFactory<?> creatConnectionFactroy(){
QQProperties qq =securityProperties.getSocial().getQq();
return new QQConnectionFactroy(qq.getProviderId(),qq.getAppId(),qq.getAppSecret());
}
}
ConnectionFactroy(OAuth2ConnectionFactory>)
其父类构造方法参数:
String providerId 第三方应用id,由自己定义,最好设计为可配置的,请求URI与之有关
QQServiceProvidor(String, String) 其两个参数为申请的appId,和appSecret
QQAdapter api适配器, 将第三方返回的数据转为springSecurity规范的数据,然后通过JdbcUsersConnectionRepository存入数据库
public class QQConnectionFactroy extends OAuth2ConnectionFactory<QQ> {
public QQConnectionFactroy(String providerId, String appId, String appSecret) {
super(providerId, new QQServiceProvidor(appId, appSecret), new QQAdapter());
}
}
包含两个类(换句话说可以获得两个关键类)
QQOAuth2Template(appId, appSecret, URL_AUTHORIZE, URL_ACCESS_TOKNE))
QQImpl(accessToken, appId) api的实现
public class QQServiceProvidor extends AbstractOAuth2ServiceProvider<QQ> {
//定义了获取Code和Token的url
private static final String URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize";
private static final String URL_ACCESS_TOKNE ="https://graph.qq.com/oauth2.0/token";
private String appId;
public QQServiceProvidor(String appId, String appSecret) {
super(new QQOAuth2Template(appId, appSecret, URL_AUTHORIZE, URL_ACCESS_TOKNE));
this.appId = appId;
}
@Override
public QQ getApi(String accessToken) {
return new QQImpl(accessToken, appId);
}
}
用于完成OAuth2协议的获取Code和token
public class OAuth2Template implements OAuth2Operations {
private final String clientId;
private final String clientSecret;
private final String accessTokenUrl;
private final String authorizeUrl;
private String authenticateUrl;
//需要注意的是
private RestTemplate restTemplate;
private boolean useParametersForClientAuthentication;
public OAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
this(clientId, clientSecret, authorizeUrl, (String)null, accessTokenUrl);
}
//构造器中为clientId(appId),clientSecret,authorizeurl(获取code的url),accessTokenUrl(获取Token的url),均有ServiceProvider赋值
public OAuth2Template(String clientId, String clientSecret, String authorizeUrl, String authenticateUrl, String accessTokenUrl) {
Assert.notNull(clientId, "The clientId property cannot be null");
Assert.notNull(clientSecret, "The clientSecret property cannot be null");
Assert.notNull(authorizeUrl, "The authorizeUrl property cannot be null");
Assert.notNull(accessTokenUrl, "The accessTokenUrl property cannot be null");
this.clientId = clientId;
this.clientSecret = clientSecret;
String clientInfo = "?client_id=" + this.formEncode(clientId);
//拼接获取code的url,为url添加encode后的appid参数,
//若重写方法没有拼接需要重写方法buildAuthenticateUrl()
this.authorizeUrl = authorizeUrl + clientInfo;
if (authenticateUrl != null) {
this.authenticateUrl = authenticateUrl + clientInfo;
} else {
this.authenticateUrl = null;
}
this.accessTokenUrl = accessTokenUrl;
}
//
public String buildAuthenticateUrl(OAuth2Parameters parameters) {
return this.authenticateUrl != null ? this.buildAuthUrl(this.authenticateUrl, GrantType.AUTHORIZATION_CODE, parameters) : this.buildAuthorizeUrl(GrantType.AUTHORIZATION_CODE, parameters);
}
protected RestTemplate createRestTemplate() {
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactorySelector.getRequestFactory();
RestTemplate restTemplate = new RestTemplate(requestFactory);
List<HttpMessageConverter<?>> converters = new ArrayList(2);
//未添加StringHttpMessageConverter,不能处理text/html
//若需要处理则需要重写该方法,为RestTemplate添加上
converters.add(new FormHttpMessageConverter());
converters.add(new FormMapHttpMessageConverter());
converters.add(new MappingJackson2HttpMessageConverter());
//注意restTemplate调用该方法会将其构造方法添加converter去除,然后添加
restTemplate.setMessageConverters(converters);
restTemplate.setErrorHandler(new LoggingErrorHandler());
if (!this.useParametersForClientAuthentication) {
List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if (interceptors == null) {
interceptors = new ArrayList();
restTemplate.setInterceptors((List)interceptors);
}
((List)interceptors).add(new PreemptiveBasicAuthClientHttpRequestInterceptor(this.clientId, this.clientSecret));
}
return restTemplate;
}
}
@Slf4j
public class QQImpl extends AbstractOAuth2ApiBinding implements QQ {
private static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s";
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){
//调用父类构造方法,设置默认属性,即在RestTemplate发送请求是在请求url中加上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);
this.openId = StringUtils.substringBetween(result,"\"openid\":","}");
log.info(result);
}
@Override
public QQUserInfo getQQUserInfo(){
String url = String.format(URL_GET_USERINFO, appId, openId);
String result = getRestTemplate().getForObject(url, String.class);
try {
QQUserInfo qqUserInfo = objectMapper.readValue(result, QQUserInfo.class);
qqUserInfo.setOpenId(openId);
return qqUserInfo;
} catch (IOException e) {
throw new RuntimeException("获取用户信息失败", e);
}
}
}
public class QQConnectionFactroy extends OAuth2ConnectionFactory<QQ> {
public QQConnectionFactroy(String providerId, String appId, String appSecret) {
super(providerId, new QQServiceProvidor(appId, appSecret), new QQAdapter());
}
}