java怎么拦截某个对象
414
2022-11-15
22-Spring Authorization Server初体验
在上一篇中我对授权服务器项目Spring Authorization Server进行了介绍,这一篇我们来一起看看它是如何搭建、如何使用。先不要纠结为什么要这样配置,先跑起来,后面会去深入探究原因。
本文DEMO:springauthserver 分支。
项目环境依赖
本文Spring Authorization Server版本为0.2.2。
像OAuth2 Client、Resource Server一样,Spring Authorization Server也是以插件的形式接入Spring Security的体系中。下面列举了目前必备的环境依赖:
这里要注意几点,环境要求参考Spring Authorization Server介绍一文。此外,Spring Authorization Server是需要数据库支持的,这里我依然使用了小巧的H2数据库。
在实际开发中,你可以通过更换驱动的手段切换到其它类型的数据库,可能会涉及到简单的数据库方言修改或者配置变化。
授权服务器过滤器链
OAuth2授权服务器专门处理OAuth2客户端的授权请求流程,授权端点、Token端点、用户信息端点等等都需要对应的过滤器支持,这些过滤器由Spring Authorization Server中的OAuth2AuthorizationServerConfigurer负责初始化和配置。我们只需要定义一个优先级最高的过滤器链,把授权服务器配置类初始化并激活即可。
@Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity throws Exception { OAuth2AuthorizationServerConfigurer
上面是一个基本的配置,关键的步骤为:
配置拦截授权服务器相关的请求端点。由于是接口调用,同时关闭相关端点的CSRF功能。将配置类加入HttpSecurity激活配置。
客户端的注册和持久化管理
按照OAuth2协议,所有的OAuth2客户端都应该在授权服务器中进行信息注册。你去申请接入第三方开放平台,都要提交一些信息,第三方平台审核通过后会把一些OAuth2客户端信息发给你,这些信息你不会陌生,大部分都包含在OAuth2客户端类库的OAuth2ClientProperties.Registration中,对应Spring Authorization Server授权服务器的实体为RegisteredClient:
public class RegisteredClient implements Serializable { private static final long serialVersionUID = Version.SERIAL_VERSION_UID; private String id; private String clientId; private Instant clientIdIssuedAt; private String clientSecret; private Instant clientSecretExpiresAt; private String clientName; private Set
这些属性多数在前面的章节中已经介绍了,redirect_uri变成了复数以适应多个OAuth2客户端,另外redirect_uri还有一些隐含规则和操作 ,相关源码:
private static boolean isValidRedirectUri(String requestedRedirectUri, RegisteredClient registeredClient) { UriComponents requestedRedirect; try { requestedRedirect = UriComponentsBuilder.fromUriString(requestedRedirectUri).build(); if (requestedRedirect.getFragment() != null) { return false; } } catch (Exception ex) { return false; } String requestedRedirectHost = requestedRedirect.getHost(); if (requestedRedirectHost == null || requestedRedirectHost.equals("localhost")) { // As per // While redirect URIs using localhost (i.e., // "function similarly to loopback IP // redirects described in Section 10.3.3, the use of "localhost" is NOT RECOMMENDED. return false; } if (!isLoopbackAddress(requestedRedirectHost)) { // As per // When comparing client redirect URIs against pre-registered URIs, // authorization servers MUST utilize exact string matching. return registeredClient.getRedirectUris().contains(requestedRedirectUri); } // As per // The authorization server MUST allow any port to be specified at the // time of the request for loopback IP redirect URIs, to accommodate // clients that obtain an available ephemeral port from the operating // system at the time of the request. for (String registeredRedirectUri : registeredClient.getRedirectUris()) { UriComponentsBuilder registeredRedirect = UriComponentsBuilder.fromUriString(registeredRedirectUri); registeredRedirect.port(requestedRedirect.getPort()); if (registeredRedirect.build().toString().equals(requestedRedirect.toString())) { return true; } } return false; }
这里简单总结一个要点:
如果OAuth2授权服务器是Spring Authorization Server,目前必须严格按照这个规则配置redirect_uri。
ClientSettings
该OAuth2客户端的一些规则配置,包括:
private_key_jwt和client_secret_jwt参见ClientAuthenticationMethod。
TokenSettings
注册OAuth2客户端时对该客户端令牌的通用规则配置,包含了:
ACCESS_TOKEN_TIME_TO_LIVE 访问令牌生存时间,默认5分钟。REUSE_REFRESH_TOKENS 是否可以复用刷新令牌,默认true。ID_TOKEN_SIGNATURE_ALGORITHMOIDC ID Token使用的签名算法,默认RS256
你可以通过TokenSettings.withSettings添加额外的自定义属性或者覆盖已有的属性。
我们来初始化一个OAuth2客户端,这里我们使用的客户端授权方法ClientAuthenticationMethod是client_secret_basic,因为之前对应的basic已经不建议使用了:
private RegisteredClient createRegisteredClient(final String id) { return RegisteredClient.withId(UUID.randomUUID().toString())// 客户端ID .clientId("felord")// 此处为了避免频繁启动重复写入仓库 .id(id)// client_secret_basic 模式下的密码 在客户端需要存明文 在授权服务器存密文 .clientSecret(PasswordEncoderFactories.createDelegatingPasswordEncoder() .encode("secret"))// 名称可不定义 .clientName("felord")// 授权方法 .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)// 支持的授权类型 .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)// 回调地址名单,不在此列将被拒绝 而且只能使用IP或者域名 不能使用 localhost .redirectUri(" .redirectUri(" .redirectUri(" .redirectUri(" .redirectUri(" OIDC支持 .scope(OidcScopes.OPENID)// 其它Scope .scope("message.read") .scope("userinfo") .scope("message.write")// JWT的配置项 包括TTL 是否复用refreshToken等等 .tokenSettings(TokenSettings.builder().build())// 配置客户端相关的配置项,包括验证密钥或者 是否需要授权页面 .clientSettings(ClientSettings.builder() .requireAuthorizationConsent(true).build()) .build(); }
上面注册的OAuth2客户端信息需要持久化到数据库,RegisteredClientRepository接口抽象了对RegisteredClient的持久化操作,这里我们直接启用内置的JDBC实现以代替默认的内存实现:
@SneakyThrows @Bean public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) { // 每次都会初始化 生产的话 只初始化JdbcRegisteredClientRepository JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate); // TODO 生产上 注册客户端需要使用接口 不应该采用下面的方式 // only@test begin final String id = "10000"; RegisteredClient registeredClient = registeredClientRepository.findById(id); if (registeredClient == null) { registeredClient = this.createRegisteredClient(id); registeredClientRepository.save(registeredClient); } // only@test end return registeredClientRepository; }
这里为了测试,我们在初始化JdbcRegisteredClientRepository的时候保存了一个OAuth2客户端信息。
授权状态信息持久化
资源拥有者的OAuth2授权状态信息OAuth2Authorization也需要持久化管理,Spring Authorization Server提供了OAuth2AuthorizationService来负责这个工作,我们同样需要启用内置的JDBC实现以代替默认的内存实现:
@Bean public OAuth2AuthorizationService authorizationService( JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository); }
授权确认状态持久化
如果该客户端配置ClientSettings开启了授权确认REQUIRE_AUTHORIZATION_CONSENT ,授权确认的信息也要持久化管理,需要启用内置的JDBC实现以代替默认的内存实现:
@Bean public OAuth2AuthorizationConsentService authorizationConsentService( JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) { return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository); }
OAuth2客户端注册到授权服务器的注册信息中配置了授权确认功能才有用。
JWK源配置
授权服务器公私钥都需要,参考Spring Security中的JOSE类库中的方法,结合Spring Authorization Server提供的方案,我们只需要定义一个JWKSource类型的Spring Bean即可:
/** * 加载JWK资源 * * @return the jwk source */ @SneakyThrows @Bean public JWKSource
到这里就配置完了。启动项目,访问下面的issue端点:
"issuer": " "authorization_endpoint": " "token_endpoint": " "token_endpoint_auth_methods_supported": [ "client_secret_basic", "client_secret_post", "client_secret_jwt", "private_key_jwt" ], "jwks_uri": " "response_types_supported": [ "code" ], "grant_types_supported": [ "authorization_code", "client_credentials", "refresh_token" ], "revocation_endpoint": " "revocation_endpoint_auth_methods_supported": [ "client_secret_basic", "client_secret_post", "client_secret_jwt", "private_key_jwt" ], "introspection_endpoint": " "introspection_endpoint_auth_methods_supported": [ "client_secret_basic", "client_secret_post", "client_secret_jwt", "private_key_jwt" ], "code_challenge_methods_supported": [ "plain", "S256" ]}
这些配置是提供给OAuth2客户端的,里面也有不少的端点,比如jwks_uri你可以访问一下,看看能否获取公钥JWK。
授权服务器尽量配置域名避免使用localhost。
附数据库DDL脚本
Spring Authorization Server的类库内置了数据库DDL脚本,在org/springframework/security/oauth2/server/authorization下,分别是
oauth2-authorization-schema.sqloauth2-authorization-consent-schema.sqloauth2-registered-client-schema.sql
你可以手动或者借助于spring.sql.init系列命令进行初始化。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~