从实战出发,深入理解 RESTful API OAuth2 身份认证

介绍

现在的网络应用程序通常使用 RESTful API 来完成客户端和服务器之间的通信。OAuth2 则是一种常见的身份验证机制,用于保护用户信息和应用程序数据。

什么是 RESTful API?

REST 定义和原则

REST 是一种 Web 服务应用程序的架构风格,意为“表现层状态转化”(Representational State Transfer)。这种架构风格被描述为基于“资源”、“状态”和“操作”的方式来处理客户端-服务器之间的通信。

在 REST 中,资源表示数据、具有一些可操作的状态和响应的动作(例如读取、创建、更新或删除)。随着客户端的请求,资源的状态可能会发生变化。

RESTful API 遵循以下几个基本原则:

  • 资源的唯一标识符(URI)应该被用来标识资源。
  • 不管是什么操作,都应该是无状态以及无会话的。
  • 这些操作只针对资源进行。

RESTful API 设计与实现

RESTful API 的设计和实现包括以下几个关键方面:

  1. URI 设计:在 URI 中描述资源,通过 URL 传递资源状态和操作信息。其中,URL 含有一组特定的参数,指定要执行的操作。
  2. HTTP 方法:使用 HTTP verb(GET、POST、PUT、DELETE等)表示操作类型。例如,使用 GET 提取资源,POST 创建或修改资源,DELETE 删除资源。
  3. 消息体格式:跟据服务返回的数据类型使用对应的输出格式,可以是 JSON、XML、HTML 或其他格式。
  4. 身份验证:一种安全机制,用于检查客户端是否有资格执行特定操作。
  5. 过滤:用于从结果集中选择基于某些条件的子集。
  6. 分页:在获取大量数据时,可以使用分页控制数据返回数量。

什么是 OAuth2?

OAuth2 是一种开放标准的身份验证和授权协议,允许客户端应用程序以可限制的方式访问受保护的资源。在 OAuth2 中,资源所有者授权客户端访问他们的私人资源(例如个人信息)而不需要共享他们的用户名和密码。

OAuth2 通过下面的三种主要角色来工作:

  1. 资源所有者:拥有所有访问资源的用户。
  2. 客户端:代表资源所有者进行请求的应用程序。
  3. 授权服务器:管理客户端对受保护资源的访问。

授权服务器会向客户端颁发访问令牌,该令牌包括受保护资源的访问权限和其他方面的信息。这个令牌被客户端用来访问受保护的资源,而无需直接暴露用户的身份认证信息。

OAuth2 身份认证的实现

以下是如何使用 OAuth2 为 RESTful API 提供身份认证的实现过程:

步骤一:请求授权

客户端向关键服务器请求授权操作。这可以通过以下三种授权方式来完成。

  1. 授权码许可:该授权方式的步骤如下:
  • 客户端把用户重定向到授权服务器。
  • 授权服务器要求用户进行身份验证,如果认证成功,会向客户端发回授权码。
  • 客户端向授权服务器发出另一个请求,携带该授权码以及另一个机密代码,以获取访问令牌。
  • 授权服务器对客户端发出的请求进行验证,通过后发回访问令牌。
  1. 隐藏式许可:在这个操作模式中,没有请求任何授权码,客户端直接从授权服务器中获取访问令牌。

  2. 用户名和密码:在这个操作模式中,客户端将请求一些关键服务器,用户必须输入他们的凭据才能得到访问令牌。

步骤二:获取访问令牌

客户端获得授权后,可以向授权服务器请求访问令牌。下面是获取访问令牌的流程:

  1. 客户端将授权码发送到授权服务器。
  2. 为避免令牌劫持,授权服务器会验证授权码是否有效,然后为客户端创建一个访问令牌。
  3. 如果使用的是 JSON Web Token (JWT),访问令牌将被编码并返回给客户端。

步骤三:使用访问令牌

客户端使用访问令牌来访问受保护的资源。

在过期之前,访问令牌都是有效的。当访问令牌过期时,客户端需要重新获取令牌进行访问。

示例代码

下面是一个示例代码,演示如何使用 OAuth2 身份认证来保护 RESTful API。该示例使用 Spring 框架和 Spring Security 进行身份认证。

项目依赖

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <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.0.RELEASE</version>
  </dependency>
  <dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    <version>1.0.10.RELEASE</version>
  </dependency>
</dependencies>

Spring 配置

