IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Spring Authorization Server 的一些核心组件和内置 Filter -> 正文阅读

[Java知识库]Spring Authorization Server 的一些核心组件和内置 Filter

前言

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();
    }
  • 准确地说,SecurityFilterChainSpring Security 的组件,当然 spring-security-oauth2-authorization-server 是依赖 Spring Security 搭建的
  • SecurityFilterChain 的作用跟在 Spring Security 中一样,就是提供基于 RequestMatcherFilter 声明,以 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);
    }
  • 这两个组件分别是对 OAuth2AuthorizationOAuth2AuthorizationConsent 管理抽象,前者代表客户端在认证中心的授权信息,后者代表客户端授权的审核信息
  • 这两个组件一般就是用 Jdbc 的实现了,不需要自己拓展,当然如果注册的 Client 不需要 Consent 时,OAuth2AuthorizationConsentService 组件可以不注册

JWKSource

	@Bean
    public JWKSource<SecurityContext> 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 获取
  • JWKSourceNimbus 的类,持有 JWKSelectorSecurityContext 上下文信息来解析 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<JwtEncodingContext> oAuth2TokenCustomizer() {

        return context -> {

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

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

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

            // 覆盖 JWT scope 信息
            context.getClaims()
                    .claim(OAuth2ParameterNames.SCOPE, authorizedScopes);
        };
    }
  • 该组件非必需,主要负责 JWT 的自定义处理
  • 比如示例中,将认证用户的权限也添加到 JWTscope 属性中,这主要是针对 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
  • 这里主要负责处理的 AuthenticationProviderOAuth2AuthorizationCodeAuthenticationProvider,负责 TokenRefreshToken) 的生成
  • 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 示例

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-08 07:54:27  更:2022-05-08 07:58:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 22:53:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码