有时以前,我们发表了一篇文章,分享了一种在云环境中实现无状态会话的自定义方法。 今天,让我们探讨为Spring Boot应用程序设置Oauth2身份验证的另一个流行用例。 在此示例中,我们将使用JSON Web令牌(JWT)作为Oauth2令牌的格式。
该示例部分是基于Spring Security Oauth 2的官方示例开发的。但是,我们将专注于理解Oauth 2请求的原理。
源代码位于https://github.com/tuanngda/spring-boot-oauth2-demo.git
背景
Oauth2和JWT
当您要使用Oauth2和JWT时,我们将不做详细介绍。 通常,如果需要允许其他人为您的服务构建前端应用程序,则可能需要采用Oauth。 我们专注于Oauth2和JWT,因为它们是市场上最流行的身份验证框架和协议。
Spring安全Oauth 2
Spring Security Oauth2是Oauth 2的实现,它是在Spring Security之上构建的,Spring Security是一个非常可扩展的身份验证框架。
总体而言,Spring Security包括2个基本步骤:为每个请求创建一个身份验证对象,并根据身份验证应用授权检查。 第一步是在多层安全筛选器中完成的。 根据配置,每一层都可以帮助创建基本身份验证,摘要身份验证,表单身份验证或我们选择自行实现的任何自定义身份验证的身份验证。 我们在上一篇文章中构建的客户端会话是一种自定义身份验证,而Spring Security Oauth 2是另一种自定义身份验证。
因为在此示例中,我们的应用程序既提供令牌又使用令牌,因此Spring Security Oauth 2不应是应用程序的唯一身份验证层。 我们需要另一种身份验证机制来保护令牌提供者端点。
对于集群环境,令牌或签名令牌的秘密(对于JWT)假定是持久的,但是我们跳过此步骤以简化示例。 同样,用户身份验证和客户端身份都是硬编码的。
系统设计
总览
在我们的应用程序中,我们需要设置3个组件
- 授权端点和令牌端点,以帮助提供Oauth 2令牌。
- WebSecurityConfigurerAdapter,这是一个身份验证层,其硬编码顺序为3(根据Dave Syer )。 该身份验证层将为包含Oauth 2令牌的任何请求设置身份验证和委托人。
- 如果令牌丢失,则另一种身份验证机制可以保护令牌端点和其他资源。 在此示例中,我们选择基本身份验证是为了简化编写测试时的操作。 由于我们未指定顺序,因此它将采用默认值100。对于Spring安全性,顺序越低,优先级越高; 因此,我们应该期望Oauth 2在FilterChainProxy中进行基本身份验证之前。 在IDE中检查证明我们的设置正确。
在上图中,Oauth2AuthenticationProcessingFilter出现在BasicAuthenticationFilter的前面。
授权服务器配置
这是我们的授权和令牌端点配置
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {@Value("${resource.id:spring-boot-application}")private String resourceId;@Value("${access_token.validity_period:3600}")int accessTokenValiditySeconds = 3600;@Autowiredprivate AuthenticationManager authenticationManager;@Beanpublic JwtAccessTokenConverter accessTokenConverter() {return new JwtAccessTokenConverter();}@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.authenticationManager(this.authenticationManager).accessTokenConverter(accessTokenConverter());}@Overridepublic void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')").checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");}@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory().withClient("normal-app").authorizedGrantTypes("authorization_code", "implicit").authorities("ROLE_CLIENT").scopes("read", "write").resourceIds(resourceId).accessTokenValiditySeconds(accessTokenValiditySeconds).and().withClient("trusted-app").authorizedGrantTypes("client_credentials", "password").authorities("ROLE_TRUSTED_CLIENT").scopes("read", "write").resourceIds(resourceId).accessTokenValiditySeconds(accessTokenValiditySeconds).secret("secret");}
}
关于此实现,没有什么值得注意的事情。
- 设置JWT令牌就像使用JwtAccessTokenConverter一样简单。 因为我们从未设置签名密钥,所以它是随机生成的。 如果我们打算将应用程序部署到云中,则必须在所有授权服务器之间同步签名密钥。
- 除了选择创建身份验证管理器之外,我们还选择从Spring容器中注入现有的身份验证管理器。 通过此步骤,我们可以与基本身份验证过滤器共享身份验证管理器。
- 可能有受信任的应用程序,而不是受信任的应用程序。 受信任的应用程序可以有自己的秘密。 这是客户凭证授权授予所必需的。 除客户端凭据外,所有其他三个授予都需要资源所有者的凭据。
- 我们允许匿名检查令牌端点。 使用此配置,无需基本身份验证或Oauth 2令牌即可访问检查令牌。
资源服务器配置
这是我们的资源服务器配置配置
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {@Value("${resource.id:spring-boot-application}")private String resourceId;@Overridepublic void configure(ResourceServerSecurityConfigurer resources) {resources.resourceId(resourceId);}@Overridepublic void configure(HttpSecurity http) throws Exception {http.requestMatcher(new OAuthRequestedMatcher()).authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest().authenticated();}private static class OAuthRequestedMatcher implements RequestMatcher {public boolean matches(HttpServletRequest request) {String auth = request.getHeader("Authorization");// Determine if the client request contained an OAuth Authorizationboolean haveOauth2Token = (auth != null) && auth.startsWith("Bearer");boolean haveAccessToken = request.getParameter("access_token")!=null;return haveOauth2Token || haveAccessToken;}}}
这里有几件事要注意:
- 添加了OAuthRequestedMatcher,以便Oauth过滤器仅处理Oauth2请求。 我们添加了此内容,以便在基本身份验证层而不是Oauth 2层将拒绝未授权的请求。 在功能方面,这可能没有任何区别,但出于可用性考虑,我们对其进行了添加。 对于客户端,他们将收到401 HTTP状态,其中包含此新标头和旧标头:
- WWW-Authenticate:基本领域=“领域”
- 使用新的响应标头,浏览器将自动提示用户输入用户名和密码。 如果您不希望任何其他身份验证机制访问该资源,则无需执行此步骤。
- 某些浏览器(例如Chrome)喜欢在发出AJAX调用之前发送OPTIONS请求以查找CORS。 因此,最好始终允许OPTIONS请求。
基本身份验证安全配置
如前所述,因为我们需要保护令牌提供者端点。
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {@Autowiredpublic void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("user").password("password").roles("USER").and().withUser("admin").password("password").roles("USER", "ADMIN");}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest().authenticated().and().httpBasic().and().csrf().disable();}@Override@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}
}
有几件事要注意:
- 我们公开了AuthenticationManager bean,以便我们的两个身份验证安全适配器可以共享一个身份验证管理器。
- Spring Security CSRF可与JSP无缝协作,但对RestAPI来说很麻烦。 因为我们希望将此示例应用程序用作用户开发自己的应用程序的基础,所以我们关闭了CSRF并添加了CORS过滤器,以便可以立即使用它。
测试中
我们严格按照Oauth2规范为每种授权授予类型编写了一个测试方案。 由于Spring Security Oauth 2是基于Spring Security框架的实现,因此我们的兴趣转向查看如何构造基础身份验证和主体。
在总结实验结果之前,让我们快速看一下要记录的内容。
- 对令牌提供者端点的大多数请求都是使用POST请求发送的,但它们包含用户凭据作为参数。 即使为了方便起见,我们将此凭据作为url的一部分放置,也切勿在Oauth 2客户端中执行此操作。
- 我们创建了两个端点/ resources / principal和/ resources / roles来捕获Oauth 2身份验证的主体和权限。
这是我们的设置:
用户 | 类型 | 当局 | 凭据 |
用户 | 资源所有者 | ROLE_USER | ÿ |
管理员 | 资源所有者 | ROLE_ADMIN | ÿ |
普通应用 | 客户 | ROLE_CLIENT | ñ |
受信任的应用 | 客户 | ROLE_TRUSTED_CLIENT | ÿ |
赠款类型 | 用户 | 客户 | 主要 | 当局 |
授权码 | 用户 | 普通应用 | 用户 | ROLE_USER |
客户凭证 | 不适用 | 受信任的应用 | 受信任的应用 | 没有权限 |
隐含的 | 用户 | 普通应用 | 用户 | ROLE_USER |
资源所有者密码凭证 | 用户 | 受信任的应用 | 用户 | ROLE_USER |
除客户端凭据外,此结果与预期的相当。 有趣的是,即使客户端通过客户端证书检索Oauth 2令牌,批准的请求仍然没有任何客户端权限,而仅具有客户端证书。 我认为这是有道理的,因为隐式授予类型的令牌无法重用。 这是我们发现的
翻译自: https://www.javacodegeeks.com/2016/04/spring-oauth2-jwt-sample.html