基于SpringSecurity OAuth2实现单点登录——集成Github实现第三方账户登录

1、前言

  在《SpringSecurity系列 之 集成第三方登录》中,我们基于SpringSecurity实现了集成的用户名密码、短信验证码和Github三种登录方式,其中,基于Github实现的登录,其实已经在SpringSecurity Oauth2中提供了一套实现流程,而且可以通过简单的配置就完成,我们下面尝试使用基于Oauth2的方式来实现Github的登录。

2、Github账户配置

  在使用Github实现登录的时候,首先需要在Github账户进行OAuth配置,和《SpringSecurity系列 之 集成第三方登录》中的配置方式一样,这里不再重复。

  完成Github配置后,我们需要得到Client ID、Client secrets和Authorization callback URL三个参数值,其中Authorization callback URL可以自定义,对应Github登录成功进行回调的地址。

3、Oauth2集成Github登录

  完成了上述准备工作,我们开始基于Oauth2实现Github的登录。首先,增加Oauth2所需要的的依赖,如下所示:

<dependency>
   <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-oauth2artifactId>
    <version>2.2.5.RELEASEversion>
dependency>

  然后,完成application.yml配置,具体内容如下:

server:
  port: 8080
  servlet:
    context-path: /oauth2
  tomcat:
    uri-encoding: UTF-8
security:
  oauth2:
    client:
      #对应Github账号配置的Client ID
      client-id: xxxxxx 
      #对应Github账号配置的Client secrets
      client-secret: xxxxxx 
      accessTokenUri: https://github.com/login/oauth/access_token
      userAuthorizationUri: https://github.com/login/oauth/authorize
      clientAuthenticationScheme: form
      #对应Github账号配置的Authorization callback URL
      registered-redirect-uri: ${site.baseUrl}/github_login 
      use-current-uri: false
    resource:
      userInfoUri: https://api.github.com/user
      preferTokenInfo: false
    sso:
      #对应Github账号配置的Authorization callback URL
      login-path: /github_login

  然后,配置启动类的单点登录,即在启动类上增加@EnableOAuth2Sso注解,如下所示:

@SpringBootApplication
@EnableOAuth2Sso
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}

  最后,增加一个测试Controller类,默认配置时,所有地址都需要鉴权后才可以访问,所以"/index"地址需要登录后,才能够进行访问,如下所示:

@RestController
public class IndexController {

    @GetMapping("/index")
    public String index(){
        return "Welcome to the index!";
    }
}

  经过上述配置,我们启动服务,当我们访问http://localhost:8080/oauth2/index 地址时,会自动跳转到Github的授权登录页,输入Github的用户名密码,登录授权成功后,就可以成功访问到了http://localhost:8080/oauth2/index 地址。

等待用户授权:
基于SpringSecurity OAuth2实现单点登录——集成Github实现第三方账户登录_第1张图片
登录成功后:
基于SpringSecurity OAuth2实现单点登录——集成Github实现第三方账户登录_第2张图片

4、原理分析

4.1、初始化一 —— @EnableOAuth2Sso注解

  在初始化Oauth2的时候,有两个入口,其中一个就是在启动类上添加的@EnableOAuth2Sso注解,还有一个就是SpringBoot会加载META-INF/spring.factories文件中的配置类。

  我们这里首先来分析@EnableOAuth2Sso注解主要实现了那些配置的初始化。首先,@EnableOAuth2Sso注解的定义如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableOAuth2Client
@EnableConfigurationProperties(OAuth2SsoProperties.class)
@Import({ OAuth2SsoDefaultConfiguration.class, OAuth2SsoCustomConfiguration.class,
		ResourceServerTokenServicesConfiguration.class })
public @interface EnableOAuth2Sso {

}

  在定义@EnableOAuth2Sso注解时,又用到了另外三个注解@EnableOAuth2Client、@Import、@EnableConfigurationProperties,我们分别进行分析:

@EnableConfigurationProperties

  通过@EnableConfigurationProperties(OAuth2SsoProperties.class)注解实现OAuth2SsoProperties属性对象的初始化,这里主要初始化了单点登录的登录页地址,即对应application.yml配置文件中的security.oauth2.sso.login-path属性值。

