微服务之-Security-与-OAuth2

162 阅读7分钟

当你使用浏览器访问网站, 或是使用手机 App , 一般都要求你注册一个帐号, 然后使用这个帐号登录先. 最传统的方式让用户注册自己的帐号, 很多时间比较懒的用户对于一个新 app 的试用就会在这里截止了. 现在越来越多的应用并不要求用户注册一个帐号, 而是允许用户使用微博或微信帐号来登录.

1. oAuth2 协议简介

1. OAuth2 的核心概念

资源所有者(Resource Owner) :通常是最终用户,拥有并可以授予对其资源的访问权限。

客户端(Client) :代表资源所有者的应用程序,请求访问资源。

资源服务器(Resource Server) :存储资源并负责响应请求。一般使用 Access Token 验证请求的合法性。

授权服务器(Authorization Server) :负责验证资源所有者的身份,并颁发 Token(授权码或 Access Token)。

2. 常用的 OAuth2 授权模式

授权码模式(Authorization Code) :安全性高,推荐在 Web 应用中使用。客户端通过用户授权得到授权码,再用授权码获取 Access Token。

简化模式(Implicit) :通常用于单页应用(SPA),直接向客户端颁发 Access Token。

密码模式(Resource Owner Password Credentials) :用户直接提供用户名和密码给客户端,适用于高度信任的客户端。

客户端模式(Client Credentials) :客户端以自身身份请求授权,通常用于服务间通信。

2. OAuth2 协议示例

举个例子, 我曾经当过秘书, 秘书如果需要调阅机密档案, 一般要经过如下步骤

  1. 秘书找保密室领导申请调阅某机密文档
  2. 领导签字批准
  3. 秘书拿着领导签字去找档案员拿钥匙
  4. 档案员给了一把档案柜密码锁的临时密码 (临时密码是有时效性的, 过期无效)
  5. 秘书拿着临时密码去开文件柜拿文档
  6. 秘书拿到文档查阅所需内容

这个过程就是一个典型的 OAuth2 过程, OAuth 协议定义的四种角色如下

  • Client 是秘书
  • Resource Owner 是领导
  • Authorization Server 是 档案员
  • Resource Server 是档案柜, resource 就是档案

其中

  • Authorization Grant 是资源所有者(领导)给予的授权, 也就是 Resource Owner 所给予的凭据, OAuth协议中定义了四种方式
  • authorization code 授权码, 就好比领导给的签字
  • implicit 隐含方式
  • resource owner password 领导给你的一个临时密码
  • credentials, and client credentials
  • Access Token 是档案柜密码锁的临时密码
  • Protected Resource 是机密档案

标准流程如下:

  1. Client 从 resource owner 要求授权许可, 这个请求可以直接找 resource owner, 或者通过 authorization server 间接找 resource owner

  2. Client 收到一个授权许可, 也就是一个 credential,它代表 resource owner 的授权

  3. Client 向 authorization server 认证并出示这个授权许可,请求一个 Access token

  4. authorization server 认证 client 并校验这个授权许可,如果有效,则发布一个 access token

  5. Client 通过出示 access token 来向 resource server 请求一个受保护的资源

  6. Resource server 校验这个 Access token, 如果有效,则处理这个请求

API 示例

获取访问令牌 access_token

  • 请求
POST /v1/oauth2/token HTTP/1.1

Authorization: Basic xxx
Content-Type: application/x-www-form-urlencoded

grant_type=password
&username=walter
&password=pass
&scope=api
  • 响应
HTTP1.1 200 OK Content-Type: application/json
{ 
"token_type": "bearer""expires_in": "3000000""access_token": "xxxyyy""refresh_token": "xxxzzz"
}

通过访问令牌来调用 API

  • 请求
GET /api/v1/users/4c65201a-2052-4cab-8b22-4c60148a1178 HTTP/1.1 
Authorization: Bearer xxxyyy
  • 响应
HTTP1.1 200 OK Content-Type: application/json
{ 
"id": "4c65201a-2052-4cab-8b22-4c60148a1178""name": "walter"
}

如果 Access_token 过期了则返回

HTTP1.1 401 Unauthorized Content-Type: application/json
{ 
"error": "invalid_token" 
}

@Configuration
@EnableWebSecurity
public class BasicConfiguration extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(AuthenticationManagerBuilder auth)
      throws Exception {
        auth
          .inMemoryAuthentication()
          .withUser("user")
            .password("password")
            .roles("USER")
            .and()
          .withUser("admin")
            .password("admin")
            .roles("USER", "ADMIN");
    }
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
          .authorizeRequests()
          .anyRequest()
          .authenticated()
          .and()
          .httpBasic();
    }
}

