OAuth2使用文档

翻译自:spring-security-oauth2-boot

写在前面的话:
一直在学习Spring OAuth2 ,但是网上的讲解材料让我看了头疼,而且一直也没有能让我一次就顺利运行起来的例子。不得已只能翻看官方文档。但是以我半吊子英语水平,很多句子的意思都是很模糊,甚至有时候猜测的意思完全相反。正好最近趁着在家,尝试翻译一下官方文档。
受英语水平所限,尽管有有道翻译和GOOGLE翻译,难免有地方翻译不准确或描述不准确,当然亦有可能有各种错误。如果你看到错误或有任何建议,欢迎留言反馈和讨论。如果你想转载,请注明出处: https://blog.csdn.net/levelmini

PS:

  1. 文章由我一个人翻译完成,没有校对。而且部分内容为机翻,我没有修改润色。
  2. Google翻译比有道翻译好用太多啦。
  3. 翻译对我这样的人来说真是辛苦活。向所有的官方文档翻译人员致敬。
  4. CSDN的博客好多都是无脑转载,而且没有原文链接,说自己原创。希望这种情况尽快改善。要不然我就要转战其他博客啦。
  5. MD格式真好用。但是csdn的Markdown编辑器的章节内跳转为啥不好使呢?

OAuth2 Boot


如果你在classpath中引入了 spring-security-oauth2,你可以利用一些自动配置来简化授权和资源服务端的设置。想要获取所有细节,访问 Spring Security OAuth 2 Developers Guide.

以下项目在正在持续维护中:

  • spring-security-oauth2
  • spring-security-oauth2-autoconfigure

欢迎使用这些项目,我们为你提供帮助。
然而,在选择spring-security-oauth2spring-security-oauth2-autoconfigure之前,你应该检查Spring Security的特性矩阵,看看新的支持是否满足您的需要。

(以下内容为机翻)
该项目是Spring Boot 1.x附带的Spring安全OAuth支持的一个端口。在Spring Boot 2中删除了支持以支持Spring Security 5的一流OAuth支持
This project is a port of the Spring Security OAuth support that came with Spring Boot 1.x. Support was removed in Spring Boot 2.x in favor of Spring Security 5’s first-class OAuth support.

为了简化迁移,这个项目作为旧Spring安全OAuth支持和Spring Boot 2.x之间的桥梁存在。
To ease migration, this project exists as a bridge between the old Spring Security OAuth support and Spring Boot 2.x.
(机翻完毕)

1. 授权服务端

Spring Security OAuth2 Boot 简化了OAuth 2.0授权服务端的部署。

1.1. 我需要建立自己的授权服务端吗?

你需要部署你自己的授权服务端,如果:

  • 您希望将登录、退出和密码恢复的操作委托给一个单独的服务(也称为身份联合),并由您自己管理。
  • 您希望对这个单独的服务使用OAuth 2.0协议来与其他服务进行协调。

1.2. Dependencies

要使用此库中的auto-configuration功能,你需要spring-security-oauth2,它具有OAuth 2.0基本功能和spring-security-autoconfigure。请注意你需要指定spring-security-autoconfigure的版本,因为其不再由Spring Boot管理,但需要和Spring Boot的版本兼容。
如果需要支持JWT,你需要spring-security-jwt

1.3. 最小化的 OAuth2 Boot 配置

创建最小的Spring Boot 授权服务端需要包含三个基本步骤:

  1. 引入相关依赖。
  2. 引入@EnableAuthorizationServer注解。
  3. 指定至少一个 client id 和 secret对。

1.3.1. 激活授权服务端

像其他的Spring Boot @Enable注解一样,你可以在包含main方法的类中添加@EnableAuthorizationServer注解,例:

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

添加这个注解会引入其他Spring的配置文件,这些文件会天界一些合理的默认值,比如Token如可被签名、Token的持续时间、以及允许的授权范围。

1.3.2. 指定Client和Secret

根据规范,许多OAuth 2.0端点都需要客户端身份授权,因此您需要至少指定一个客户端,以便任何人都可以与您的授权服务端进行通信。
下面这个例子展示了如何指定一个客户端:

security:
  oauth2:
    client:
      client-id: first-client
      client-secret: noonewilleverguess

尽管这很方便,但是生产环境中你需要做更多事情。(比如将此配置移入数据库中来更灵活的管理,译者注)

1.3.3. 获取Token

