栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring Authorization Server 的一些核心组件和内置 Filter

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring Authorization Server 的一些核心组件和内置 Filter

Spring Authorization Server 的一些核心组件和内置 Filter
  • 前言
  • 核心组件
    • SecurityFilterChain
    • RegisteredClientRepository
    • OAuth2AuthorizationService OAuth2AuthorizationConsentService
    • JWKSource
    • ProviderSettings
    • OAuth2TokenCustomizer
  • 内置 Filter
    • OAuth2AuthorizationEndpointFilter
    • OAuth2TokenEndpointFilter
    • NimbusJwkSetEndpointFilter
    • 其他
  • 总结

前言

Spring Security 将 认证中心 的实现剥离了出去,由另一个工程 spring-security-oauth2-authorization-server 提供,这是一个由 Spring Security 团队牵头、社区主导开发的新项目,到写这篇文章时才发布到正式版本 0.2.3

本文基于自己的使用体验,简单的聊一下 Spring Authorization Server 的一些核心组件以及内置的 Filter

核心组件 SecurityFilterChain
	@Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationSFC(HttpSecurity httpSecurity) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(httpSecurity);
        return httpSecurity
                .formLogin(Customizer.withDefaults())
                .build();
    }

    @Bean
    public SecurityFilterChain defaultSFC(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                .authorizeHttpRequests()
                .mvcMatchers(
                        "/test"
                ).permitAll()
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
                .formLogin(Customizer.withDefaults())
                .build();
    }
  • 准确地说,SecurityFilterChain 是 Spring Security 的组件,当然 spring-security-oauth2-authorization-server 是依赖 Spring Security 搭建的
  • SecurityFilterChain 的作用跟在 Spring Security 中一样,就是提供基于 RequestMatcher 的 Filter 声明,以 Spring Security 风格的流式 API 声明针对请求路径的 Filter 组
  • OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(httpSecurity) 这是 spring-security-oauth2-authorization-server 提供的方法,可以理解为针对 Spring Authorization Server 最佳实践配置,主要是针对默认的路径比如 /oauth2/authorize /oauth2/token 等配置默认规则
  • 上述示例中,authorizationSFC 组件优先级最高,主要是处理 Spring Authorization Server 的指定路径, defaultSFC 组件处理应用的其他路,效果为:除了 /test 路径外都需要认证
RegisteredClientRepository
	@Bean
    public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("messaging-client")
                .clientSecret("{noop}secret")
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc")
                .redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-id")
                .redirectUri("http://127.0.0.1:8080/authorized")
                .redirectUri("http://127.0.0.1:8080/callback")
                // 建议注册一个 openid 的 scope,可以不提供 userinfo endpoint
                .scope(OidcScopes.OPENID)
                .scope("message.read")
                .scope("message.write")
                // 设置 Client 需要页面审核授权
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
                // 设置 Token 的有效期为 30min
                .tokenSettings(TokenSettings.builder().accessTokenTimeToLive(Duration.ofMinutes(30)).build())
                .build();

        JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
        registeredClientRepository.save(registeredClient);

        return registeredClientRepository;
    }
  • RegisteredClientRepository,该组件主要负责注册的 Client 信息
  • 示例中我们注册的是一个 JdbcRegisteredClientRepository,它基于数据库持久化 Client 信息
  • 不难猜测,Spring Authorization Server 还提供一个默认实现 InMemoryRegisteredClientRepository,基于内存保存 Client 信息,除了测试一般不用
  • 在实际开发中,如果不是一开始就基于 Spring Security Oauth2 的认证搭建的系统,则 Client 信息一般都基于其他形式管理,则我们需要提供自己的 RegisteredClientRepository 实现,好在这并不复杂,一共就三个方法 save findById findByClientId
  • 其中 RegisteredClient 的构造也提供了方便的 Builder API,包括了对 clientSettings 的构造比如 Token 有效期等,可见示例
OAuth2AuthorizationService OAuth2AuthorizationConsentService
	@Bean
    public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
    }

    @Bean
    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
    }
  • 这两个组件分别是对 OAuth2Authorization 和 OAuth2AuthorizationConsent 管理抽象,前者代表客户端在认证中心的授权信息,后者代表客户端授权的审核信息
  • 这两个组件一般就是用 Jdbc 的实现了,不需要自己拓展,当然如果注册的 Client 不需要 Consent 时,OAuth2AuthorizationConsentService 组件可以不注册
JWKSource
	@Bean
    public JWKSource jwkSource() throws JOSEException {
        RSAKey rsaKey = new RSAKeyGenerator(2048).generate();
        JWKSet jwkSet = new JWKSet(rsaKey);
        return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
    }
  • spring-security-oauth2-authorization-server 默认发放的令牌是 JWT,具体的加签算法基于 JWKSource 获取
  • JWKSource 是 Nimbus 的类,持有 JWKSelector 和 SecurityContext 上下文信息来解析 JWK
  • 示例中默认返回 RSAKey,即 RSA256 算法加签
ProviderSettings
	@Bean
    public ProviderSettings providerSettings() {
        return ProviderSettings.builder().issuer("http://localhost:9000").build();
    }
  • 这是 spring-security-oauth2-authorization-server 比较新颖的一个机制,可以通过 provider 机制将 认证中心 的元数据信息比如 AuthorizationEndpoint TokenEndipoint 等暴露出去
  • 对应的,Client 就可以基于此机制获取 认证中心 元数据信息,从而避免了大量繁琐的配置
