使用JDBC和JWT实现Spring OAuth2,并使用基于XML的配置自定义现有授权流

我开始使用Spring OAuth2,在这个过程中,我很难找到相关的教程和内容,主要是因为以下内容

  • 我不想使用Spring Boot
  • 我不想使用Java配置而是使用xml配置
  • 我需要根据我们的特定需求自定义Spring OAuth2授权流程和其他function
  • 我需要禁用一些授权流程
  • 我需要Spring OAuth2来使用自定义用户和角色
  • 我需要在自定义数据库选项卡中使用store oauth_client详细信息
  • 其他的东西

我设法编写了我的实现,解决了上述问题,现在我想分享我的发现,以便拯救他人的痛苦。

请参阅我的答案,了解我所采用的方法,如果您有任何建议,请随时分享您的建议,建议和反馈。

这个问题的主要目标是

  • 获得有关我遵循的方法的反馈,建议和建议
  • 分享我所学到的所有困难,希望能够拯救他人的麻烦,并回馈社区从社区中学到的东西。

我首先使用主要使用XML配置的Spring Security框架的基本设置。

Spring Security OAuth2我做了大量的谷歌搜索,看了一堆存储库,包括主要的Spring OAuth 2 repo。

我从OAuth2 XML Configs开始,我需要进行更改才能实现

  1. 组织,清理spring-security.xml OAuth2配置文件
  2. 指示Spring OAuth2从数据库中查找不同的(自定义的)用户和角色表
  3. 指示Spring OAuth2查找不同的(自定义的)oauth_client_details表(带有额外的列)
  4. 使用JWT令牌并进行必要的更改以自定义JWT令牌以包含自定义声明
  5. 开发OAuth2RequestValidator的自定义实现,并使用XML配置将其注入Token端点
  6. 禁用授权流程
  7. 启用validation令牌endpiont /oauth/check_token并validationJWT令牌自定义声明

而且,我已经达到了以下目的

1.组织并清理spring-security.xml

上面链接的默认spring-security.xml包含以下假设

  1. 授权服务器资源服务器都在同一台机器上。
  2. 使用内存中用户
  3. 使用内存中的oauth客户端

我想保持身份validation资源服务器分离,因此,我剥离了所有受保护的端点配置以及内存中的用户服务和oauth-client-details。

2.指示OAuth2查找不同的用户和角色数据库表

为实现这一目标,我确保了这一点

  1. MyUser对象实现以下org.springframework.security.core.userdetails.UserDetails并覆盖返回GrantedAuthority集合的getAuthorities
  2. MyRole对象实现org.springframework.security.core.GrantedAuthority并覆盖方法getAuthority

我现在需要告知Spring OAuth2有关上面定制的MyUser和MyRole的信息 ,为了做到这一点,我需要做以下事情

  1. org.springframework.security.core.userdetails.UserDetailsService接口提供自定义实现,并覆盖inheritance的loadByUsername方法以查询我的自定义用户数据库表并检索具有其角色的用户并构造org.springframework.security.core.userdetails.User的实例org.springframework.security.core.userdetails.User并归还。
  2. 创建上述自定义实现的 ,然后将其命名为MyUserDetailsService并将其放在spring-security.xml配置文件中。 如下

仅仅定义bean是不够的,我们必须告诉Spring OAuth2使用MyUserDetailsService并且为此我们需要将myUserDetailsService注入Spring OAuth2的默认身份validation提供程序或我们自己的身份validation提供程序,然后将其传递给authentication-manager元素,如下所示

      

最后,我们需要将上述身份validation提供程序注入spring-security.xml中指定的Authentication Manager,如下所示

          

用以下内容进行更改

     

现在使用上面使用的Authentication Manager,将其传递给的授权流程如下

  

3.指示Spring OAuth2查找不同的oauth_client_table

如果仔细查看上面链接的官方Spring OAuth2 repo中的默认spring-security.xml ,您将看到以下引用链

clientCredentialsTokenEndpointFilter bean

引用

clientAuthenticationManager bean

引用

clientDetailsUserDetailsService

引用

clientDetails

clientDetails只不过是在标记下最后声明的固定oauth客户端的列表。

好的,所以目标是指示Spring OAuth2从数据库读取和存储oauth_clients。 如果我们不需要自定义默认的spring oauth数据库表来满足我们的特定需求,那么它非常简单,过程如下

  1. 设置默认的Spring OAuth2数据库表,如此链接所示
  2. spring-security.xml中,更改clientDetailsUserDetailsService以传递默认的Spring OAuth2 org.springframework.security.oauth2.provider.client.JdbcClientDetailsService并声明与bean相同,如下所示。

Defautlt Spring OAuth2 JdbcClientDetailsS​​ervice bean定义

       

现在clientDetailsUserDetailsS​​ervice bean应该如下所示

    

