spring OAuth快速入门

SymonsJune 发布于1年前

如何阅读

前几节简单介绍了OAuth,以及spring secuirty OAuth的一些基本构成。文中有一些关联知识点的引用,如果要深入研究,可以点进去详细了解一下,这样可以加深立即。如果你只想快速浏览OAuth server的搭建可以跳过前面几节,直接看下面的代码实例。

什么是OAuth2

OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容。

Oauth2 授权方式

  • authorization_code(授权码类型,使用三方qq登录使用该方式)
  • implicit(隐式授权类型)
  • password(资源所有者即用户密码类型,典型应用是微服务里使用uaa服务器进行登录认证)
  • client_credentials(客户端凭据【客户端ID以及Key】类型,典型应用是微服务内部系统与系统之间调用客服端授权)
  • refresh_token(通过以上授权获得的刷新令牌来获取新的令牌)。

关于OAuth不同授权的具体流程可以参考[理解OAuth 2.0
-阮一峰](http://www.ruanyifeng.com/blo...

spring Security OAuth2

spring security OAuth 是对OAuth2协议的一个实现。是在spring security的基础上发展而来,之前是spring security的一个子项目,现在已经独立出来。详细见官网.
spring OAuth使用类似spring secuirty的机制来实现OAuth2 .根据OAuth协议规定,一个完整的授权服务器应当包含:授权服务器和资源服务器。授权服务器负责认证和授权,资源服务器负责根据用户提供的授权凭证(比如给以token)。这里有一篇官方文档介绍了spring OAuth的使用.[OAuth 2 Developers Guide
](http://projects.spring.io/spr...

授权服务

一个授权服务大致几个模块:client管理、授权接口、用户认证、令牌管理。
  1. client管理主要用来管理和区分不同的client,我们可以通过配置认证client链接是否合法,能为该client提供哪些授权服务,个性化定制client允许的行为。在spring secruity OAuth2中,可以对client进行如下属性配置:
  • clientId:(必须的)用来标识客户的Id。
  • secret:(需要值得信任的客户端)客户端安全码,如果有的话。
  • scope:用来限制客户端的访问范围,如果为空(默认)的话,那么客户端拥有全部的访问范围。
  • authorizedGrantTypes:此客户端可以使用的授权类型,默认为空。
  • authorities:此客户端可以使用的权限(基于Spring Security authorities)。
  1. 授权接口是授权服务对外提供的http入口。在spring中授权端口如下:

spring security OAuth授端口

  • /oauth/authorize:授权端点。对应AuthorizationEndpoint类
  • /oauth/token:令牌端点。对应TokenEndpoint类
  • /oauth/confirm_access:用户确认授权提交端点。对应WhitelabelApprovalEndpoint类
  • /oauth/error:授权服务错误信息端点。对应WhitelabelApprovalEndpoint类
  • /oauth/check_token:用于资源服务访问的令牌解析端点。对应CheckTokenEndpoint类
  • /oauth/token_key:提供公有密匙的端点,如果你使用JWT令牌的话。对应TokenKeyEndpoint类

授权是使用 AuthorizationEndpoint 这个端点来进行控制的,你能够使用 AuthorizationServerEndpointsConfigurer 这个对象的实例来进行配置 ,如果你不进行设置的话,默认是除了资源所有者密码(password)授权类型以外,支持其余所有标准授权类型的(RFC6749),我们来看一下这个配置对象有哪些属性可以设置吧,如下列表:

  • authenticationManager:认证管理器,当你选择了资源所有者密码(password)授权类型的时候,请设置这个属性注入一个 AuthenticationManager 对象。
  • userDetailsService:如果啊,你设置了这个属性的话,那说明你有一个自己的 UserDetailsService 接口的实现,或者你可以把这个东西设置到全局域上面去(例如 GlobalAuthenticationManagerConfigurer 这个配置对象),当你设置了这个之后,那么 "refresh_token" 即刷新令牌授权类型模式的流程中就会包含一个检查,用来确保这个账号是否仍然有效,假如说你禁用了这个账户的话。
  • authorizationCodeServices:这个属性是用来设置授权码服务的(即 AuthorizationCodeServices 的实例对象),主要用于 "authorization_code" 授权码类型模式。
  • implicitGrantService:这个属性用于设置隐式授权模式,用来管理隐式授权模式的状态。
  • tokenGranter:这个属性就很牛B了,当你设置了这个东西(即 TokenGranter 接口实现),那么授权将会交由你来完全掌控,并且会忽略掉上面的这几个属性,这个属性一般是用作拓展用途的,即标准的四种授权模式已经满足不了你的需求的时候,才会考虑使用这个
  1. 用户认证,例如在使用password授权模式时,需要在获取令牌之前先校验用户提供的凭证是否合法,合法的凭证是用户获取授权令牌的前提,spring secuiurty OAuth使用了spring security的认证服务,在令牌获取端口AuthenticationManager进行授权,这个会在后面的授权端口中提到。
  2. 令牌管理服务负责令牌是生成、校验等操作。

令牌主要有两种解决方案:使用随机算法生成唯一标示与用户授权关联,然后保存起来供校验时查询,为了方便资源服务方便验证令牌,这种方案常常是授权服务和资源服务共存的,如果不共存,那么资源服务的tokenStore服务于授权服务tokenStore要做到数据互通,spring 的解决方案是提供/oauth/check_token接口来完成。第二种是授权服务器使用某种算法生成字符串,资源服务器使用约定好的算法对令牌进行解析校验,以验证 他的合法性。在spring security OAuth2中提供了InMemoryTokenStore、JdbcTokenStore、JwtTokenStore。前面两者需要将令牌存在起来,最后一个JwtTokenStore是jwt令牌TokenStore的实现,他不存储令牌,只根据一定的规则和秘钥验证令牌的合法性。jwt令牌分为三段:头部信息、playload、签名。每段使用base64编码,每段之间使用"."连接。

资源服务

一个资源服务(可以和授权服务在同一个应用中,当然也可以分离开成为两个不同的应用程序)提供一些受token令牌保护的资源,Spring OAuth提供者是通过Spring Security authentication filter来验证过滤器来实现的保护(OAuth2AuthenticationProcessingFilter),你可以通过 @EnableResourceServer 注解到一个 @Configuration 配置类上来标记应用是一个资源服务器,你可以通过配置 ResourceServerConfigurer 配置对象来进行资源服务器的一些自定义配置(可以选择继承自 ResourceServerConfigurerAdapter 然后覆写其中的方法,参数就是这个对象的实例),下面是一些可以配置的属性:

  • tokenServices:ResourceServerTokenServices 类的实例,用来实现令牌服务。
  • resourceId:这个资源服务的ID,这个属性是可选的,但是推荐设置并在授权服务中进行验证。
  • 其他的拓展属性例如 tokenExtractor 令牌提取器用来提取请求中的令牌,也就说,你可以自定义提如何在请求中提取令牌。
  • 请求匹配器,用来设置需要进行保护的资源路径,默认的情况下是受保护资源服务的全部路径。
  • 受保护资源的访问规则,默认的规则是简单的身份验证(plain authenticated)。
  • 其他的自定义权限保护规则通过 HttpSecurity 来进行配置。

@EnableResourceServer 注解自动增加了一个类型为 OAuth2AuthenticationProcessingFilter 的过滤器链。
ResourceServerTokenServices 是组成授权服务的另一半,如果你的授权服务和资源服务在同一个应用程序上的话,你可以使用 DefaultTokenServices ,这样的话,你就不用考虑关于实现所有必要的接口的一致性问题,这通常是很困难的。如果你的资源服务器是分离开的,那么你就必须要确保能够有匹配授权服务提供的 ResourceServerTokenServices,它知道如何对令牌进行解码。
在授权服务器上,你通常可以使用 DefaultTokenServices 并且选择一些主要的表达式通过 TokenStore(后端存储或者本地编码)。
RemoteTokenServices 可以作为一个替代,它将允许资源服务器通过HTTP请求来解码令牌(也就是授权服务的 /oauth/check_token 端点)。如果你的资源服务没有太大的访问量的话,那么使用RemoteTokenServices 将会很方便(所有受保护的资源请求都将请求一次授权服务用以检验token值),或者你可以通过缓存来保存每一个token验证的结果。

spring Security OAuth 配置示例

关于spring OAuth的更多配置细节可以参考官方文档: OAuth2 Autoconfig.spring Secuirty OAuth2配置比较简单,关键还是要理解OAuth2协议,以及他的使用场景。

添加依赖

<dependencies>
  <!-- ... other dependency elements ... -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.1.1.RELEASE</version>
  </dependency>
</dependencies>

授权服务配置

授权服务配置主要需要完成以下几个配置:

  • ClientDetailsServiceConfigurer:用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
  • AuthorizationServerSecurityConfigurer:用来配置令牌端点(Token Endpoint)的安全约束.
  • AuthorizationServerEndpointsConfigurer:用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)

我们可以继承AuthorizationServerConfigurerAdapter并且覆写其中的三个configure方法来进行配置。下面是简单的
配置demo,配置来源于官方文档。

@Component
public class CustomAuthorizationServerConfigurer extends
    AuthorizationServerConfigurerAdapter {

    AuthenticationManager authenticationManager;

    public CustomAuthorizationServerConfigurer(
        AuthenticationConfiguration authenticationConfiguration
    ) throws Exception {
        this.authenticationManager =
            authenticationConfiguration.getAuthenticationManager();
    }

    @Override
    public void configure(
        ClientDetailsServiceConfigurer clients
    ) throws Exception {
        //下面配置了一个简单的客服端
        clients.inMemory()// 使用内存保存客服端信息,建议编写自己的clientService来配置客服端
            .withClient("client")//客服端的clientid
                .authorizedGrantTypes("password","client_credentials","refresh_token")//支持的授权方式
                .secret("secret")//该客服端访问时的密码
                .scopes("all");//scope范围
    }

    @Override
    public void configure(
        AuthorizationServerEndpointsConfigurer endpoints
    ) throws Exception {
        endpoints.authenticationManager(authenticationManager);
        endpoints.tokenStore(tokenStore());
    }
    
    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }
    
}