OAuth2TokenCustomizer
	@Bean
    public OAuth2TokenCustomizer oAuth2TokenCustomizer() {

        return context -> {

            // 客户端 scope
            Set scopes = context.getAuthorizedScopes();

            // 认证用户的权限
            Set authorities = context.getPrincipal()
                    .getAuthorities()
                    .stream()
                    .map(GrantedAuthority::getAuthority)
                    .collect(Collectors.toSet());

            // 权限集合
            HashSet authorizedScopes = new HashSet<>();
            authorizedScopes.addAll(scopes);
            authorizedScopes.addAll(authorities);

            // 覆盖 JWT scope 信息
            context.getClaims()
                    .claim(OAuth2ParameterNames.SCOPE, authorizedScopes);
        };
    }
  • 该组件非必需,主要负责 JWT 的自定义处理
  • 比如示例中,将认证用户的权限也添加到 JWT 的 scope 属性中,这主要是针对 Client oauth2login 的场景,则对应的 Resource Server 就可以针对 认证用户 的权限进行控制了
内置 Filter OAuth2AuthorizationEndpointFilter
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		// 匹配 /oauth2/authorize 路径
		if (!this.authorizationEndpointMatcher.matches(request)) {
			filterChain.doFilter(request, response);
			return;
		}

		try {
			// 将 request 处理为 OAuth2AuthorizationCodeRequestAuthenticationToken
			OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
					(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationConverter.convert(request);

			// 由对应的 AuthorizationProvider 处理
			// 比如生成授权码、处理 Consent 信息等
			OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
					(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationManager.authenticate(authorizationCodeRequestAuthentication);

			// 如果用户还未认证,则继续执行下去,可能会被登录页面等拦截器处理
			if (!authorizationCodeRequestAuthenticationResult.isAuthenticated()) {
				filterChain.doFilter(request, response);
				return;
			}

			// 如果需要审核则发送 Consent 页面
			if (authorizationCodeRequestAuthenticationResult.isConsentRequired()) {
				sendAuthorizationConsent(request, response, authorizationCodeRequestAuthentication, authorizationCodeRequestAuthenticationResult);
				return;
			}

			// 如果不需要则直接重定向客户端回调地址
			this.authenticationSuccessHandler.onAuthenticationSuccess(
					request, response, authorizationCodeRequestAuthenticationResult);

		} catch (OAuth2AuthenticationException ex) {
			
		}
	}
  • 该拦截器主要处理默认 /oauth2/authorize 路径,主要场景是 oauth2login 或自行请求 授权码
  • 它会将 Request 转换成对应的 OAuth2AuthorizationCodeRequestAuthenticationToken 交给 AuthenticationManager 处理
  • AuthenticationManager 的实现类 ProviderManager 是一种 组合 的设计模式,其下有一组 AuthenticationProvider,对应的 AuthenticationProvider 会处理,比如此处由 OAuth2AuthorizationCodeRequestAuthenticationProvider 处理
  • 如果用户未认证,则拦截器继续执行下去以继续 登录认证 等操作
  • 认证过后则会选择返回 Consent 页面或者 重定向 客户端回调地址
OAuth2TokenEndpointFilter
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		// 处理 /oauth2/token 路径
		if (!this.tokenEndpointMatcher.matches(request)) {
			filterChain.doFilter(request, response);
			return;
		}

		try {
			
			// ...

			// 由 OAuth2AuthorizationCodeAuthenticationProvider 处理
			// 包括生成 Token RefreshToken 等
			OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
					(OAuth2AccessTokenAuthenticationToken) this.authenticationManager.authenticate(authorizationGrantAuthentication);
			this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, accessTokenAuthentication);
		} catch (OAuth2AuthenticationException ex) {

		}
	}
  • 该过滤器主要处理 /oauth2/token 路径,主要场景为 oauth2login 或自行获取 Token
  • 这里主要负责处理的 AuthenticationProvider 为 OAuth2AuthorizationCodeAuthenticationProvider,负责 Token (RefreshToken) 的生成
  • Token 的生成由 OAuth2TokenGenerator 接口负责,仍旧采用 组合 设计模式,由 DelegatingOAuth2TokenGenerator 依次组合 JwtGenerator OAuth2AccessTokenGenerator 等实例,即默认生成 JWT
NimbusJwkSetEndpointFilter
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		// 处理 /oauth2/jwks 路径
		if (!this.requestMatcher.matches(request)) {
			filterChain.doFilter(request, response);
			return;
		}

		// 基于我们声明的 JWKSource 组件
		JWKSet jwkSet;
		try {
			jwkSet = new JWKSet(this.jwkSource.get(this.jwkSelector, null));
		}
		catch (Exception ex) {

		}

		// ...
	}
  • 该过滤器处理 /oauth2/jwks 路径,主要场景是 Resource Server 验签 JWT 时获取 JWK 公钥
  • 基于我们声明的 JWKSource 的组件返回
  • 对应的,加签 JWT 的私钥也是基于该组件生成的
其他

还有一些拦截器不再一一列举,比如:

  • OAuth2AuthorizationServerMetadataEndpointFilter:提供 认证中心 的 Provider 元数据信息
  • OAuth2ClientAuthenticationFilter:负责 Client 的认证
  • 等等
总结

因为官方暂时未提供成熟的 Spring Boot 自动装配,因此这些组件需要自行注册

当然示例中给出的 个人最佳实践 也是基本可以应付工作中的大多数场景

完整 demo 示例

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/866348.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号