OAuth 2.0本质上是一个框架,它指定了将长期的令牌替换为短期的令牌的策略。
默认情况下,@ EnableAuthorizationServer授予客户端访问客户端凭据的权限,这意味着您可以执行以下操作:
curl first-client:noonewilleverguess@localhost:8080/oauth/token -dgrant_type=client_credentials -dscope=any
应用会响应一个token,类似:

{
    "access_token" : "f05a1ea7-4c80-4583-a123-dc7a99415588",
    "token_type" : "bearer",
    "expires_in" : 43173,
    "scope" : "any"
}

此令牌可以提供给任何支持不透明的OAuth 2.0令牌的资源服务端,并配置为指向此授权服务端进行授权。
你可以从此跳到如下章节:

  • [章节1.4,如何关闭OAuth2 Boot 的自动配置](# 1.4. 如何关闭OAuth2 Boot 的自动配置)
  • [章节1.5, 如何创建Authorization Code授权模式](# 1.5. 如何创建Authorization Code授权模式)
  • [章节1.6, 如何创建Password授权模式](# 1.6. 如何创建Password授权模式)
  • [章节1.7,如何以及何时向授权服务端提供AuthenticationMamager](# 1.7. 如何以及何时向授权服务端提供AuthenticationMamager)
  • [章节1.8,授权服务端兼容Spring Security 5.1的资源服务端和客户端吗?](# 1.8. 授权服务端兼容Spring Security 5.1的资源服务端和客户端吗?)
  • [如何配置Jwt Tokens](# 2.2.2.1. JWT)

1.4. 如何关闭OAuth2 Boot 的自动配置

基本上,OAuth2 Boot项目会创建一个AuthorizationServerConfigurer实例,该实例带有一些合理的默认值:

  • 注册了一个NoOpPasswordEncoder(覆盖SpringSeuciryt默认值
  • 使你提供的客户端可以使用此授权服务端提供的任何一个授权模式:authorization_codepasswordclient_credentialsimplicitrefresh_token.
    另外,项目亦会尝试创建一些有用的bean,他们的名字是:
  • AuthenticationManager:用于寻找终端用户(非客户端)
  • TokenStore:用于生成和检索令牌。
  • AccessTokenConverter:用于将访问令牌转化成不同的格式,如JWT。

虽然本文档简单介绍了这些bean的作用,但Spring Security Oauth文档更适合获取他们的基础信息。

如果你声明了一个类型为AuthorizationServerConfigurer的bean,那么这些不会自动完成。
因此,如果您需要配置多个客户端,更改其允许的授权类型,或使用比NoOpPasswordEncoder更好的方法(强烈建议!),那么您需要定义自己的AuthorizationServerConfigurer,如:

@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired DataSource dataSource;

    protected void configure(ClientDetailsServiceConfigurer clients) {
        clients
            .jdbc(this.dataSource)
            .passwordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
    }
}

上面的配置使OAuth2 Boot不再从环境属性中检索客户端,并回退到Spring Security密码编码器的默认值。

读到这,你可能想要了解更多信息比如:

  • [章节1.5 如何创建Authorization Code授权模式](# 1.5. 如何创建Authorization Code授权模式)
  • [章节1.6 如何创建Password授权模式](# 1.6. 如何创建Password授权模式)

1.5. 如何创建Authorization Code授权模式

使用默认配置时,从技术上讲,Authorization Code模式已经可行,但实际上其仍然未配置完全。
因为除了预先的配置外,Authorization Code模式还需要:

  • 终端用户
  • 终端用户的登录流程
  • 在客户端中注册一个重定向URI

1.5.1. 添加终端用户

在受Spring Security保护的典型Spring Boot应用程序中,用户由UserDetailsService定义。而授权服务端也一样:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return new InMemoryUserDetailsManager(
            User.withDefaultPasswordEncoder()
                .username("enduser")
                .password("password")
                .roles("USER")
                .build());
    }
}

请注意,像典型的Spring Security Web应用一样,用户在WebSecurityConfigurerAdapter实例中定义。

1.5.2. 添加终端用户的登录流程

顺便一提,添加WebSecurityConfigurerAdapter实例是我们现在为终端用户用户添加表单登录流程所需要的。但是,请注意,这是有关Web应用程序本身的所有其他配置(而不是OAuth 2.0 API)的地方。
如果你想自定义登录页,WebSecurityConfigurerAdapter不仅可以为用户提供表单登录(form login),还可以提供其他支持(如密码恢复,password recovery)。

1.5.3. 在客户端中注册一个重定向URI

OAuth2 Boot不支持将重定向URI配置为属性(例如,与client-idclient-secret一起使用)。
要添加重定向URI,您需要使用InMemoryClientDetailsServiceJdbcClientDetailsService指定客户端。
使用任意一个都意味着你将AuthorizationServerConfigurer替换为你自己定义的,如下例所示:

@Configuration
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Bean
    PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    protected void configure(ClientDetailsServiceConfigurer clients) {
        clients
            .inMemory()
                .withClient("first-client")
                .secret(passwordEncoder().encode("noonewilleverguess"))
                .scopes("resource:read")
                .authorizedGrantTypes("authorization_code")
                .redirectUris("http://localhost:8081/oauth/login/client-app");
    }
}

1.5.4. 测试授权流程

OAuth的流程不好测试,因为那需要不止一个服务端来测试完整的流程(这是来自官方的吐槽)。然而测试的第一步却很简单:

  1. 访问 http://localhost:8080/oauth/authorize?grant_type=authorization_code&response_type=code&client_id=first-client&state=1234
  2. 如果用户尚未登录,会重定向到登录页面,如 http://localhost:8080/login
  3. 一旦用户登录成功,应用会生成code并重定向到注册的重定向URI(在这个例子中是 http://localhost:8081/oauth/login/client-app)
    这样,任何使用 Autnorization Code 授权模式的资源服务端就可以在指向授权服务端实例的情况下继续授权流程。

1.6. 如何创建Password授权模式

使用默认配置时,Password授权模式从技术上讲是可以运行的,但其同 Autnorization Code授权模式所面临的问题一样,缺少用户。
也就是说,由于默认配置创建了一个具有用户名和随机密码的user,你可以尝试检查日志,找到password后执行一下操作:
curl first-client:noonewilleverguess@localhost:8080/oauth/token -dgrant_type=password -dscope=any -dusername=user -dpassword=the-password-from-the-logs
当你执行此命令,你会获得一个token。
大多数情况,你想要设置多个用户。
正如在[章节1.5,如何创建Authorization Code授权模式](# 1.5. 如何创建Authorization Code授权模式)中所述,在Spring Security中,用户通常被UserDetailsService所指定,这并没有什么区别。例:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return new InMemoryUserDetailsManager(
            User.withDefaultPasswordEncoder()
                .username("enduser")
                .password("password")
                .roles("USER")
                .build());
    }
}

这就是我们需要所的所有工作。我们不需要重写AuthorizationServerConfigurer,因为client ID和secret已经通过环境属性(environment properties)所指定。
此时,下面这个指令应该就有用了。
curl first-client:noonewilleverguess@localhost:8080/oauth/token -dgrant_type=password -dscope=any -dusername=enduser -dpassword=password

1.7. 如何以及何时向授权服务端提供AuthenticationMamager

这是一个很常见的问题,因为在AuthorizationServerEndpointsConfigurer并非很直观的指定AuthenticationManager实例。简单来说,答案是:仅在资源服务端[使用 Password 授权模式](# 1.6. 如何创建Password授权模式)时。
记住以下基本知识会帮你判断:

  • AuthenticationManager对用户来说是抽象的,他通常需要指定如UserDetailsService这样的类型才能完成其功能。
  • 终端用户通过WebSecurityConfigurerAdapter来指定。
  • OAuth2 Boot 默认自动获取任何已经定义的AuthenticationManager.

然而,不是所有流程都需要AuthenticationManager,因为不是所有流程都涉及到终端用户。比如,客户凭证流程只需要一个基于客户端权限的token(而不是需要终端用户)。此时 Refresh Token 流程需要一个仅基于 Refresh Token 的权限的Token。
当然,也不是所有流程都需要明确要求 OAuth 2.0 API 本身具有AuthenticationManager,举个例子, Authorization Code 授权模式和 Implicit 授权模式在用户登录(应用本身的流程)时授权用户,而不是在请求Token(OAuth 2.0 API)时授权用户。

只有当资源服务端的流程返回一个基于code的终端用户凭证,这才以为这你的授权服务端需要一个AuthenticationManager
下面这个示例展示了资源服务端配置请求Password授权模式:
.authorizedGrantTypes("password", ...)
上述的例子中,你的授权服务端需要一个AuthenticationManager
有几个办法可以做到这点(还记得之前说的基本知识吗):

  • 使用OAuth2 Boot的默认配置(不要手动配置AuthorizationServerConfigurer),并[定义UserDetailsService](# 1.7.1. 定义UserDetailsService)
  • 使用OAuth2 Boot的默认配置,并[定义AuthenticationManager](# 1.7.2. 定义AuthenticationManager)
  • 重写AuthorizationServerConfigurerAdapter(使OAuth2 Boot’s默认配置失效)并[定义AuthenticationConfiguration](# 1.7.3. 定义AuthenticationConfiguration)
  • 重写AuthorizationServerConfigurerAdapter并手动配置[手动配置AuthenticationManager](# 1.7.4. 手动配置AuthenticationManager)

1.7.1. 定义UserDetailsService

终端用户被WebSecurityConfigurerAdapterUserDetailsService指定,所以如果你使用了OAuth2 Boot的默认配置(意思是你没有实现AuthorizationServerConfigurer接口),你可以仅提供一个UserDetailsService然后结束配置工作。如:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired DataSource dataSource;

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return new JdbcUserDetailsManager(this.dataSource);
    }
}

1.7.2. 定义AuthenticationManager

如果你想对AuthenticationManager做一些个性的配置,可以在WebSecurityConfigurerAdapter中实施,并将其声明为一个bean。比如:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean(BeansId.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(customAuthenticationProvider());
    }
}

如果你用的是OAuth2 Boot默认配置,那么这个bean会被自动创建。

1.7.3. 定义AuthenticationConfiguration

AuthenticationConfiguration中你可以做任何关于AuthenticationManager的配置。这意味着,如果你需要配置AuthorizationServerConfigurer(比如需要往其中注入一些自己的bean),你可以自定义配置类并使其继承AuthorizationServerConfigurerAdapter,然后在其中获取AuthenticationManager的bean,比如这样:

@Component
public class CustomAuthorizationServerConfigurer extends
    AuthorizationServerConfigurerAdapter {

    AuthenticationManager authenticationManager;

    public CustomAuthorizationServerConfigurer(AuthenticationConfiguration authenticationConfiguration) {
        this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) {
        // .. your client configuration that allows the password grant
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager);
    }
}
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        return new MyCustomUserDetailsService();
    }
}

1.7.4. 手动配置AuthenticationManager

在最复杂的情况下,AuthenticationManager需要做一些特别配置,同时你有自己的AuthenticationServerConfigurer,那么你需要同时创建你自己的AuthorizationServerConfigurerAdapterWebSecurityConfigurerAdapter

@Component
public class CustomAuthorizationServerConfigurer extends
    AuthorizationServerConfigurerAdapter {

    AuthenticationManager authenticationManager;

    public CustomAuthorizationServerConfigurer(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) {
        // .. your client configuration that allows the password grant
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager);
    }
}
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean(BeansId.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(customAuthenticationProvider());
    }
}

1.8. 授权服务端兼容Spring Security 5.1的资源服务端和客户端吗?

不。Spring Security 5.1 只支持JWT编码和JWK签名授权。并且授权服务端不支持JWK集URI。
但其有一些基础支持,
为了将授权服务端和Spring Security 5.1的资源端进行适配,你需要做:

  • 配置授权服务端使用JWK
  • 添加JWK集URI终端

1.8.1. 配置授权服务端使用JWK

为更改访问和刷新令牌的格式,你可以变更AccessTokenConverterTokenStore。例:

@EnableAuthorizationServer
@Configuration
public class JwkSetConfiguration extends AuthorizationServerConfigurerAdapter {

	AuthenticationManager authenticationManager;
	KeyPair keyPair;

	public JwkSetConfiguration(AuthenticationConfiguration authenticationConfiguration,
			KeyPair keyPair) throws Exception {

		this.authenticationManager = authenticationConfiguration.getAuthenticationManager();
		this.keyPair = keyPair;
	}

    // ... client configuration, etc.

	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
		// @formatter:off
		endpoints
			.authenticationManager(this.authenticationManager)
			.accessTokenConverter(accessTokenConverter())
			.tokenStore(tokenStore());
		// @formatter:on
	}

	@Bean
	public TokenStore tokenStore() {
		return new JwtTokenStore(accessTokenConverter());
	}

	@Bean
	public JwtAccessTokenConverter accessTokenConverter() {
		JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
		converter.setKeyPair(this.keyPair);
		return converter;
	}
}

