专业定制网站建设,网站seo收录,个人网站一定要备案吗,建设银行潍坊支行网站本专栏将从基础开始#xff0c;循序渐进#xff0c;以实战为线索#xff0c;逐步深入SpringSecurity相关知识相关知识#xff0c;打造完整的SpringSecurity学习步骤#xff0c;提升工程化编码能力和思维能力#xff0c;写出高质量代码。希望大家都能够从中有所收获#… 本专栏将从基础开始循序渐进以实战为线索逐步深入SpringSecurity相关知识相关知识打造完整的SpringSecurity学习步骤提升工程化编码能力和思维能力写出高质量代码。希望大家都能够从中有所收获也请大家多多支持。 专栏地址:SpringSecurity专栏 本文涉及的代码都已放在gitee上gitee地址 如果文章知识点有错误的地方请指正大家一起学习一起进步。 专栏汇总专栏汇总 文章目录13.1 编写你自己的authorization server实现13.2 定义用户管理13.3 向authorization server注册clients13.4 使用密码授予类型13.5 使用授权码授予类型13.6 使用客户端凭证授予类型13.7 使用刷新令牌授予类型本章包括实现OAuth 2authorization server管理authorization server的客户端使用OAuth 2授权类型
在本章中我们将讨论用Spring Security实现一个authorization server。正如你在第12章中学到的authorization server是OAuth 2架构中的一个组件图13.1。authorization server的作用是验证用户并向客户端提供一个令牌。客户端使用这个令牌来代表用户访问资源服务器所暴露的资源。你还了解到OAuth 2框架定义了多个获取令牌的流程。我们称这些流程为授予。你可以根据你的情况选择不同的授予方式之一。authorization server的行为会根据所选择的授予而有所不同。在本章中你将学习如何用Spring Security为最常见的OAuth 2授权类型配置authorization server。
授权码Authorization code grant授予类型密码授予Password grant类型客户凭证授予Client credentials grant类型
你还将学习如何配置authorization server来发行刷新令牌。客户端使用刷新令牌来获得新的访问令牌。如果一个访问令牌过期了客户端必须获得一个新的。要做到这一点客户有两个选择使用用户凭证重新认证或使用刷新令牌。我们在12.3.4节中讨论了使用刷新令牌比用户凭证的优势。 图13.1 authorization server是OAuth 2的角色之一。它识别资源所有者并向客户提供一个访问令牌。客户端需要该访问令牌来代表用户访问资源。
Spring Security团队宣布正在开发一个新的authorization serverhttp://mng.bz/4Be5 https://spring.io/projects/spring-authorization-server。可以通过这个链接了解不同的Spring Security项目中所实现的功能http://mng.bz/Qx01。
在本章我我们实现一个自定义的authorization server可以帮助你更好地理解这个组件的工作原理。 当然这也是目前实现authorization server的唯一方法。
我看到开发人员在他们的项目中应用这种方法。如果你不得不和一个这样实现authorization server的项目打交道那么在你使用新的实现之前你还是要理解它。
你可以使用第三方工具如Keycloak或Okta而不是实现一个自定义的authorization serverauthorization server。在第18章中我们将在我们的实践案例中使用Keycloak。但根据我的经验有时利益相关者不会接受使用这样的解决方案你需要去实现自定义代码。让我们在本章下面的章节中学习如何做到这一点并更好地理解authorization server。
13.1 编写你自己的authorization server实现
没有authorization server就没有OAuth 2的流程。正如我前面所说OAuth 2主要是为了获得一个访问令牌。而authorization server是OAuth 2架构中发放访问令牌的组成部分。所以你首先需要知道如何实现它。然后在第14章和第15章中你将学习资源服务器如何根据客户端从authorization server获得的访问令牌来授权请求。让我们开始构建一个authorization server。首先你需要创建一个新的Spring Boot项目并添加以下代码片断中的依赖项。
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-security/artifactId
/dependency
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId
/dependency
dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-oauth2/artifactId
/dependency在项目中你还需要为spring-cloud-dependencies添加dependencyManagement标签。接下来的代码片段显示了这一点。
dependencyManagementdependenciesdependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-dependencies/artifactIdversionHoxton.SR12/versiontypepom/typescopeimport/scope/dependency/dependencies
/dependencyManagement我们现在可以定义一个配置类我称之为AuthServerConfig。除了经典的Configuration注解我们还需要给这个类加上EnableAuthorizationServer注解。这样我们就指示Spring Boot启用OAuth 2authorization server的特定配置。我们可以通过扩展AuthorizationServerConfigurerAdapter类和重写特定的方法来定制这种配置我们将在本章讨论。下面列出了AuthServerConfig类。
代码清单13.1 AuthServerConfig类
package com.hashnode.proj0001firstspringsecurity.config;import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;Configuration
EnableAuthorizationServer
public class AuthServerConfigextends AuthorizationServerConfigurerAdapter {
}
我们已经有了authorization server的最低配置。然而为了使其可用我们仍然需要实现用户管理注册至少一个客户端并决定支持哪些授权类型。
13.2 定义用户管理
在本节中我们将讨论用户管理。authorization server是处理OAuth 2框架中的用户认证的组件。因此它自然需要管理用户。幸运的是用户管理的实现与你在第3章和第4章中学到的并没有改变。我们继续使用UserDetails、UserDetailsService和UserDetailsManager接口来管理凭证。而为了管理密码我们继续使用PasswordEncoder接口。在这里这些接口具有相同的角色并且与你在第3章和第4章中学到的相同。
图13.2提醒你在Spring Security的认证过程中的主要组件。你应该观察到与我们之前描述的认证架构不同的是我们在这张图中不再有SecurityContext了。发生这种变化是因为认证的结果并不存储在SecurityContext中。认证是通过TokenStore的令牌来管理的。你会在第14章中了解到更多关于TokenStore的信息在那里我们讨论资源服务器。 图13.2 认证过程。一个过滤器拦截用户请求并将认证责任委托给 authentication manager。此外 authentication manager使用一个authentication provider来实现认证逻辑。为了找到用户authentication provider使用UserDetailsService为了验证密码认证提供者使用PasswordEncoder。
让我们来看看如何在我们的authorization server中实现用户管理。我总是喜欢把配置类的责任分开。为此我选择在我们的应用程序中定义第二个配置类在那里我只写用户管理所需的配置。我把这个类命名为WebSecurityConfig你可以在下面的代码中看到它的实现。
package com.hashnode.proj0001firstspringsecurity.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;Configuration
//扩展WebSecurityConfigurerAdapter以访问AuthenticationManager实例。
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {Beanpublic UserDetailsService uds() {InMemoryUserDetailsManager uds new InMemoryUserDetailsManager();UserDetails user User.withUsername(john).password(12345).authorities(read).build();uds.createUser(user);return uds;}Beanpublic PasswordEncoder passwordEncoder() {return NoOpPasswordEncoder.getInstance();}//将AuthenticationManager实例作为Spring上下文中的一个Bean加入。OverrideBeanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}}我们现在可以修改AuthServerConfig类将Authentication- Manager注册到authorization server上。接下来的代码显示了你需要在AuthServerConfig类中进行的修改。
package com.hashnode.proj0001firstspringsecurity.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;Configuration
EnableAuthorizationServer
public class AuthServerConfigextends AuthorizationServerConfigurerAdapter {//从上下文中注入AuthenticationManager实例Autowiredprivate AuthenticationManager authenticationManager;//重写configure()方法以设置AuthenticationManager。Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) {endpoints.authenticationManager(authenticationManager);}
}
有了这些配置我们现在有了可以在我们的认证服务器上进行认证的用户。但是OAuth 2的架构意味着用户要授予客户端权限。代表用户使用资源的是客户端。在第13.3节你将学习如何为authorization server配置客户端。
13.3 向authorization server注册clients
在这一节中你将学习如何让authorization server知道你的客户端。 为了调用authorization server在OAuth 2架构中作为客户端的应用程序需要有自己的凭证。authorization server也会管理这些凭证只允许来自已知客户端的请求图13.3。 图13.3 authorization server存储用户和客户的凭证。它使用客户端的凭证因此它只允许已知的应用程序被它授权。
你还记得我们在第十二章开发的客户端程序吗我们用GitHub作为我们的认证服务器。GitHub需要知道这个客户端程序所以我们做的第一件事就是在GitHub上注册这个程序。然后我们收到了一个客户ID和一个客户密码客户凭证。我们配置了这些凭证然后我们的应用就用它们与授权服务器GitHub进行认证。在这种情况下也是如此。我们的authorization server需要知道它的客户因为它接受来自客户的请求。在这里这个过程应该变得很熟悉。定义授权服务器的客户端的接口是ClientDetails。定义通过ID检索ClientDetails的对象的接口是ClientDetailsService。
这些名字听起来很熟悉吗这些接口的工作方式与UserDetails和UserDetailsService接口类似但这些接口代表的是客户端。你会发现我们在第3章中讨论的许多东西对ClientDetails和ClientDetailsService的作用是相似的。例如我们的InMemoryClientDetailsService是ClientDetailsService接口的一个实现它在内存中管理ClientDetails。它的工作原理类似于InMemoryUserDetailsManager类的UserDetails。同样地JdbcClientDetailsService与JdbcUserDetailsManager类似。图13.4显示了这些类和接口以及它们之间的关系。 图13.4 我们用来定义authorization server客户端管理的类和接口之间的依赖关系
我们可以把这些相似之处总结为几点你可以很容易地记住
ClientDetails是为客户提供的正如UserDetails是为用户提供的。ClientDetailsService对客户来说就像UserDetailsService对用户来说一样。InMemoryClientDetailsService对客户来说就像InMemoryUserDetailsManager对用户来说一样。JdbcClientDetailsService对客户而言就像JdbcUserDetails- Manager对用户而言一样。
代码清单13.5向你展示了如何使用InMemoryClientDetailsService定义一个客户端配置并进行设置。我在清单中使用的BaseClientDetails类是Spring Security提供的ClientDetails接口的一个实现。在代码清单13.6中你可以找到一种更简短的方法来编写同样的配置。
代码清单13.5 使用InMemoryClientDetailsService来配置一个客户端
package com.hashnode.proj0001firstspringsecurity.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;Configuration
EnableAuthorizationServer
public class AuthServerConfigextends AuthorizationServerConfigurerAdapter {//Omitted code//重写configure()方法来设置ClientDetailsService实例。Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {//使用ClientDetailsService的实现创建一个实例。InMemoryClientDetailsService service new InMemoryClientDetailsService();//创建一个ClientDetails的实例并设置所需的关于客户的细节。BaseClientDetails cd new BaseClientDetails();cd.setClientId(client);cd.setClientSecret(secret);cd.setScope(List.of(read));cd.setAuthorizedGrantTypes(List.of(password));//在InMemoryClientDetailsService中添加ClientDetails实例。service.setClientDetailsStore(Map.of(client, cd));//配置ClientDetailsService供我们的authorization server使用。clients.withClientDetails(service);}}清单13.6提出了一个更短的方法来编写相同的配置。这使我们能够避免重复写出更干净的代码。
清单13.6 在内存中配置ClientDetails
package com.hashnode.proj0001firstspringsecurity.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.client.InMemoryClientDetailsService;Configuration
EnableAuthorizationServer
public class AuthServerConfigextends AuthorizationServerConfigurerAdapter {//Omitted codeOverridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {//使用ClientDetailsService实现来管理存储在内存中的ClientDetails。clients.inMemory()//构建并添加一个ClientDetails的实例。.withClient(client).secret(secret).authorizedGrantTypes(password, refresh_token).scopes(read);}}
为了少写代码我更喜欢使用较短的版本而不是代码清单13.5中更详细的版本。但是如果你写的实现是将客户的详细信息存储在一个数据库中这主要是现实世界中的情况那么最好是使用代码清单13.5中的实现。 注意 正如我们对UserDetailsService所做的那样在这个例子中我们使用一个在内存中管理细节的实现。这种方法只适用于例子和研究目的。在现实世界中你会使用一个持久化这些细节的实现通常是在一个数据库中。 13.4 使用密码授予类型
在这一节中我们使用了带有OAuth 2密码授予的authorization server。好吧我们主要是测试它是否达到预期因为通过我们在第13.2和13.3节所做的实现我们已经有一个使用密码授予类型的authorization server。我告诉过你这很容易!图13.5提醒了你密码授予类型和授权服务器在这个流程中的位置。 图13.5 密码授予类型。授权服务器收到用户的凭证并对用户进行认证。如果凭证是正确的授权服务器会发出一个访问令牌客户可以用它来调用属于被认证用户的资源。
细节作为查询参数。正如你在第12章所知道的我们需要在这个请求中发送的参数是:
grant_type的值为password用户名和密码这是用户凭证scope也就是授予的权力
在下一个代码片断中你看到了cURL命令。
#如果网址携带了“拼接的多个参数如果不做处理”“后面的参数无法取到。
#这个时候需要对”“进行转义包括两个步骤
#1.使用英文模式下输入的单引号将参数包含。
#2.使用^符号对”符号进行转义。
curl -v -XPOST -u client:secret http://localhost:8080/oauth/token?grant_typepassword^usernamejohn^password12345^scoperead运行这个命令你会得到这样的响应
{access_token:de1c975a-5268-49df-a910-20e2b970da59,token_type:bearer,refresh_token:53c4e171-204b-42da-9e56-5b185a9ba915,expires_in:43199,scope:read}观察响应中的访问令牌。在Spring Security的默认配置下令牌是一个简单的UUID。客户端现在可以使用这个令牌来调用资源服务器所暴露的资源。在第13.2节中你学到了如何实现资源服务器同时也学到了更多关于自定义令牌的知识。
13.5 使用授权码授予类型
在这一节中我们将讨论如何为授权码授予类型配置授权服务器。你在第12章开发的客户端应用程序中使用了这种授予类型你知道它是最常用的OAuth 2授予类型之一。 了解如何配置你的授权服务器以适应这种授予类型是很有必要的因为你很可能会在现实世界的系统中发现这种需求。因此在这一节中我们写一些代码来证明如何让它与Spring Security一起工作。从图13.6中你可以回忆起授权代码授予类型是如何工作的以及授权服务器如何与这个流程中的其他组件进行交互。 图13.6 在授权代码授予类型中客户端将用户重定向到授权服务器进行认证。用户直接与授权服务器进行交互一旦通过认证授权服务器就会向客户端返回一个重定向URI。当它回拨给客户端时它也提供一个授权码。客户端使用授权码来获得一个访问令牌。
正如你在第13.3节中所学到的这完全是关于你如何注册客户的问题。所以你需要做的就是在客户端注册中设置另一种授予类型如清单13.7所示。对于授权码授予类型你还需要提供重定向URI。这是授权服务器在完成认证后将用户重定向到的URI。在调用重定向URI时授权服务器也会提供访问代码。
代码清单13.7 设置授权码授予类型
package com.laurentiuspilca.ssia.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;Configuration
EnableAuthorizationServer
public class AuthServerConfigextends AuthorizationServerConfigurerAdapter {Autowiredprivate AuthenticationManager authenticationManager;Overridepublic void configure(ClientDetailsServiceConfigurer clients)throws Exception {clients.inMemory().withClient(client).secret(secret).authorizedGrantTypes(authorization_code).scopes(read).redirectUris(http://localhost:9090/home);}Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) {endpoints.authenticationManager(authenticationManager);}}你可以有多个客户每个客户可能使用不同的权限。但也有可能为一个客户设置多个授权。授权服务器会根据客户的请求采取行动。看一下下面的代码看看你如何为不同的客户配置不同的授权。
清单13.8 配置具有不同授予类型的客户端
Configuration
EnableAuthorizationServer
public class AuthServerConfigextends AuthorizationServerConfigurerAdapter {// Omitted codeOverridepublic void configure(ClientDetailsServiceConfigurer clients)throws Exception {clients.inMemory().withClient(client1).secret(secret1)//ID为client1的客户只能使用authority_code授权。.authorizedGrantTypes(authorization_code).scopes(read).redirectUris(http://localhost:9090/home).and().withClient(client2).secret(secret2)//ID为client2的客户可以使用授权码、密码和刷新令牌中的任何一种。.authorizedGrantTypes(authorization_code, password, refresh_token).scopes(read).redirectUris(http://localhost:9090/home);}Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) {endpoints.authenticationManager(authenticationManager);}
}为一个客户使用多种授权类型 正如你所学到的可以为一个客户允许多种授予类型。但是你必须谨慎对待这种方法因为从安全的角度来看它可能会暴露出你在架构中使用了错误的做法。授予类型是客户端应用程序获得访问令牌的流程这样它就可以访问一个特殊的资源。当你在这样的系统中实现客户端时就像我们在第12章中做的那样你要根据你使用的授予类型来编写逻辑。 那么在授权服务器端为同一个客户分配多个授权类型的原因是什么呢我在一些系统中看到我认为这是一种不好的做法最好避免那就是共享客户证书。共享客户凭证意味着不同的客户程序共享相同的客户凭证。 在共享客户端凭证时多个客户端使用相同的凭证从授权服务器获得访问令牌。 在OAuth 2的流程中客户端即使它是一个应用程序也作为一个独立的组件有自己的凭证用来识别自己。因为你不分享用户凭证所以你也不应该分享客户端凭证。即使所有定义客户的应用程序都是同一个系统的一部分也不能阻止你在授权服务器层面上将这些客户注册为独立的客户。在授权服务器上单独注册客户端会带来以下好处。 它提供了从每个应用程序单独审计事件的可能性。 当你记录事件时你知道哪个客户端产生了这些事件。它允许更强的隔离性。如果一对凭证丢失只有一个客户端受到影响。允许范围的分离。你可以给一个以特定方式获得令牌的客户分配不同的范围授予的权限。 范围分离是最基本的如果管理不当会导致奇怪的情况。让我们假设你定义了一个客户端就像下一个代码片断中介绍的那样。 clients.inMemory().withClient(client).secret(secret).authorizedGrantTypes(authorization_code,client_credentials).scopes(read)这个客户端被配置为授权代码和客户端凭证授予类型。 使用其中任何一种客户端都会获得一个访问令牌这为它提供了读取权限。这里奇怪的是客户端可以通过认证用户或只使用自己的凭证来获得相同的令牌。这没有道理甚至可以说这是一个安全漏洞。即使这对你来说听起来很奇怪我在一个被要求审计的系统中也看到了这种做法。为什么那个系统的代码是这样设计的最有可能的是开发人员并不了解授予类型的目的而是使用了他们在网上找到的一些代码。请确保你避免此类错误。要小心。为了指定授予类型你使用字符串而不是枚举值这种设计可能导致错误。是的你可以写一个像这个代码片断中提出的配置。 clients.inMemory().withClient(client).secret(secret).authorizedGrantTypes(password, hocus_pocus).scopes(read)只要你不尝试使用 hocus_pocus 授予类型该应用程序将实际工作。 让我们使用代码清单13.9中的配置来启动应用程序。当我们想接受授权码授予类型时服务器还需要提供一个客户端重定向用户登录的页面。我们使用你在第五章学到的formlogin配置来实现这个页面。你需要重写configure()方法如下所示。
Configuration
public class WebSecurityConfigextends WebSecurityConfigurerAdapter {// Omitted codeOverrideprotected void configure(HttpSecurity http)throws Exception {http.formLogin();}
}AuthServerConfig类代码如下
package com.hashnode.proj0001firstspringsecurity.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;Configuration
EnableAuthorizationServer
public class AuthServerConfigextends AuthorizationServerConfigurerAdapter {Autowiredprivate AuthenticationManager authenticationManager;Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory().withClient(client).secret(secret).authorizedGrantTypes(authorization_code, refresh_token).scopes(read).redirectUris(http://localhost:9090/home);}Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) {endpoints.authenticationManager(authenticationManager);}Overridepublic void configure(AuthorizationServerSecurityConfigurer security) {security.checkTokenAccess(isAuthenticated());}}现在你可以启动应用程序并在浏览器中访问链接如下面的代码段所示。然后你会被重定向到图13.7所示的登录页面。
http://localhost:8080/oauth/authorize?response_typecodeclient_idclientscoperead图13.7 授权服务器将你重定向到登录页面。在它验证了你的身份后它把你重定向到所提供的重定向URI。
登录后授权服务器会明确要求你授予或拒绝所请求的作用域。图13.8显示了这个表格。 图13.8 认证后授权服务器要求你确认你要授权的范围。
一旦你授予了这些作用域授权服务器就会把你重定向到重定向URI并提供一个访问令牌。在下一个代码片段中你会发现授权服务器将我重定向到的URL。观察一下客户端通过请求中的查询参数得到的访问代码
http://localhost:9090/home?codeQSEJW9
其中code是授权码
你的应用程序现在可以使用授权码来获得一个调用/oauth/token端点的令牌。
curl -v -XPOST -u client:secret http://localhost:8080/oauth/token?grant_typeauthorization_code^scoperead^codeQSEJW9响应如下
{access_token:72e70268-80ea-428f-838e-5ef881ef3eac,token_type:bearer,refresh_token:51d2ff63-0b2d-4de5-ac98-e7c15ffc6aec,expires_in:43199,scope:read}请注意一个授权码只能使用一次。如果你试图再次使用相同的代码调用/oauth/ token端点你会收到像下一个代码片断中显示的错误。你只能通过要求用户再次登录来获得另一个有效的授权码。
{error:invalid_grant,error_description:Invalid authorization code: QSEJW9}13.6 使用客户端凭证授予类型
在这一节中我们将讨论实现客户凭证授予类型。你可能还记得在第12章中我们将这种授予类型用于后端到后端的认证。在这种情况下它不是强制性的但有时我们会看到这种授予类型是我们在第8章讨论的API密钥认证方法的替代品。当我们保护一个与特定用户无关的、客户需要访问的api时我们也可能使用客户凭证授予类型。比方说你想实现一个返回服务器状态的api。客户端调用这个api来检查连接情况并最终向用户显示连接状态或错误信息。因为这个api只代表客户端和资源服务器之间的交易而不涉及任何用户特定的资源所以客户端应该能够调用它而不需要用户进行认证。对于这种情况我们使用客户端凭证授予类型。图13.9提醒你客户端凭证授予类型是如何工作的以及授权服务器如何与这个流程中的其他组件进行交互。 客户端凭证授予类型不涉及用户。一般来说我们使用这种授予类型在两个后端解决方案之间进行认证。客户端只需要它的凭证来验证和获得访问令牌。
正如你所期望的那样要使用客户凭证授予类型必须用这个授予类型注册一个客户。在接下来的代码中你可以找到客户端的配置它使用了这种授予类型。
Configuration
EnableAuthorizationServer
public class AuthServerConfigextends AuthorizationServerConfigurerAdapter {// Omitted codeOverridepublic void configure(ClientDetailsServiceConfigurer clients)throws Exception {clients.inMemory().withClient(client).secret(secret).authorizedGrantTypes(client_credentials).scopes(info);}
}你现在可以启动应用程序并调用/oauth/token api来获取访问令牌。下一个代码片断向你展示了如何获得这个
curl -v -XPOST -u client:secret http://localhost:8080/oauth/token?grant_typeclient_credentials^scopeinfo响应为
{access_token:e0e7f45f-48a9-48c0-97cc-04e24329334e,token_type:bearer,expires_in:43199,scope:info}对客户凭证授予类型要小心。这种授予类型只要求客户端使用其凭证。需要确保你不能像下图那样授予权限否则你可能允许客户端访问用户的资源而不需要用户的许可。图13.10展示了这样一个设计开发者通过允许客户端调用用户的资源api而不需要用户首先进行认证从而造成了安全漏洞。 图13.10 开发者想为客户端提供调用/info端点的可能性而不需要得到用户的许可。但由于这些使用了相同的范围他们现在也允许客户端调用/transactions api这是一个用户的资源。
13.7 使用刷新令牌授予类型
在这一节中我们将讨论在用Spring Security开发的授权服务器中使用刷新令牌。你可能还记得第12章当与另一种授予类型一起使用时刷新令牌具有一些优势。你可以在授权码授予类型和密码授予类型中使用刷新令牌图13.11。 图13.11 当用户认证时除了访问令牌客户端还收到一个刷新令牌。客户端使用刷新令牌来获得新的访问令牌。
如果你想让你的授权服务器支持刷新令牌你需要在客户端的授予列表中加入刷新令牌授予。
Configuration
EnableAuthorizationServer
public class AuthServerConfigextends AuthorizationServerConfigurerAdapter {// Omitted codeOverridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory().withClient(client).secret(secret)//在客户端的授权授予类型列表中添加 refresh_token。.authorizedGrantTypes(password,refresh_token).scopes(read);}
}现在试试你在第13.4节中使用的同样的cURL命令。你会看到响应是类似的但现在包括一个刷新令牌。
curl -v -XPOST -u client:secret http://localhost:8080/oauth/token?grant_typepassword^usernamejohn^password12345^scoperead响应为
{access_token:8c3b248d-5cc1-42e7-a56b-5aabeb01c7b8,token_type:bearer,refresh_token:d88665e3-f10e-4045-85ed-97c1ffc1785d,expires_in:43199,scope:read}总结 ClientRegistration接口定义了Spring Security中的OAuth 2客户端注册。ClientRegistrationRepository接口描述了负责管理客户端注册的对象。这两个接口允许你自定义你的授权服务器如何管理客户端注册。对于用Spring Security实现的授权服务器来说客户端的注册决定了授予类型。同一个授权服务器可以为不同的客户提供不同的授予类型。这意味着你不需要在你的授权服务器中实现特定的东西来定义多种授予类型。对于授权码授予类型授权服务器必须向用户提供登录的可能性。这一要求是由于在授权代码流程中用户资源所有者直接在授权服务器上授权给客户端访问的结果。一个客户端注册可以请求多种授予类型。这意味着一个客户可以在不同情况下使用例如密码和授权码授予类型。我们使用客户端证书授予类型来进行后端到后端的授权。 技术上有可能但不常见的是客户端请求客户端证书授予类型与另一个授予类型一起。我们可以将刷新令牌与授权码授予类型和密码授予类型一起使用。通过将刷新令牌添加到客户注册中我们指示授权服务器在访问令牌之外也发放一个刷新令牌。客户端使用刷新令牌来获得新的访问令牌而不需要再次验证用户。