【问题标题】:Implement Spring OAuth2 using JDBC with JWT and customize existing grant flows using XML based configuration使用带有 JWT 的 JDBC 实现 Spring OAuth2,并使用基于 XML 的配置自定义现有的授权流
【发布时间】:2017-07-24 23:56:51
【问题描述】:

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

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

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

请查看我对我遵循的方法的回答,如果您有任何建议、建议和反馈,请随时分享。

这个问题的主要目的是

  • 就我遵循的方法获取反馈、建议和建议
  • 分享我在艰难中学到的所有东西,希望为他人省去麻烦,并将我从社区中学到的东西回馈给社区。​​li>

【问题讨论】:

    标签: java spring spring-security oauth-2.0 spring-security-oauth2


    【解决方案1】:

    我从主要使用 XML 配置的 Spring Security 框架的基本设置开始。

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

    我从 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. 启用验证令牌 endpiont /oauth/check_token 并验证 JWT 令牌自定义声明

    而且,我已经实现了如下

    1。整理整理spring-security.xml

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

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

    我想保持 身份验证资源服务器 分开,因此,我剥离了所有 受保护的端点 配置和内存用户服务和 oauth 客户端详细信息。

    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 接口提供自定义实现并覆盖继承的loadByUsername 方法来查询我的自定义用户数据库表并检索用户及其角色并构造org.springframework.security.core.userdetails.User 的实例并返回它。
    2. 为上述自定义实现创建一个<bean>,我们将其命名为MyUserDetailsService,并将其放在spring-security.xml配置文件中。如下

      <bean id="myUserDetailsService" class="your.package.hierarcy.goes.here.MyUserDetailsService" />

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

    <bean id="daoAuthenticationProvider"    class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
          <property name="userDetailsService" ref="myUserDetailsService"/>
          <!-- if you encode or encrypt your password, then pass encoder-->
          <!--<property name="passwordEncoder" ref="passwordEncoder"/>-->
        </bean>
    

    最后我们需要将上面的AuthenticationProvider注入到spring-security.xml中指定的AuthenticationManager中,如下图

    <!-- Original --> 
    <authentication-manager alias="authenticationManager"
            xmlns="http://www.springframework.org/schema/security">
            <authentication-provider>
                <user-service id="userDetailsService">
                    <user name="marissa" password="koala" authorities="ROLE_USER" />
                    <user name="paul" password="emu" authorities="ROLE_USER" />
                </user-service>
            </authentication-provider>
        </authentication-manager>
    

    把上面改成下面

    <!-- Modified with our own implementation of UserDetailsService --> 
    <authentication-manager alias="authenticationManager"
            xmlns="http://www.springframework.org/schema/security">
            <authentication-provider ref="daoAuthenticationProvider" />
        </authentication-manager>
    

    现在要使用上面使用的身份验证管理器,请将其传递给&lt;oauth:authorization-server&gt; 中的授权流,如下所示

    <oauth:password authentication-manager-ref="authenticationManager"/>
    

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

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

    clientCredentialsTokenEndpointFilter

    参考文献

    clientAuthenticationManager豆子

    参考文献

    clientDetailsUserDetailsService

    参考文献

    clientDetails

    clientDetails 只不过是在&lt;oauth:client-details-service id="clientDetails"&gt; 标签下声明的固定oauth 客户端列表。

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

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

    默认 Spring OAuth2 JdbcClientDetailsS​​ervice bean 定义

    <bean class="org.springframework.security.oauth2.provider.client.JdbcClientDetailsService" id="myClientDetails">
          <constructor-arg index="0">
             <!-- This is your jdbc datasource, i.e. db details-->
             <ref bean="dataSource" />
          </constructor-arg>
       </bean>
    

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

    <bean id="clientDetailsUserService"
    class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
            <constructor-arg ref="myClientDetails" />
        </bean>
    

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

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

    1. 向默认的oauth_client_details表添加额外的列

    给定以下(在默认的 spring-security.xml 中)

    clientCredentialsTokenEndpointFilter

    参考文献

    clientAuthenticationManager豆子

    参考文献

    clientDetailsUserDetailsService

    参考文献

    clientDetails

    我们需要转换上述工作流程(或引用链),以便 Spring OAuth2 能够访问我们新定制的 oauth_client_details 数据库表。所以新的工作流程或引用链应该类似于下面的

    clientCredentialsTokenEndpointFilterbean(不变)

    参考文献

    clientAuthenticationManagerbean(不变)

    参考文献

    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 而不是从一开始就实现它(利用所有可以从 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 &amp; ClientRegistrationService 接口。在实现我们自己的 JdbcClientDetailsS​​ervice 时,我不建议扩展默认的 JdbcClientDetailsS​​ervice,因为有很多私有类变量(和方法)不会被继承,所以让我们使用默认实现来编写我们自己的。

    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​​ervicespring-security.xml 中为它创建一个 bean,如下行

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

    <!-- Original referencing hardcoded clients -->
    <bean id="clientDetailsUserService"
            class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
            <constructor-arg ref="clientDetails" />
        </bean>
    

    以上需要改成如下

    <bean id="clientDetailsUserService"
            class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
            <constructor-arg ref="myJdbcClientDetailsService" />
        </bean>
    

    我们去,现在链在指定我们自己的 ClientDetailsS​​ervice 后如下所示:

    clientCredentialsTokenEndpointFilterbean(不变)

    参考文献

    clientAuthenticationManagerbean(不变)

    参考文献

    clientDetailsUserDetailsService(参考更新)

    参考文献

    myJdbcClientDetailsService(我们定制的客户详细信息服务)

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

    4。使用 JWT Token 并想添加额外的声明

    JWT Token 或 JSON Web Token 可以在 OAuth2 的三个不同参与者(授权服务器参与者、资源服务器参与者、客户端参与者)之间使用,以便在执行与 授权相关的不同操作时相互通信 和访问受保护的资源授权服务器使用 JWT 令牌,它使用公钥/私钥进行签名,目的是确保 JWT 令牌的内容不会被更新(除非有人有权访问私钥)。基本工作流程如下(假设密码授予流程)

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

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

    有关 Spring OAuth2 的 JWT 实现的更多信息,请参阅官方 repo here 和 JWT webpage

    我们看看,我们有下面的JWT

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

    我们希望在上述令牌中包含一组范围,以便资源服务器参与者知道该用户是否被允许发出此请求。更改可能如下所示

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

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

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

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

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

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

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

    <oauth:authorization-server
            client-details-service-ref="clientDetails" token-services-ref="tokenServices"
            user-approval-handler-ref="userApprovalHandler">
            <oauth:authorization-code />
            <oauth:implicit />
            <oauth:refresh-token />
            <oauth:client-credentials />
            <oauth:password />
        </oauth:authorization-server>
    

    当我们向 /oauth/token 端点发出 OAuth2 请求时,该请求首先映射到 ClientCredentialsTokenEndpointFilter,然后在某个阶段从那里将请求委托给 org.springframework.security.oauth2.provider.endpoint.TokenEndpoint,如果您查看 source code TokenEndpoint 你会看到下面的

    private OAuth2RequestValidator oAuth2RequestValidator = new DefaultOAuth2RequestValidator();
    

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

    1. 创建一个引用我们自定义的 MyOAuth2RequestValidator 的 bean,并将其命名为“myOAuth2RequestValidator”
    2. 更新 spring-security 中的 &lt;oauth:authorization-server&gt; 以使用来自 #1 的自定义验证器

    见下方更新的授权服务器标签

    <oauth:authorization-server
                client-details-service-ref="clientDetails" token-services-ref="tokenServices"
                user-approval-handler-ref="userApprovalHandler"
                request-validator-ref="myOAuth2RequestValidator">
                <oauth:authorization-code />
                <oauth:implicit />
                <oauth:refresh-token />
                <oauth:client-credentials />
                <oauth:password />
            </oauth:authorization-server>
    

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

    6。禁用授权流程

    因此,我们可能不会使用所有可用的授权流,因此禁用那些未使用的授权流是有意义的。比方说,我不想使用 refresh-token 授权流程(只是一个示例)并且要禁用它,我们必须更新 &lt;oauth:authorization-server&gt;

    禁用 refresh-token 授权流程的授权服务器如下所示

    您还可以禁用其他流。显然你可以不禁用它,因为如果你不支持让我们说 Authorization Code grant flow 那么根本不注册一个具有 authorization_code 值的 oauth_client它是 oauth_client_table(如果是默认 oauth 表)的 authorized_grant_types 列。

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

    7。启用验证令牌端点和验证 JWT 自定义声明

    如果资源服务器和授权服务器不在同一个服务器,并且不共享同一个数据库,那么资源服务器需要在收到一个请求资源以确保令牌仍然有效(这在令牌不会过早过期时很有用)并具有正确的权限/声明。为了实现这一点,Spring OAuth 提供了 Check Token Endpoint,可以通过添加以下内容来启用

    check-token-enabled="true"
    

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

    • 有效期
    • 签名验证

    而且您必须进行一些自定义来验证您的自定义声明。有关详细信息,请参阅我在 here 中的其他帖子。

    【讨论】:

      猜你喜欢
      • 2018-10-28
      • 2021-09-19
      • 1970-01-01
      • 2020-07-09
      • 2018-11-04
      • 2022-11-30
      • 2021-07-09
      • 2020-10-23
      • 2019-10-01
      相关资源
      最近更新 更多