以上完成了一个简单的授权服务配置,我们的配置重点放在了client的配置上,由于我们为配置资源拥有者(用户),我们无法使用password授权,应为用户信不存在,我们无论输入用户名和密码都不正确。因此我暂且只能使用,client_credentials,这个仅仅是client授权,不牵扯到用户信息。使用下面命令

curl client:secret@localhost:8080/oauth/token -d grant_type=client_credentials

一切顺利的话我们可以得到token相关信息。如果要使用其他比如password等用户授权,那么需要在授权服务器配置用户相关信息,并把相关的userDetailService配置到AuthorizationServerEndpointsConfigurer对象上,上面有提到endponint配置相关规则。

资源服务配置

资源服务的作用前面已经介绍过了,资源服务主要关注两个事情,对token进行鉴权,根据具体权限包含资源。由于token可能是其他服务器生成,因此要做到ResourceServerTokenServices与授权服务的tokenService相匹配。下面是简单的配置.

   @EnableResourceServer
   public static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

       // 注意这个TokenStore需要和之前配置的一致
       private final TokenStore tokenStore;


       public ResourceServerConfiguration(TokenStore tokenStore) {
           this.tokenStore = tokenStore;
       }

       // 资源服务器安全规则配置
       @Override
       public void configure(HttpSecurity http) throws Exception {
           http
                   .exceptionHandling()
                   .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
                   .and()
                   .csrf()
                   .disable()
                   .headers()
                   .frameOptions()
                   .disable()
                   .and()
                   .sessionManagement()
                   .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                   .and()
                   .authorizeRequests()
                   .antMatchers("/api/register").permitAll()
                   .antMatchers("/api/activate").permitAll()
                   .antMatchers("/api/authenticate").permitAll()
                   .antMatchers("/api/account/reset-password/init").permitAll()
                   .antMatchers("/api/account/reset-password/finish").permitAll()
                   .antMatchers("/api/**").authenticated()
                   // 配置scope权限访问
//                    .antMatchers("/api/**").access("#oauth2.hasScope('read') or (!#oauth2.isOAuth() and hasRole('ROLE_USER'))");
       }

       @Override
       public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
           resources.resourceId("jhipster-uaa")
                   .tokenStore(tokenStore);
       }
   }

