续. 前一篇OAuth 2.0
OAuth 2.0 Provider 实现
在 OAuth 2.0 中, provider 角色事实上是把授权服务和资源服务分开, 有时候它们也可能在同一个应用中, 用 Spring Security OAuth 你可以选择把它们分成两个应用, 当然多个资源服务可以共享同一个授权服务.
获取 token 的请求由 Spring MVC 的控制端点处理, 访问受保护的资源由标准的 Spring Security 请求过滤器处理.
(The requests for the tokens are handled by Spring MVC controller endpoints, and access to protected resources is handled by standard Spring Security request filters.)
为了实现 OAuth 2.0 授权服务器, 在 Spring Security 的过滤器链中需要有以下端点:
AuthorizationEndpoint 用于服务授权请求. 默认 URL 是 / oauth/authorize
TokenEndpoint 用于服务访问令牌请求. 默认 URL 是 / oauth/token
在 OAuth 2.0 的资源服务器中需要实现下列过滤器:
OAuth2AuthenticationProcessingFilter 用于加载认证
对于所有的 OAuth 2.0 provider 特性, 最简单的配置是用 Spring OAuth @Configuration 适配器.
Authorization Server 配置
只要你配置了授权服务器, 那么你应该考虑客户端用于获取 access token 的授权类型(例如, 授权码, 用户凭证, 刷新 token).
服务器的配置是用来提供 client detail 服务和 token 服务的, 并且可以启用或者禁用全局的某些机制.
每个客户端可以配置不同的权限
@EnableAuthorizationServer 注解被用来配置授权服务器, 也可以和实现了 AuthorizationServerConfigurer 接口的任意被标记为 @Bean 的 Bean 一起来对授权服务器进行配置.
下列特性被委托给 AuthorizationServerConfigurer:
ClientDetailsServiceConfigurer :a configurer that defines the client details service
AuthorizationServerSecurityConfigurer :defines the security constraints on the token endpoint
AuthorizationServerEndpointsConfigurer :defines the authorization and token endpoints and the token services
一件重要的事情是, provider 配置了将授权码给 OAuth 客户端的方式(PS: 在授权码类型授权过程中)
OAuth 客户端通过将 end-user(最终用户)导向授权页, 用户可用在此输入他的凭证. 之后, 授权服务器携带授权码通过重定向的方式将授权码返回给客户端.
配置 Client Details
The ClientDetailsServiceConfigurer (a callback from your AuthorizationServerConfigurer) can be used to define an in-memory or JDBC implementation of the client details service.
ClientDetailsServiceConfigurer 可用使用 client details service 的两种实现中的任意一种: in-memory 或者 JDBC
客户端重要的属性是:
clientId :(必须的)客户端 ID
secret :(对于信任的客户端需要)客户端秘钥
scope : 客户端被限定的范围. 如果 scope 为定义或者为空 (默认为空) 则客户端不受 scope 限制
authorizedGrantTypes : 客户端使用到的授权类型
authorities : 授予客户端的权限
客户端 details 可以在应用运行时被更新, 通过直接访问存储 (例如: 如果用 JdbcClientDetailsService 的话可以实时改变数据库表中的数据) 或者通过实现 ClientDetailsManager 接口(它们也都实现了 ClientDetailsService 接口).
NOTE: the schema for the JDBC service is not packaged with the library (because there are too many variations you might like to use in practice), but there is an example you can start from in the test code in github https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql .
注意: 用于 JDBC 服务的数据库 schema 并没有打包到 library 中(因为你再实际使用的时候可能有诸多差异), 但是这里有一个例子你可以参考一下.
https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql
-- used in tests that use HSQL
- create table oauth_client_details (
- client_id VARCHAR(256) PRIMARY KEY,
- resource_ids VARCHAR(256),
- client_secret VARCHAR(256),
- scope VARCHAR(256),
- authorized_grant_types VARCHAR(256),
- web_server_redirect_uri VARCHAR(256),
- authorities VARCHAR(256),
- access_token_validity INTEGER,
- refresh_token_validity INTEGER,
- additional_information VARCHAR(4096),
- autoapprove VARCHAR(256)
- );
- create table oauth_client_token (
- token_id VARCHAR(256),
- token LONGVARBINARY,
- authentication_id VARCHAR(256) PRIMARY KEY,
- user_name VARCHAR(256),
- client_id VARCHAR(256)
- );
- create table oauth_access_token (
- token_id VARCHAR(256),
- token LONGVARBINARY,
- authentication_id VARCHAR(256) PRIMARY KEY,
- user_name VARCHAR(256),
- client_id VARCHAR(256),
- authentication LONGVARBINARY,
- refresh_token VARCHAR(256)
- );
- create table oauth_refresh_token (
- token_id VARCHAR(256),
- token LONGVARBINARY,
- authentication LONGVARBINARY
- );
- create table oauth_code (
- code VARCHAR(256), authentication LONGVARBINARY
- );
- create table oauth_approvals (
- userId VARCHAR(256),
- clientId VARCHAR(256),
- scope VARCHAR(256),
- status VARCHAR(10),
- expiresAt TIMESTAMP,
- lastModifiedAt TIMESTAMP
- );
-- customized oauth_client_details table
- create table ClientDetails (
- appId VARCHAR(256) PRIMARY KEY,
- resourceIds VARCHAR(256),
- appSecret VARCHAR(256),
- scope VARCHAR(256),
- grantTypes VARCHAR(256),
- redirectUrl VARCHAR(256),
- authorities VARCHAR(256),
- access_token_validity INTEGER,
- refresh_token_validity INTEGER,
- additionalInformation VARCHAR(4096),
- autoApproveScopes VARCHAR(256)
- );
管理 Tokens
AuthorizationServerTokenServices 定义了管理 OAuth 2.0 Token 所必须的操作. 请注意:
当创建一个 access token 的时候, 这个认证必须被存储起来, 以便后续访问资源的时候对接收到的 access token 进行引用校验.
access token 用来加载认证
当你实现了 AuthorizationServerTokenServices 接口, 你可能考虑用 DefaultTokenServices. 有许多内置的插件化的策略可以用来改变 access token 的格式和存储.
默认情况下, 用随机值来生成 token, 并且用 TokenService 来处理所有 (除了 token 持久化以外) 事情. 默认的存储是 in-memory 实现, 但是有其它的实现可以使用.
对于单服务器而言, 默认的 InMemoryTokenStore 是完美的. 大多数的项目是从这里开始的, 为了使它很容易启动, 也不需要其它依赖, 并且可能以开发模式进行操作.
JdbcTokenStore 是 JDBC 版本的 Token 存储. 它把 Token 数据存储到关系型数据库中. 为了使用 JdbcTokenStore 需要 classpath 下有 "spring-jdbc".
JSON Web Token (JWT) 它将授权的 token 的所有数据进行编码后存储(没有使用后端存储是它最大的优势). 这种方式的一个缺点是你不能很容易的撤销一个 access token, 因此一般用该方式存储的 token 的有效期很短, 并且在刷新 token 的时候之前的 token 会被废除. 另一个缺点是, token 很长, 因为它里面存了很多关于用户凭证的信息. JwtTokenStore 不会真的存储数据, 它不持久化任何数据. 但是在 DefaultTokenServices 中, 它扮演着 token 值和认证信息转换的角色.
注意: 对于 JDBC 的 schema 没有打包到 library 中, 但是这儿有一个例子你可以参考一下 test code in github https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql . 确保用 @EnableTransactionManagement 来防止多个客户端在同一行创建 token. 注意, 示例中的 schema 都有明确地主键声明, 在并发环境中这是必须的.
JWT Tokens
为了使用 JWT Tokens, 你需要在你的授权服务器中有一个 JwtTokenStore. 资源服务器也需要解码这个 token, 所以 JwtTokenStore 有一个依赖 JwtAccessTokenConverter, 相同的实现需要被包含在授权服务器和资源服务器中. 也就是说, 授权服务器和资源服务器中都需要 JwtTokenStore 实现. 默认情况下, token 是被签名的, 而且资源服务器必须能够校验这个签名, 因此需要有相同的对称 key, 或者需要公钥来匹配授权服务器上的私钥. 公钥被授权服务器暴露在 / oauth/token_key 端点, 默认情况下这个端点的访问规则是 "denyAll()". 你可以用标准的 SpEL 表达式 (例如: permitAll()) 到 AuthorizationServerSecurityConfigurer 来开放它.
为了使用 JwtTokenStore, 在 classpath 下需要有 "spring-security-jwt"
Grant Types
授权类型通过 AuthorizationEndpoint 来支持. 默认情况下, 除了 password 以外, 所有授权类型都支持. 下面是授权类型的一些属性:
authenticationManager : 通过注入一个 AuthenticationManager 来切换成 password 授权
userDetailsService : 如果你注入一个 UserDetailsService 或者以任意方式配置了一个全局的 UserDetailsService(例如: 在 GlobalAuthenticationManagerConfigurer 中), 那么一个刷新 token 将被包含在 user detail 中, 为了强制账户是激活的.
authorizationCodeServices : 定义授权码服务(AuthorizationCodeServices 的实例)
implicitGrantService : 在隐式授权期间管理状态
tokenGranter :tokenGranter
Configuring the Endpoint URLs
AuthorizationServerEndpointsConfigurer 有一个 pathMapping()方法. 它有两个参数:
端点的默认 URL 路径
自定义的路径(必须以 "/" 开头)
下面是框架提供的 URL 路径:
/oauth/authorize 授权端点
/oauth/token 令牌端点
/oauth/confirm_access 用户批准授权的端点
/oauth/error 用于渲染授权服务器的错误
/oauth/check_token 资源服务器解码 access token
/oauth/check_token 当使用 JWT 的时候, 暴露公钥的端点
授权端点 / oauth/authorize 应该被保护起来, 以至于它只能被认证过的用户访问. 下面是一个例子, 用标准的 Spring Security WebSecurityConfigurer :
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- http
- .authorizeRequests().antMatchers("/login").permitAll().and()
- // default protection for all resources (including /oauth/authorize)
- .authorizeRequests()
- .anyRequest().hasRole("USER")
- // ... more configuration, e.g. for form login
- }
注意: 如果您的授权服务器同时也是一个资源服务器的话, 那么就有另一个具有较低优先级的安全过滤器链来控制 API 资源. 通过访问令牌来保护这些请求, 你需要它们的路径不能与主用户所面对的过滤器链中的那些相匹配, 所以请确保包含一个请求 matcher, 它只挑选出上面的 WebSecurityConfigurer 中的非 api 资源.
Customizing the UI
授权服务器的大多数端点主要都是被机器使用的, 但是有两个资源是需要 UI, 它们分别是 / oauth/confirm_access 和 html 响应 / oauth/error. 框架为它们提供的实现是空白页, 真实的情况是大多数授权服务器可能想要提供它们自己的实现来控制样式和内容. 所以, 你需要做的事情就是提供一个 Spring MVC 被标注了 @RequestMappings 注解的 Controller 来映射这些端点, 并且框架将用一个低的优先级来发放请求. 在默认的 / oauth/confirm_access 你期望一个 AuthorizationRequest 绑定到 session. 你可以抓取请求的所有数据并按照自己喜欢的方式渲染它们, 然后用户需要做的就是向 / oauth/authorize 发送关于批准或拒绝授予的信息. 默认的 UserApprovalHandler 取决于是否你再 AuthorizationServerEndpointsConfigurer 中提供了一个 ApprovalStore. 标准的审批处理器如下:
TokenStoreUserApprovalHandler : 通过 user_oauth_approval 做一个简单的 yes/no 决定等同于 "true" 或 "false"
ApprovalStoreUserApprovalHandler : 一组 "scope*" 参数 key. 参数的值可以是 "true" 或者 "approval". 至少有一个 scope 是 approval 才算是授权成功.(A grant is successful if at least one scope is approved.)
强制 SSL
纯 HTTP 对于测试来说是可以的, 但是在生成中授权服务器应该使用 SSL. 你可以在一个安全的容器或代理后面运行应用程序, 如果你正确地设置代理和容器 (这与 OAuth2 无关), 那么它应该可以正常工作. 对于 / authorize 端点你需要把它当作正常的应用安全的一部分来做, 对于 / token 端点在 AuthorizationServerEndpointsConfigurer 中有一个标记可以设置, 通过用 sslOnly() 方法.
自定义错误处理
授权服务器用标准的 Spring MVC 特性来进行错误处理.
你可以提供自己的实现, 通过添加 @Controller 并且带上 @RequestMapping("/oauth/error")
Mapping User Roles to Scopes
有时候, 为了限制 token 的 scope, 不仅仅要根据指定的客户端的范围, 也要根据用户自己的权限来进行限制. 如果你在你的 AuthorizationEndpoint 用 DefaultOAuth2RequestFactory, 你可以设置 checkUserScopes=true 来限制匹配的用户角色的允许范围. AuthorizationServerEndpointsConfigurer 允许你注入一个自定义的 OAuth2RequestFactory
资源服务器配置
一个资源服务器 (可能与授权服务器是相同的应用, 也可能与授权服务器是分开的应用) 通过 OAuth2 Token 服务受保护的资源.
Spring OAuth 提供一个 Spring Security 认证过滤器来实现这个保护. 你可以
你可以在一个 @Configuration 类上用 @EnableResourceServer 来切换它, 并且用 ResourceServerConfigurer 配置它. 下列特性可以被配置:
tokenServices : 一个 ResourceServerTokenServices 的实例
resourceId : 资源 ID(推荐的, 如果存在的话会被授权服务器校验)
资源服务器的其它扩展端点
- request matchers for protected resources (defaults to all)
- access rules for protected resources (defaults to plain "authenticated")
其它通过 HttpSecurity 配置的自定义的受保护的资源
@EnableResourceServer 注释将自动添加一个 OAuth2AuthenticationProcessingFilter 类型的过滤器到 Spring 安全过滤器链中.
OAuth 2.0 客户端
客户端配置
对于 OAuth 2.0 客户端配置, 简化的配置用 @EnableOAuth2Client. 这个注解做两件事情:
创建一个过滤器 (ID 是 oauth2ClientContextFilter) 来存储当前的请求和上下文. 在请求期间需要进行身份认证时, 它管理重定向 URI.
在请求范围内创建一个 AccessTokenRequest 类型的 bean. 对于授权代码 (或隐式) 授予客户端是很有用的, 可以避免与单个用户相关的状态发生冲突.
AccessTokenRequest 可以用一个 OAuth2RestTemplate, 就像下面这样:
- @Autowired
- private OAuth2ClientContext oauth2Context;
- @Bean
- public OAuth2RestTemplate sparklrRestTemplate() {
- return new OAuth2RestTemplate(sparklr(), oauth2Context);
- }
访问受保护的资源
建议用 https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html 访问受保护的资源.
一般来说, web 应用程序不应该使用密码授予, 因此如果您可以支持 AuthorizationCodeResourceDetails, 请避免使用 ResourceOwnerPasswordResourceDetails.
为了和用户令牌 (授权码) 一起使用, 你应该考虑用 @EnableOAuth2Client 配置.
客户端持久化 Token
客户端不需要持久化令牌, 但是最好不要在每次重启客户端应用程序时都要求用户批准新的令牌授予.
ClientTokenServices 接口定义了为特定用户保存 OAuth 2.0 令牌所需的操作. 这是一个 JDBC 实现, 但是如果您希望实现自己的服务, 以便在持久数据库中存储访问令牌和相关的身份验证实例, 则可以这样做. 如果你想要使用这个特性, 你需要为 OAuth2RestTemplate 提供一个经过特殊配置的 TokenProvider. 例如:
- @Bean
- @Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
- public OAuth2RestOperations restTemplate() {
- OAuth2RestTemplate template = new OAuth2RestTemplate(resource(), new DefaultOAuth2ClientContext(accessTokenRequest));
- AccessTokenProviderChain provider = new AccessTokenProviderChain(Arrays.asList(new AuthorizationCodeAccessTokenProvider()));
- provider.setClientTokenServices(clientTokenServices());
- return template;
- }
参考
https://projects.spring.io/spring-security-oauth/docs/oauth2.html
来源: https://www.cnblogs.com/cjsblog/p/9184173.html