1.8.2. 添加JWK集URL终端

Spring Security OAuth不支持JWK,同时@EnableAuthorizationServer注解也不支持在初始化集中添加更多OAUTH 2.0 API。不过,我们可以通过添加少量代码来实现。
首先,你需要添加另外一个依赖包:com.nimbusds:nimbus-jose-jwt。这个包给你提供了JWK的原生支持
然后,不要使用@EnableAuthorizationServer注解,直接使用两个@Configuration类即可:

  • AuthorizationServerEndpointsConfiguration:配置OAUTH 2.0 API终端(如token要使用什么格式)的@Configuration类。
  • AuthorizationServerSecurityConfiguration:配置终端的访问权限的@Configuration类。这个类你需要像下例中一样被其他类继承:
@FrameworkEndpoint
class JwkSetEndpoint {
	KeyPair keyPair;

	public JwkSetEndpoint(KeyPair keyPair) {
		this.keyPair = keyPair;
	}

	@GetMapping("/.well-known/jwks.json")
	@ResponseBody
	public Map<String, Object> getKey(Principal principal) {
		RSAPublicKey publicKey = (RSAPublicKey) this.keyPair.getPublic();
		RSAKey key = new RSAKey.Builder(publicKey).build();
		return new JWKSet(key).toJSONObject();
	}
}
@Configuration
class JwkSetEndpointConfiguration extends AuthorizationServerSecurityConfiguration {
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		super.configure(http);
		http
			.requestMatchers()
				.mvcMatchers("/.well-known/jwks.json")
				.and()
			.authorizeRequests()
				.mvcMatchers("/.well-known/jwks.json").permitAll();
	}
}