上面是资源服务和授权服务在一台机器上时的配置。由于共享tokenStore,因此资源服务在验证token的时候不会出任何问题。


@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends ResourceServerConfigurerAdapter {
   private final OAuth2Properties oAuth2Properties;

   public SecurityConfiguration(OAuth2Properties oAuth2Properties) {
       this.oAuth2Properties = oAuth2Properties;
   }

   // 主要配置资源的保护规则
   @Override
   public void configure(HttpSecurity http) throws Exception {
       http
           .csrf()
           .disable()
           .headers()
           .frameOptions()
           .disable()
       .and()
           .sessionManagement()
           .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
       .and()
           .authorizeRequests()
           .antMatchers("/api/**").authenticated() 
           .antMatchers("/management/health").permitAll()
           .antMatchers("/management/info").permitAll()
           .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN);
   }

   // 配置tokenstore,资源服务器在进行token验证的时候需要,
   @Bean
   public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) {
       return new JwtTokenStore(jwtAccessTokenConverter);
   }

   @Bean
   public JwtAccessTokenConverter jwtAccessTokenConverter(OAuth2SignatureVerifierClient signatureVerifierClient) {
       return new OAuth2JwtAccessTokenConverter(oAuth2Properties, signatureVerifierClient);
   }

}

上面是采用jwt,资源/授权服务分离的配置,以上配置无法和上面的授权服务配置配合使用。若要配置使用,需要修改授权服务的TokenStore为JwtTokenStore,并且在OAuth2JwtAccessTokenConverter中需访问授权服务器/token/access_key获取RSA公钥,该公钥会在token验证时用到。

查看原文: spring OAuth快速入门

  • yellowcat
  • bigleopard