@Configuration
@EnableResourceServer
class ResourceServerConfig : ResourceServerConfigurerAdapter() {

  @Throws(Exception::class)
  override fun configure(http: HttpSecurity) {
    http.authorizeRequests()
      .antMatchers("/api/public").permitAll()
      .anyRequest().authenticated()
  }

  override fun configure(resources: ResourceServerSecurityConfigurer) {
    resources.tokenServices(tokenServices())
  }

  @Bean
  fun jwtAccessTokenConverter(): JwtAccessTokenConverter {
    val converter = JwtAccessTokenConverter()
    converter.setSigningKey("123456")
    return converter
  }

  @Bean
  fun tokenServices(): JwtTokenServices {
    val services = JwtTokenServices()
    services.setTokenEnhancer(jwtAccessTokenConverter())
    return services
  }
}

@Configuration
@EnableAuthorizationServer
class AuthorizationServerConfig : AuthorizationServerConfigurerAdapter() {

  @Autowired
  private lateinit var authenticationManager: AuthenticationManager

  override fun configure(clients: ClientDetailsServiceConfigurer) {
    clients.inMemory()
      .withClient("clientid")
      .secret("mysecret")
      .authorizedGrantTypes("authorization_code", "password", "refresh_token")
      .scopes("read", "write")
      .redirectUris("http://localhost:8080/code")
  }

  override fun configure(endpoints: AuthorizationServerEndpointsConfigurer) {
    endpoints.authenticationManager(authenticationManager)
      .accessTokenConverter(accessTokenConverter())
  }

  @Bean
  fun authenticationManager(): AuthenticationManager {
    return object : AuthenticationManager {
      override fun authenticate(authentication: Authentication?): Authentication {
        // Authentication process
      }
    }
  }

  @Bean
  fun accessTokenConverter(): JwtAccessTokenConverter {
    val converter = JwtAccessTokenConverter()
    converter.setSigningKey("123456")
    return converter
  }
}

@Configuration
class WebSecurityConfig : WebSecurityConfigurerAdapter() {

  override fun configure(http: HttpSecurity) {
    http.csrf().disable().authorizeRequests()
      .antMatchers("/web/**").permitAll()
      .anyRequest().authenticated()
  }

  override fun configure(auth: AuthenticationManagerBuilder) {
    auth.inMemoryAuthentication()
      .withUser("admin").password("{noop}password").roles("ADMIN")
  }

  @Bean
  override fun authenticationManagerBean(): AuthenticationManager {
    return super.authenticationManagerBean()
  }
}

Web 控制器

@RestController
@RequestMapping(path = ["/api"])
@CrossOrigin(origins = ["http://localhost:4200", "http://localhost:8080"])
class ApiController(private val userRepository: UserRepository) {

  @GetMapping("/public")
  fun getPublic(): String {
    return "Public"
  }

  @GetMapping("/private")
  fun getPrivate(): String {
    return "Private"
  }
}

@RepositoryRestResource
interface UserRepository : CrudRepository<User, Long>

@Entity
data class User(
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  var id: Long? = null,
  var name: String? = null,
  var email: String? = null,
  var password: String? = null)

在上面的代码中,使用 Spring Security 和 Spring Boot 中的 @EnableResourceServer@EnableAuthorizationServer 注解来配置资源服务器和授权服务器。

使用 @EnableResourceServer 指定该服务器是资源服务器,使用 @EnableAuthorizationServer 指定该服务器是授权服务器。

资源服务器和授权服务器的配置信息在 application.yml 文件中定义。

spring:
  security:
    oauth2:
      resource:
        jwt:
          key-value: 123456

security:
  user:
    name: user
    password: password
    role: USER
  oauth2:
    client:
      clientId: clientid
      clientSecret: mysecret
      accessTokenUri: http://localhost:8080/oauth/token
      userAuthorizationUri: http://localhost:8080/oauth/authorize
      clientAuthenticationScheme: form
    resource:
      userInfoUri: http://localhost:8080/api/user

总结

本文深入介绍了 RESTful API 和 OAuth2 身份认证的基本概念和实现方法。通过示例代码,我们可以看到如何使用 Spring Security 和 Spring Boot 来构建 RESTful API 和 OAuth2 身份认证。这些技术可以用来构建安全、可扩展的 Web 应用程序,同时保护用户信息和应用程序数据。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65b5c37dadd4f0e0ffe82069