3. Spring 中 oAuth2 的实现

在 Spring 中实现 OAuth2 可以使用 Spring Security 提供的 OAuth2 支持,用于安全地限制应用访问用户资源,而不会将用户的凭证暴露给第三方应用。

Spring Security 通过支持各种 OAuth2 授权流程(例如 Authorization Code、Implicit、Client Credentials 和 Resource Owner Password Credentials)来实现这一点。以下是实现 OAuth2 的主要步骤和概念:

3.1 引入依赖****

在使用 Spring Boot 和 Spring Security 时,可以通过添加依赖来实现 OAuth2 授权服务器和资源服务器功能。

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>

</dependency>

  
<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-oauth2-client</artifactId>

</dependency>

3.2 配置授权服务器****

Spring 提供了 AuthorizationServerConfigurerAdapter 类来配置授权服务器。

import org.springframework.context.annotation.Configuration;
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 AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
  
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

        clients.inMemory()
            .withClient("client_id")
            .secret("{noop}client_secret")
            .authorizedGrantTypes("authorization_code", "refresh_token", "password", "client_credentials")
            .scopes("read", "write")
            .redirectUris("http://localhost:8080/login/oauth2/code/my-client");
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {

        security.tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");

    }

  
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
            .authenticationManager(authenticationManager)
            .tokenStore(tokenStore());
    }

}

在这个配置中:

  • withClient 定义客户端 ID 和密码。
  • authorizedGrantTypes 指定客户端授权类型,如授权码、密码模式等。
  • scopes 定义客户端的权限范围。
  • redirectUris 是授权码模式下的回调地址。

3.3 配置资源服务器****

在资源服务器上,配置 ResourceServerConfigurerAdapter 来保护 API 资源,并指定哪些 URL 需要访问令牌才能访问。

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {

        http
            .authorizeRequests()
            .antMatchers("/public/**").permitAll()
            .antMatchers("/api/**").authenticated();
    }

 }

在这里:

  • /public/**:开放访问的 URL,不需要认证。
  • /api/**:受保护的 API,需要 Access Token 认证。

4. 配置客户端****

客户端应用需要请求授权码,然后用授权码换取 Access Token。在 Spring Security 中,可以使用 OAuth2AuthorizedClientService 来实现。

4.1 配置客户端注册****

在 application.yml 中配置客户端:


spring:
  security:
    oauth2:
      client:
        registration:
          my-client:
            client-id: client_id
            client-secret: client_secret
            scope: read,write
            authorization-grant-type: authorization_code
            redirect-uri: "{baseUrl}/login/oauth2/code/my-client"
            client-name: My Client
        provider:
          my-provider:
            authorization-uri: http://localhost:8080/oauth/authorize
            token-uri: http://localhost:8080/oauth/token

4.2 使用 OAuth2RestTemplate 请求资源****

配置完毕后,客户端可以使用 OAuth2RestTemplate 请求资源。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ApiController {
    @Autowired
    private OAuth2RestTemplate oAuth2RestTemplate;

    @GetMapping("/fetchData")
    public String fetchData() {
        String resourceUrl = "http://localhost:8081/api/resource";
        return oAuth2RestTemplate.getForObject(resourceUrl, String.class);
    }
}

5. 使用 Access Token 访问受保护资源****

当客户端获得 Access Token 后,它可以在 HTTP 请求的 Authorization 头部携带该 Token 访问受保护资源。典型的请求头如下:

GET /api/resource HTTP/1.1
Host: localhost:8081
Authorization: Bearer ACCESS_TOKEN

6. OAuth2 流程的整体总结****

  1. 授权请求:客户端请求授权服务器获取授权码或 Access Token。

  2. Access Token 生成:授权服务器验证请求并生成 Access Token。

  3. 资源访问:客户端使用 Access Token 请求资源服务器的数据。

  4. Token 验证:资源服务器验证 Token 的合法性,允许合法的请求访问资源。

7. 示例工作流程****

假设使用授权码模式进行 OAuth2 授权:

  1. 客户端请求授权:用户在客户端登录,客户端重定向到授权服务器的授权 URL。

  2. 用户授权并返回授权码:用户在授权服务器上同意授权,授权服务器重定向回客户端并附带授权码。

  3. 客户端用授权码获取 Access Token:客户端用授权码向授权服务器请求 Access Token。

  4. 客户端用 Access Token 请求资源:客户端在请求中附带 Access Token 访问资源服务器的受保护资源。

参考资料