Spring Security OAuth2 第三方登录(一)之流程说明

SpringBoot第三方登录流程

在这里插入图片描述

OAuth2AuthenticationService

该类执行获取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;
        }
    }

}

SocialConfigurerAdapter

为第三方登录添加一些组件到容器,比如SpringSocialConfigurer(只是添加到容器中,需在WebSecurityConfigurerAdapter应用才可生效),JdbcUsersConnectionRepository等

  • 添加Filter
    SpringSecurity在添加验证时都是通过在其FilterChain上添加Filter来实现,第三方登录需要配置的是AutenticationFilter
    代码演示
@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;
    }
}
  • 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);
}
  • WebSecurityConfigurerAdapter

配置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();
    }

  • 配置SocialConfigurerAdapter
    (可以配置多个,若不重写getUsersConnectionRepository()方法会直接调用父类方法)
    SocialConfigurerAdapter为配置类,直接添加到容器中即可,可以为容器添加ConnectionFactory和UsersConnectionRepository等
@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());
    }
}
  • QQServiceProvidor

包含两个类(换句话说可以获得两个关键类)
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);
    }


}
  • QQOAuth2Template()

用于完成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;
    }
}
  • AbstractOAuth2ApiBinding
    该类定义了获取第三方信息的一些公用方法和属性(如RestTemplate),需要被实现其构造方法,本例为QQImp
@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);
        }
    }
}

  • OAuth2ConnectionFactory
    创建connection
    获取ServiceProvier,实现获取code和Token
    获取ApiAdapter,实现通过Api获取第三方应用信息与connection匹配
public class QQConnectionFactroy extends OAuth2ConnectionFactory<QQ> {

    public QQConnectionFactroy(String providerId, String appId, String appSecret) {
        super(providerId, new QQServiceProvidor(appId, appSecret), new QQAdapter());
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_42074868/article/details/85561171