最后,如果你需要变更AuthorizationServerEndpointsConfiguration,你可以像下例中使用@Import来代替@EnableAuthorizationServer

@Import(AuthorizationServerEndpointsConfiguration.class)
@Configuration
public class JwkSetConfiguration extends AuthorizationServerConfigurerAdapter {

    // ... the rest of the configuration from the previous section
}

1.8.3. 针对Spring Security 5.1资源服务端进行测试

现在你可以使用POST访问/oauth/token终端([像之前一样](# 1.5.4. 测试授权流程))来获取toke并将其授予Spring Security 5.1资源端

2. 资源服务端

Spring Security OAuth2 Boot 使用两种 Bearer Token 的token格式:JWT 和Opaque.

2.1. 各种依赖

如果要使用 auto-configuration 功能,你需要spring-security-oauth2,它包含了 OAuth 2.0的基本功能和spring-security-oauth2-autoconfigure。需要注意的是你需要指定spring-security-oauth2-autoconfigure的版本,因为它不再被 Spring Boot 所管理,并且使用的版本号需要和 Boot 的版本兼容。
如果要支持JWT,则需要spring-security-jwt

2.2. 最小化的OAuth2 Boot 配置

创建最小化的 Spring Boot 资源服务端包含三个基本步骤:

  1. 引入相关依赖。
  2. 引入@EnableResourceServer注解
  3. 指定校验 bearer token 的策略。

2.2.1. 激活资源端

同其他 Spring Boot 的 @Enable注解一样,你可以在包含main方法的类中添加@EnableResourceServer 注解。例:

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

添加这个注解同时会添加OAuth2AuthenticationProcessingFilter,不过你仍然需要配置来使得应用知道如何适当处理和授权 token。

2.2.2. 指定Token授权策略

Bearer Token通常有一或两种形式:JWT-encoded 或 opaque。你需要为你的资源服务端配置一种策略。

2.2.2.1. JWT

要指定JWT,只需要在授权服务端上指定 jwk 的 key-set-uri:

security:
  oauth2:
    resource:
      jwk:
        key-set-uri: https://idp.example.com/.well-known/jwks.json

你也可以指定一个key来代替 key-set-uri。
需要注意的是,如果使用这种配置,授权服务端需要在启动资源服务端之前启动授权服务端。

2.2.2.2. Opaque

要指定Opaque,只需要在授权服务端配置如何解码 token:

security:
  oauth2:
    resource:
      token-info-uri: https://idp.example.com/oauth2/introspect

(机翻)
此端点可能需要与令牌本身不同的某种授权,例如客户端身份授权。
It’s likely this endpoint requires some kind of authorization separate from the token itself, for example, client authentication.
(机翻完毕)

That’s it! But, what do you do with it? We cover that next.

2.2.3. 访问资源

为确保资源服务端正确处理 token ,你可以添加一个Controller,如:

@RestController
public class SimpleController
	@GetMapping("/whoami")
	public String whoami(@AuthenticationPrincipal(expression="name") String name) {
		return name;
    }
}

然后,从您的授权服务端获取活动访问令牌,并将其提供给资源服务端:
curl -H "Authorization: $TOKEN" http://localhost:8080/whoami
你会在 token 中看到user_name的值。
读到此处,你可能会想要了解更多有关使用 bearer token 进行身份授权的三种替代方法:

  • [章节2.3. 如何通过单一键使用JWK](#2.3. 如何通过单一键使用JWK)
  • [章节2.4. 如何配置Token信息终端](#2.4. 如何配置Token信息终端)
  • [章节2.5. 如何配置User Info终端](#2.5. 如何配置User Info终端)

2.3. 如何通过单一键使用JWK

如果不想使用 JWK Set 终端,你或许会配置自己本地的key来做授权。尽管这种功能比较弱(大概是指安全度不够高。译者注),不过你仍然可以按需选择此方式。
给资源服务端配置适当的对称密钥或PKCS#8 PEM编码的公共密钥很简单,如下所示:

security:
  oauth2:
    resource:
      jwt:
        key-value: |
          -----BEGIN PUBLIC KEY-----
          MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC...
          -----END PUBLIC KEY-----

配置中的分隔符号 | 表示属性值有多行

你也可以改为提供 key-storekey-store-passwordkey-aliaskey-password 属性。
或者你亦可以使用key-uri来从授权服务端远程获取key,这是一种介于静态、本地配置和bearer token之间的很好的方式。

2.4. 如何配置Token信息终端

token 信息终端有时也被称作内省终端(introspection endpoint)。它可能需要某种客户端授权,即 Basic 或 Bearer。一般来说,Security Context 中的 bearer token 并不够用,因为它与用户绑定。相反,你需要指定代表客户端的凭据,如下所示:

security:
  oauth2:
    client:
      clientId: client-id
      clientSecret: client-secret
    resource:
      tokenInfoUri: https://idp.example.com/oauth2/check_token

默认采用 Basic 授权方式使用配置的凭据来针对 token信息终端进行身份授权。

2.5. 如何配置User Info终端

资源服务端通常调用用户信息端点。因为从根本上来讲,资源服务端是是对 request 进行授权请求(authorizing a request),而不是对其进行授权(not authenticating it)。(资源服务端需要对request 向 授权服务端申请授权,而不是在内部直接对其进行授权。译者注)
如果你像这样指定 userinfo 端点:

security:
  oauth2:
    resource:
      userInfoUri: https://idp.example.com/oauth2/userinfo

那么资源服务端会将 bearer token 作为 request 的一部分发送出去,并使用结果来增强Authentication对象。

2.5.1. 自定义User Info 请求

在内部,资源服务端使用OAuth2RestTemplate调用/ userinfo端点。有时,可能需要为此调用添加过滤器或执行其他自定义操作。要自定义此bean的创建,可以使用UserInfoRestTemplateCustomizer。例:

@Bean
public UserInfoRestTemplateCustomizer customHeader() {
	return restTemplate ->
			restTemplate.getInterceptors().add(new MyCustomInterceptor());
}

该bean将被传递到UserInfoTemplateFactory,后者将添加有助于与/ userinfo端点协调的其他配置。
当然,如果您需要完全控制OAuth2RestTemplate的配置,则可以完全替换UserInfoTemplateFactory

2.6. 自定义授权规则

与 Spring Security 工作方式类似,你可以通过 Spring Security OAuth 内的终端自定义授权规则,比如:

public class HasAuthorityConfig
		extends ResourceServerConfigurerAdapter {

	@Override
	public void configure(HttpSecurity http) throws Exception {
		// @formatter:off
		http
			.authorizeRequests()
				.antMatchers("/flights/**").hasAuthority("#oauth2.hasScope('message:read')")
				.anyRequest().authenticated();
		// @formatter:on
	}

但是请注意,如果将资源服务端和授权服务端配置在一起,有些端点需要特殊处理。为了避免在这些端点(例如/token)顶部进行配置,最好将资源服务端端点使用目录隔离开,如下所示:

public class ResourceServerEndpointConfig
		extends ResourceServerConfigurerAdapter {

	@Override
	public void configure(HttpSecurity http) throws Exception {
		// @formatter:off
		http
			.antMatchers("/resourceA/**", "/resourceB/**")
			.authorizeRequests()
				.antMatchers("/resourceA/**").hasAuthority("#oauth2.hasScope('resourceA:read')")
				.antMatchers("/resourceB/**").hasAuthority("#oauth2.hasScope('resourceB:read')")
				.anyRequest().authenticated();
		// @formatter:on
	}

这样配置可以使目标指向你的资源端点,而不会作用于授权端点。

2.7. 不常见的功能

2.7.1. 变更Token类型

Google和某些其他第三方身份提供商对标头中发送给用户信息端点的令牌类型名称更加严格。 默认值为Bearer,它适合大多数提供商并符合规范。 但是,如果需要更改它,则可以设置security.oauth2.resource.token-type

2.7.2. 变更Filter顺序

OAuth2 资源被 filter chain 以security.oauth2.resource.filter-order指定的顺序保护。
默认情况下,AuthorizationServerConfigurerAdapter中的过滤器排在最前面,其次是ResourceServerConfigurerAdapter中的过滤器,然后是WebSecurityConfigurerAdapter中的过滤器。
这意味着应用中所有的端点都需要bearertoken,除非以下至少一个情况:

  1. filter chain 的顺序发生改变
  2. ResourceServerConfigurerAdapter 授权请求集缩小范围。

首先,可以通过将WebSecurityConfigurerAdapter移动到ResourceServerConfigurerAdapter的前面来更改过滤器链的顺序,如下所示:

@Order(2)
@EnableWebSecurity
public WebSecurityConfig extends WebSecurityConfigurerAdapter {
	// ...
}

资源服务端的@Order值默认为3,所以示例中的@Order值为2能够确保其优先级更高。

尽管这种办法可行,但有一点奇怪的是,我们可能需要面临一个问题:
ResourceServerConfigurerAdapter会处理不应被其处理的请求。
另一个问题:
WebSecurityConfigurerAdapter正处理不应被其处理的请求。
更可靠的解决方案是向ResourceServerConfigurerAdapter指示应通过 bearer token 授权保护哪些端点。
比如,以下将资源服务端配置为保护以/ rest开头的Web应用程序端点:

@EnableResourceServer
public ResourceServerConfig extends ResourceServerConfigurerAdapter {
	@Override
    protected void configure(HttpSecurity http) {
        http
            .requestMatchers()
                .antMatchers("/rest/**")
            .authorizeRequests()
                .anyRequest().authenticated();
    }
}

2.7.3. 允许 /error 终端

如果资源服务端同时被配置成客户端,资源服务端在身份授权过程中可能依赖于 request-scoped OAuth2ClientContext bean。在有些错误情景中,资源服务端将转发到 ERROR servlet 处理器。
request-scoped 的bean默认情况下在 ERROR 处理器中不可用。因此,你可能会看到OAuth2ClientContext的反馈信息。
最简单的方法可能是允许/error端点,以使Resource Server不会尝试对其进行身份授权:

public class PermitErrorConfig extends ResourceServerConfigurerAdapter {
    @Override
	public void configure(HttpSecurity http) throws Exception {
		// @formatter:off
		http
			.authorizeRequests()
				.antMatchers("/error").permitAll()
				.anyRequest().authenticated();
		// @formatter:on
	}
}

其他解决方案是配置 Spring,即在错误处理器中注册RequestContextFilter或注册RequestContextListener bean。

3. 客户端

要将你的 web 应用作为 OAuth2 的客户端,你可以添加@EnableOAuth2Client注解,Spring Boot 就会创建OAuth2ClientContextOAuth2ProtectedResourceDetails这两个对于创建OAuth2RestOperations来说是必需的东西。Spring Boot不会自动创建这个bean,但可以通过下例的方法很容易手动创建:

@Bean
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oauth2ClientContext,
        OAuth2ProtectedResourceDetails details) {
    return new OAuth2RestTemplate(details, oauth2ClientContext);
}

您可能想要添加一个注入并查看您的配置,因为您的应用程序中可能定义了多个RestTemplate。

此配置使用security.oauth2.client.*作为依据(与授权服务端中可能使用的配置相同)。此外,它还需要知道授权服务端中的授权和 tokenURI,如以下示例所示:
application.yml

security:
  oauth2:
    client:
      clientId: bd1c0a783ccdd1c9b9e4
      clientSecret: 1a9030fbca47a5b2c28e92f19050bb77824b5ad1
      accessTokenUri: https://github.com/login/oauth/access_token
      userAuthorizationUri: https://github.com/login/oauth/authorize
      clientAuthenticationScheme: form

当你尝试使用OAuth2RestTemplate时,应用程序会使用此配置重定向至 Github 寻求授权。如果你已经在 Github 登录,你甚至不会感觉到那已经被授权。仅当您的应用程序在端口8080上运行时,这些特定的凭据才有效(您可以在Github或其他提供程序中注册自己的客户端应用程序,以提高灵活性)。
要限制客户端获取访问令牌时要求的范围,可以设置security.oauth2.client.scope(逗号分隔或YAML中的数组)。 默认情况下,作用域为空,由授权服务端决定默认值是什么(通常取决于它所拥有的客户端注册中的设置)。

还有一个security.oauth2.client.client-authentication-scheme的设置,默认为在header中(但是如果您的OAuth2提供者,如 Github,不喜欢标头授权,则可能需要将其设置为form)。 实际上,security.oauth2.client.*属性绑定到AuthorizationCodeResourceDetails的实例,因此可以指定其所有属性。

在非Web应用程序中,您仍然可以创建OAuth2RestOperations,并且仍将其连接到security.oauth2.client.*配置中。 在这种情况下,您要使用的是“客户端凭据令牌授权(client credentials token grant)”(并且不需要使用@ EnableOAuth2Client@ EnableOAuth2Sso)。 为防止基础组件生效,请从您的配置中删除security.oauth2.client.client-id(或使其为空字符串)。

4. 单点登录

你可以使用 OAuth2 Client 从保护容器(provider)中获取 user details(如果这个功能可用),然后将其转化成 Spring Security 的Authenticationtoken。资源服务端([之前描述的](# 2. 资源服务端))可以通过设置user-info-uri来支持此功能。这是基于 OAuth2 的单点登录协议的基础,Spring Boot 通过使用@EnableOAuth2Sso注解使其更容易实现。上一节中展示的Github客户端可以通过使用Github /user/端点,添加该注释并声明在何处查找端点(除了已经列出的security.oauth2.client.*配置),可以保护其所有资源并进行身份授权。
示例 4.1. application.yml

security:
  oauth2:
# ...
  resource:
    userInfoUri: https://api.github.com/user
    preferTokenInfo: false

由于所有路径都默认被保护,在未被授权情况下,你无法展示“home”页(通过访问/login路径或security.oauth2.sso.login-path指定的路径)。
要自定义访问规则或保护路径(如添加“home”页面),你可以可以在WebSecurityConfigurerAdapter中添加@EnableOAuth2Sso注解。此注解可以使需要被保护的资源通过/login得到保护。下面的例子仅允许未经授权的用户对主页进行访问,其他部分使用默认配置:

@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .mvcMatchers("/").permitAll()
                .anyRequest().authenticated();
    }
}

请注意,所有终端都默认被保护,包括任何错误处理器(如/error)。也就是说,如果在使用单点登录过程中出现任何问题需要重定向到/error页面,这会在授权服务端和资源端之间引发无限循环调用(类似无法跳出的递归调用。译者注)。
首先,仔细思考不需要被保护的端点。你可能会发现这只是另一个问题的起因。不过你可以像下例一样将/error端点设置为允许访问,

@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/error").permitAll()
                .anyRequest().authenticated();
    }
}

附录A. 常见的应用属性

你可以在application.properties文件或application.yml文件中定义各种属性,你也可以使用命令行来开启或关闭他们。本节提供了常见的Spring Boot属性列表以及对使用它们的基础类的引用。

属性可以来自 classpath 上的其他 jar 文件,因此您不应将其视为详尽的列表。 定义自己的属性也是完全合法的。

此样本文件仅供参考。 不要将整个内容复制并粘贴到您的应用程序中。 而是仅选择所需的属性。

# SECURITY OAUTH2 CLIENT (OAuth2ClientProperties)
security.oauth2.client.client-id= # OAuth2 client id.
security.oauth2.client.client-secret= # OAuth2 client secret. 默认生成随机Secret

# SECURITY OAUTH2 RESOURCES (ResourceServerProperties)
security.oauth2.resource.id= # resource的唯一标识符
security.oauth2.resource.jwt.key-uri= # JWT token 的URI. 如果值不可用且密钥是公共的,则可以设置。
security.oauth2.resource.jwt.key-value= # JWT令牌的授权密钥。 可以是对称密钥,也可以是PEM编码的RSA公钥。
security.oauth2.resource.jwk.key-set-uri= # 用于获取可用于验证令牌的密钥集的URI。
security.oauth2.resource.prefer-token-info=true # 使用令牌信息,可以设置为false以使用用户信息。
security.oauth2.resource.service-id=resource #
security.oauth2.resource.token-info-uri= # 解码 token 的 URI。
security.oauth2.resource.token-type= # 使用userInfoUri时发送的token的类型。 
security.oauth2.resource.user-info-uri= # URI of the user endpoint.

# SECURITY OAUTH2 SSO (OAuth2SsoProperties)
security.oauth2.sso.login-path=/login # 登录页面的路径, 即触发重定向到OAuth2授权服务端的地址
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值