@ConfigurationProperties(prefix = "security.oauth2.sso")
public class OAuth2SsoProperties {

	public static final String DEFAULT_LOGIN_PATH = "/login";

	private String loginPath = DEFAULT_LOGIN_PATH;

	public String getLoginPath() {
		return this.loginPath;
	}
	public void setLoginPath(String loginPath) {
		this.loginPath = loginPath;
	}
}

@EnableOAuth2Client注解

  @EnableOAuth2Client注解,主要是通过@Import注解引入了OAuth2ClientConfiguration配置类,实现如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(OAuth2ClientConfiguration.class)
public @interface EnableOAuth2Client {

}

  在OAuth2ClientConfiguration配置类中,又通过@Bean注解,实现了OAuth2ClientContextFilter对象、AccessTokenRequest对象(生命周期为request)和OAuth2ClientContext对象三个对象的注入,具体实现可以查看OAuth2ClientConfiguration类的源码。

@Import

  在@EnableOAuth2Sso上的@Import注解,主要用来加载OAuth2SsoDefaultConfiguration、OAuth2SsoCustomConfiguration和ResourceServerTokenServicesConfiguration三个配置类。其中OAuth2SsoDefaultConfiguration 是配置默认的WebSecurity相关配置,而OAuth2SsoCustomConfiguration是配置自定义的WebSecurity相关配置,ResourceServerTokenServicesConfiguration 是配置ResourceServerTokenServices相关内容。

  而OAuth2SsoDefaultConfiguration和OAuth2SsoCustomConfiguration两个配置同时只会有一个生效,当@EnableOAuth2Sso注解在继承了WebSecurityConfigurerAdapter配置类的实现类上时,自定义配置类OAuth2SsoCustomConfiguration会生效,否则,默认的OAuth2SsoDefaultConfiguration配置类会生效。

  这里我们先分析一下OAuth2SsoDefaultConfiguration配置类,其中根据NeedsWebSecurityCondition 决定了OAuth2SsoDefaultConfiguration 配置类是否生效,而NeedsWebSecurityCondition 和 EnableOAuth2SsoCondition 条件类生效正好相反,而EnableOAuth2SsoCondition则是OAuth2SsoCustomConfiguration配置类是否生效的的判断条件,所以两个配置类只会有一个生效。

@Configuration
@Conditional(NeedsWebSecurityCondition.class)
public class OAuth2SsoDefaultConfiguration extends WebSecurityConfigurerAdapter {

	private final ApplicationContext applicationContext;

	public OAuth2SsoDefaultConfiguration(ApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
	}
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.antMatcher("/**").authorizeRequests().anyRequest().authenticated();
		new SsoSecurityConfigurer(this.applicationContext).configure(http);
	}
	//
	protected static class NeedsWebSecurityCondition extends EnableOAuth2SsoCondition {
		@Override
		public ConditionOutcome getMatchOutcome(ConditionContext context,
				AnnotatedTypeMetadata metadata) {
			return ConditionOutcome.inverse(super.getMatchOutcome(context, metadata));
		}
	}
}

  在上述OAuth2SsoDefaultConfiguration 类的configure(HttpSecurity http)方法中,实现了默认的HttpSecurity 配置。实现了所有请求,都需要认证的默认配置,同时通过创建SsoSecurityConfigurer配置类增加了一些额外的配置。

//SsoSecurityConfigurer.java
public void configure(HttpSecurity http) throws Exception {
		OAuth2SsoProperties sso = this.applicationContext
				.getBean(OAuth2SsoProperties.class);
		// Delay the processing of the filter until we know the
		// SessionAuthenticationStrategy is available:
		http.apply(new OAuth2ClientAuthenticationConfigurer(oauth2SsoFilter(sso)));
		addAuthenticationEntryPoint(http, sso);
	}

 &esmp;在上述SsoSecurityConfigurer配置类configure(HttpSecurity http)方法中,首先获取了OAuth2SsoProperties 参数对象,前面已经完成了该对象的初始化。然后又通过oauth2SsoFilter()方法创建了OAuth2ClientAuthenticationProcessingFilter对象,该对象类似于UsernamePasswordAuthenticationFilter,主要用于Oauth2认证的过滤器。然后,又通过创建OAuth2ClientAuthenticationConfigurer配置类,把OAuth2ClientAuthenticationProcessingFilter对象设置到了SpringSecurity过滤器链中,最后再通过http.apply()方法把OAuth2ClientAuthenticationConfigurer配置对象应用到HttpSecurity 中。其中,OAuth2ClientAuthenticationConfigurer配置类实现如下:

private static class OAuth2ClientAuthenticationConfigurer
	extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

	private OAuth2ClientAuthenticationProcessingFilter filter;

	OAuth2ClientAuthenticationConfigurer(
			OAuth2ClientAuthenticationProcessingFilter filter) {
		this.filter = filter;
	}

	@Override
	public void configure(HttpSecurity builder) throws Exception {
		OAuth2ClientAuthenticationProcessingFilter ssoFilter = this.filter;
		ssoFilter.setSessionAuthenticationStrategy(
				builder.getSharedObject(SessionAuthenticationStrategy.class));
		builder.addFilterAfter(ssoFilter,
				AbstractPreAuthenticatedProcessingFilter.class);
	}
}

  在SsoSecurityConfigurer配置类configure(HttpSecurity http)方法中,最后又通过addAuthenticationEntryPoint()方法,实现了AuthenticationEntryPoint对象的配置,其中为异常处理器配置默认配置了LoginUrlAuthenticationEntryPoint 和 HttpStatusEntryPoint两个AuthenticationEntryPoint对象,其中LoginUrlAuthenticationEntryPoint 对象 loginFormUrl参数还是用了前面配置的单点登录的地址。

 &emp;而ResourceServerTokenServicesConfiguration 配置类,主要用来ResourceServerTokenServices相关内容,默认会初始化UserInfoTokenServices等对象。这个时候会使用到ResourceServerProperties参数对象。

基于SpringSecurity OAuth2实现单点登录——集成Github实现第三方账户登录_第3张图片

4.2、初始化二 —— SpringBoot加载META-INF/spring.factories配置文件

  在Oauth2初始化过程中,除了前面根据@EnableOAuth2Sso注解实现的一部分初始化内容外,还有一部分内容是通过SpringBoot加载META-INF/spring.factories配置文件进行加载的。

  首先,在spring-security-oauth2-autoconfigure.jar中存在META-INF/spring.factories配置文件,内容如下:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration

  根据SpringBoot运行机制,在SpringBoot项目启动时,就会加载OAuth2AutoConfiguration配置类。而配置类OAuth2AutoConfiguration的实现如下:

@Configuration
@ConditionalOnClass({ OAuth2AccessToken.class, WebMvcConfigurer.class })
@Import({ OAuth2AuthorizationServerConfiguration.class,
		OAuth2MethodSecurityConfiguration.class, OAuth2ResourceServerConfiguration.class,
		OAuth2RestOperationsConfiguration.class })
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties(OAuth2ClientProperties.class)
public class OAuth2AutoConfiguration {

	private final OAuth2ClientProperties credentials;

	public OAuth2AutoConfiguration(OAuth2ClientProperties credentials) {
		this.credentials = credentials;
	}
	@Bean
	public ResourceServerProperties resourceServerProperties() {
		return new ResourceServerProperties(this.credentials.getClientId(),
				this.credentials.getClientSecret());
	}
}

  在OAuth2AutoConfiguration 配置类中,初始化了OAuth2ClientProperties、ResourceServerProperties 两个参数对象,其中OAuth2ClientProperties通过@EnableConfigurationProperties注解初始化,ResourceServerProperties 对象通过@Bean注解实现,并把OAuth2ClientProperties中的clientId和clientSecret作为参数传递其中。

  通过@Import注解,又引入了OAuth2AuthorizationServerConfiguration、OAuth2MethodSecurityConfiguration、OAuth2ResourceServerConfiguration、OAuth2RestOperationsConfiguration四个配置类,分别对应授权服务、安全表达式处理器、资源服务器和客户端四类内容,如下所示:
基于SpringSecurity OAuth2实现单点登录——集成Github实现第三方账户登录_第4张图片
  其中,

  • OAuth2AuthorizationServerConfiguration配置类,主要是用来配置授权服务器的。如果AuthorizationServerConfigurer配置类存在或者不需要激活授权服务器配置(没有使用@EnableAuthorizationServer注解),该配置类将不会生效。
  • OAuth2ResourceServerConfiguration配置类,主要用于配置资源服务器。当ResourceServerConfigurer存在或没有启用资源服务器(没有使用@EnableResourceServer注解)时,该配置不会再被加载。
  • OAuth2RestOperationsConfiguration配置类,主要用来配置单点登录的相关操作。当有@EnableOAuth2Client注解时启用(@EnableOAuth2Sso注解包含了@EnableOAuth2Client注解,所以@EnableOAuth2Sso注解时也会生效)。
  • OAuth2MethodSecurityConfiguration 用于配置安全表达式解析的处理器。

  当我们使用@EnableOAuth2Sso注解启用单点登录时,主要是启用了OAuth2RestOperationsConfiguration配置类,在该配置类中,注入了ClientCredentialsResourceDetails、DefaultOAuth2ClientContext和FilterRegistrationBean等对象,其中FilterRegistrationBean对象实现了OAuth2ClientContextFilter过滤器的添加,具体实现如下:

@Bean
public FilterRegistrationBean<OAuth2ClientContextFilter> oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter, SecurityProperties security) {
		FilterRegistrationBean<OAuth2ClientContextFilter> registration = new FilterRegistrationBean<>();
		registration.setFilter(filter);
		registration.setOrder(security.getFilter().getOrder() - 10);
		return registration;
	}
}

  在这部分的初始化过程中,主要实现了把前面已经实例化的OAuth2ClientContextFilter过滤器对象添加到SpringMVC的过滤器链中,同时还注入了一个生命周期为request的DefaultOAuth2ClientContext对象。

4.3、流程分析

  当 访问http://localhost:8080/oauth2/index地址(未认证)时,首先,经过OAuth2ClientContextFilter过滤器,然后进入SpringSecurity过滤器链FilterChainProxy中,因为没有经过认证,所以会最终会跳转到登录页面,具体可以参考《未认证的请求是如何重定向到登录地址的?》。因为我们配置了单点登录的登录页地址"/github_login",所以跳转地址如下所示:
在这里插入图片描述
  再次跳转访问"/github_login"地址时,经过OAuth2ClientContextFilter过滤器,然后进入SpringSecurity过滤器链FilterChainProxy中的OAuth2ClientAuthenticationProcessingFilter过滤器,这个时候因为访问的是登录地址,所以会执行到attemptAuthentication()方法进行处理,因为没有授权,所以在通过restTemplate.getAccessToken()获取token时,会抛出UserRedirectRequiredException异常,该异常会被OAuth2ClientContextFilter过滤器捕获,然后调用redirectUser()方法,最终跳转到了github的认证地址。
基于SpringSecurity OAuth2实现单点登录——集成Github实现第三方账户登录_第5张图片
  经过上述重定向,就跳转到了Github提供的授权认证界面,输入用户名密码,并进行登录。这个时候,验证成功后,又会跳回/github_login地址,,而且这次携带了一个state状态码。
基于SpringSecurity OAuth2实现单点登录——集成Github实现第三方账户登录_第6张图片
  这个时候,再次请求/github_login地址时,因为携带了state参数,所以这次再attemptAuthentication()方法中,通过调用restTemplate.getAccessToken()方法获取token时,就不会再抛出异常了,而是获取到了accessToken信息。然后继续执行attemptAuthentication()方法中后续代码,又调用tokenServices.loadAuthentication(accessToken.getValue());获取用户信息,这个时候就开始执行认证成功后的相关逻辑了。
基于SpringSecurity OAuth2实现单点登录——集成Github实现第三方账户登录_第7张图片

你可能感兴趣的:(Spring,Spring,Cloud,github,java,spring,boot)