而其他一切都保持不变。 上述更改将指示Spring OAuth2从数据库中读取oath_clients(oauth_client_details)而不是硬编码的xml配置。

如果您希望修改默认的oauth_client_details表以添加一些自定义列以满足您的特定需求,那么在这种情况下您需要进行一组不同的更改。 所以我们有以下目标

  1. 将额外列添加到默认的oauth_client_details

给出以下内容(在默认的spring-security.xml中

clientCredentialsTokenEndpointFilter bean

引用

clientAuthenticationManager bean

引用

clientDetailsUserDetailsService

引用

clientDetails

我们需要转换上述工作流(或引用链),以便Spring OAuth2能够访问我们新的自定义oauth_client_details数据库表。 因此,新的工作流程或参考链应类似于以下内容

clientCredentialsTokenEndpointFilter bean(无变化)

引用

clientAuthenticationManager bean(无变化)

引用

clientDetailsUserDetailsService (待更新)

引用

clientDetails (待更换)

为实现上述目标,让我们从下到上。 默认的Spring OAuth2使用org.springframework.security.oauth2.provider.ClientDetails从默认的oauth_client_details表加载oauth_client。 我们需要通过实现它来为org.springframework.security.oauth2.provider.ClientDetails提供自定义实现,因此,如下所示

 public class MyClientDetails implements ClientDetails { ... } 

在我们继续之前,我只想提一下,Spring OAuth2已经提供了上述接口的实现,这意味着我们可以通过实际扩展上述接口(ClientDetails)的唯一实现(即org.springframework.security.oauth2.provider.client.BaseClientDetails )来使我们的生活变得更加容易org.springframework.security.oauth2.provider.client.BaseClientDetails而不是从头开始实现它(利用所有可以从BaseClientDetails完成并添加我们自己的自定义字段)因此, MyClientDetails看起来如下

 public class MyClientDetails extends BaseClientDetails { //fields representing custom column for oauth_client //getters and setters //make sure to call super() in the inherited constructors, before //setting custom fields. } 

好了,现在我们有了自己的ClientDetails对象,就像#2一样,我们需要实现自己的JdbcClientDetailsS​​ervice ,它将被注入到clientDetailsUserDetailsService 。 为了实现我们自己的JdbcClientDetailsS​​ervice,让我们看一下默认方法的签名

 public class JdbcClientDetailsService extends Object implements ClientDetailsService, ClientRegistrationService 

如您所见,上面的类实现了ClientDetailsService & ClientRegistrationService接口。 在实现我们自己的JdbcClientDetailsS​​ervice时,我不建议扩展默认的JdbcClientDetailsS​​ervice,因为有很多私有类变量(和方法)不会被inheritance,所以让我们使用默认的实现来编写自己的。

 public class MyJdbcClientDetailsService implements ClientDetailsService, ClientRegistrationService { //override loadClientByClientId method, query the custom //oauth_client_details database table and populate the //MyClientDetails object created above and return it. //Implement other methods to CRUD oauth_clients } 

现在您已经实现了MyJdbcClientDetailsS​​ervice ,在spring-security.xml中为它创建了一个bean,如下所示

现在将上面的bean注入clientDetailsUserDetailsService ,并查看默认的spring-security.xml ,我们有以下内容

     

以上内容需要改为如下

    

我们去了,现在链条在指定我们自己的ClientDetailsS​​ervice之后看起来如下:

clientCredentialsTokenEndpointFilter bean(无变化)

引用

clientAuthenticationManager bean(无变化)

引用

clientDetailsUserDetailsService (ref updated)

引用

myJdbcClientDetailsService (我们的自定义客户端详细信息服务)

上面指示Spring OAuth2查找oauth_client详细信息的自定义数据库表。

4.使用JWT令牌并想要添加额外的声明

JWT令牌或JSON Web令牌可以在OAuth2(授权服务器角色,资源服务器角色,客户角色)的三个不同角色中使用,以便在执行与受保护资源的 授权和访问相关的不同动作时彼此通信。 授权服务器使用JWT令牌,并使用公钥/私钥对其进行签名,目的是确保JWT令牌的内容不会更新(除非有人有权访问私钥)。 基本工作流程如下(让我们假设密码授予流程)

  1. 客户端actor代表资源所有者actor (用户)向Authorization服务器actor请求OAuth2AccessToken请求
  2. 授权服务器角色validation请求客户端是否受信任(通过检查请求的客户端传递的client_id和client_secret – MyJdbcClientDetailsS​​ervice用于查找客户端),一旦validation客户端并与客户端密钥匹配,它将validation用户名密码 (这可以使用MyUserDetailsS​​ervice来查找我们的数据库用户表中的用户),这也是客户端actor (代表用户)传递的。 如果找到了用户,则其密码匹配(可能使用自定义密码编码器 – 即由于缓慢的哈希函数而建议使用BCrypt),如果一切正常,则它将检索用户角色并构造JWT令牌,然后使用它是自己的公钥/私钥,将JWT令牌与客户端详细信息一起存储在数据库中(oauth_access_token默认表),并将JWT令牌的副本返回给请求客户端。
  3. 然后, 客户端actor将用户的请求发送到资源服务器,要求提供沿JWT令牌传递的受保护资源 。 资源服务器validationJWT是否有效且未被篡改 ,然后相应地为客户端actor提供服务。

上面简要描述了OAuth2如何使用JWT令牌。 关于JWT和各种JWT的更多细节(签名,签名和加密等)。

有关Spring OAuth2的JWT实现的更多信息,请参阅此处的官方repo和JWT 网页 。

让我们看看,我们有以下JWT

 { "sub": "1234567890", "name": "John Doe", "admin": true } 

我们希望在上面的令牌中包含一组范围,因此资源服务器角色知道是否允许该用户发出此请求。 这种变化可能如下所示

 { "sub": "1234567890", "name": "John Doe", "admin": true, "scope": "read write delete" } 

要添加自定义声明(上面的每个键/值都称为声明),我们需要创建一个MyTokenEnhancer类,它实现org.springframework.security.oauth2.provider.token.TokenEnhacer接口并覆盖它的增强方法或扩展org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter ,它依次实现默认的TokenEnhancer并覆盖它的增强方法。

通过覆盖增强方法,您可以向OAuth2AccessToken添加自定义声明。 有关详细信息,请参阅上面链接的回购。

5.开发OAuth2RequestValidator的自定义实现并将其注入令牌端点

对我来说,oauth客户端发送一个自定义的OAuth2Request ,我需要的不仅仅是deafult DefaultOAuth2RequestValidator实现,因此,我必须编写自己的并实现org.springframework.security.oauth2.provider.OAuth2RequestValidator 。 以下几行

 public class MyOAuth2RequestValidator implements OAuth2RequestValidator { //override the necessary methods } 

现在我们已经创建了自己的自定义MyOAuth2RequestValidator,我们应该如何指示令牌端点使用XML配置来使用它? 嗯,这个花了我一点时间搞清楚。 查看默认的spring-security.xml,我们有以下内容

        

当我们向/oauth/token端点发出OAuth2请求时,请求首先映射到ClientCredentialsTokenEndpointFilter ,然后在某个阶段将请求委托给org.springframework.security.oauth2.provider.endpoint.TokenEndpoint ,如果你看在源代码 TokenEndpoint,您将看到以下内容

 private OAuth2RequestValidator oAuth2RequestValidator = new DefaultOAuth2RequestValidator(); 

现在用我们的自定义validation器MyOAuth2RequestValidator替换它,我们需要执行以下操作

  1. 创建一个引用我们的自定义MyOAuth2RequestValidator的bean,并为其命名为“myOAuth2RequestValidator”
  2. 更新spring-security中以使用#1中的自定义validation器

请参阅下面更新的授权服务器标记

        

就是这样。 现在,每次命中TokenEndpoint时 ,它都将使用我们的自定义validation器而不是默认validation器。

6.禁用授权流程

因此,我们可能不会使用所有可用的授权流,因此禁用那些未使用的授权流是有意义的。 比方说,我不想使用刷新令牌授权流程(只是一个例子)并禁用它,我们必须更新

具有禁用刷新令牌授权流的授权服务器如下所示

您还可以禁用其他流程。 很明显,你可以放弃它而不是禁用它,因为如果你不支持让我们说授权代码授权流,那么就不要在其oauth_client_table(如果默认oauth表)的authorized_grant_types列中注册一个具有authorization_code值的oauth_client。

另外,为了与您分享帮助我了解Spring OAuth2的经验,我实际调试了Spring OAuth2类,以了解每个工作流如何工作并跟踪在不同授权流程中遇到的所有类。 让您全面了解Spring OAuth2的工作原理,然后在Spring OAuth2之上实现您自己的OAuth2实现变得不那么痛苦。

7.启用validation令牌端点和validationJWT自定义声明

如果资源和授权服务器不在同一服务器中且不共享同一数据库,则资源 服务器在收到资源请求后需要与授权服务器进行双重检查,以确保该令牌仍然有效(这很有用)当令牌不会过早到期时)并具有正确的权限/声明。 为了实现这一目标,OAuth提供了可以通过添加以下内容来启用的Check Token Endpoint

 check-token-enabled="true" 

到XML配置文件中的元素。 添加上述内容后,对带有密钥token和值JWT access token的表单参数的{server-url}/oauth/check_token的POST请求将指示授权服务器validation令牌是否有效。 默认实现执行默认检查,例如

  • 到期日
  • 签名validation
  • 等等

您必须进行一些自定义以validation您的自定义声明。 有关详细信息,请参阅此处的其他post。