如何在Spring Boot中使用OAuth2

x33g5p2x  于2022-09-16 转载在 Spring  
字(11.5k)|赞(0)|评价(0)|浏览(498)

在这篇文章中,我们将通过Spring Boot配置和启用Oauth2。我们将用Oauth2来保护我们的REST API,建立一个授权服务器来验证我们的客户端,并为未来的通信提供一个access_token。

1.  简介

在我们深入了解细节之前,让我们快速回顾一下Oauth2。Oauth2是一个授权框架,使应用程序能够在HTTP服务上获得对用户账户的有限访问。它的工作方式是将用户认证委托给托管用户账户的服务,并授权第三方应用程序访问用户账户。Oauth2为网络和桌面应用以及移动设备提供授权流程。

Oauth定义了四个主要角色。

  1. 资源所有者
  2. 客户
  3. 资源服务器
  4. 授权服务器

资源所有者:用户 - 资源所有者是授权应用访问其账户的用户

资源/授权服务器 - 资源服务器托管受保护的用户账户,授权服务器验证用户的身份,然后向应用发放访问令牌。

客户端。应用程序 - 客户端是想要访问用户账户的*应用程序。在它这样做之前,用户必须允许它,而且API必须验证授权。

让我们看看这个Oauth2工作流程是怎样的。

在进行OAuth2工作时,请确保你对访问令牌和刷新令牌有一个清晰的认识

2. Spring Boot中使用Oauth2授权服务器

让我们来设置一个授权服务器,以启用Spring Boot的Oauth2。我们可以选择使用IDE(如IntelliJ IDEA)创建应用程序,也可以使用Spring Boot CLI创建一个应用程序。

$ spring init --dependencies=web,actuator my-project

如果你喜欢一个更直观的界面来生成初始结构,我们可以使用Spring Initializer。

点击 "生成 "按钮,在你的本地机器上下载项目。我们为我们的应用程序选择了以下依赖项。

  1. 网络启动器 

为了启用Oauth支持,在pom.xml文件中添加以下依赖。

<dependency>
	<groupId>org.springframework.security.oauth</groupId>
	<artifactId>spring-security-oauth2</artifactId>
	<version>2.4.0.RELEASE</version>
</dependency>

这个依赖将为我们的应用程序添加使用Oauth2功能的所有先决条件。下一步是为我们的应用程序添加一些配置。在src/main/resources/application.properties文件中添加以下条目。

user.oauth.clientId=javadevjournal
user.oauth.clientSecret=1234$#@!
user.oauth.redirectUris=http://localhost:8081/login
user.oauth.user.username=javadevjournal
user.oauth.user.password=javadevjournal
user.oauth.accessTokenValidity=300
user.oauth.refreshTokenValidity=240000

根据你的要求改变这些值。

上述配置设置了授权服务器在设置过程中使用的值(你可以随时使用数据库来存储这些值)。为了激活授权服务器,添加add @EnableResourceServer注释。

@SpringBootApplication
@EnableResourceServer
public class SpringBootAuthServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootAuthServerApplication.class, args);
    }
}

2.1 安全配置

我们的下一步是配置我们的资源服务器。让我们创建OAuth2AuthServerConfiguration并扩展AuthorizationServerConfigurerAdapter.这个Spring配置类启用并配置了一个OAuth授权服务器。

package com.javadevjournal.oauth2;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.AuthorizationServerSecurityConfigurer;

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Value("${user.oauth.clientId}")
    private String clientID;

    @Value("${user.oauth.clientSecret}")
    private String clientSecret;

    @Value("${user.oauth.redirectUris}")
    private String redirectURLs;

    @Value("${user.oauth.accessTokenValidity}")
    private int accessTokenValidity;

    @Value("${user.oauth.refreshTokenValidity}")
    private int refreshTokenValidity;

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.tokenKeyAccess("permitAll()")
            .checkTokenAccess("isAuthenticated()");
    }

    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
            .inMemory()
            .withClient(clientID)
            .secret(passwordEncoder.encode(clientSecret))
            .authorizedGrantTypes("password", "authorization_code", "refresh_token")
            .scopes("user_info")
            .authorities("READ_ONLY_CLIENT")
            .redirectUris(redirectURLs)
            .accessTokenValiditySeconds(accessTokenValidity)
            .refreshTokenValiditySeconds(refreshTokenValidity);
    }
}

这个类将在客户端应用程序得到认证后返回令牌。让我们检查一下一些重要的内容。

  • Spring security Oauth暴露了两个端点。它在denyAll()方法后面保护这些端点。tokenKeyAccesstokenKeyAccess启用这些端点。
  • ClientDetailsServiceConfigurer用于定义*客户端细节服务的内存或JDBC实现。*我们在这篇文章中使用内存方法,但我建议为你的生产环境使用JDBC支持的方法。配置是使用以下属性。

客户端 - 在认证服务器上注册的客户端ID。我们使用application.properties文件来定义它。

Secret - 客户端秘密(查看application.properties文件)。

Scope - 客户端应用程序的范围。这显示了我们给客户端应用程序的访问权限。如果范围未定义或为空(默认),则客户端不受范围限制。

authorizedGrantTypes - 授予客户端使用的类型。默认值为空。

authorities - 授予客户端的权限(常规的Spring Security权限)。
广告

广告

redirectUris - 将用户代理重定向到客户端的重定向端点。它必须是一个绝对的URL。

Token Validity - 最后两个配置设置访问和刷新令牌的有效性。

让我们为我们的资源服务器添加一些额外的配置。创建一个类OAuth2ResourceServerConfig并扩展ResourceServerConfigurerAdapter类。

@Configuration
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/api/**").authenticated()
            .antMatchers("/").permitAll();
    }
}

以上配置使所有端点的保护开始于/api。所有其他端点都不安全,没有OAuth安全就可以访问。

2.2. 用户授权

Spring security Oauth2还提供了一种机制来认证用户本身。这是一个基于表单的安全功能。创建一个SecurityConfiguration类,它扩展了SecurityConfiguration类。

package com.javadevjournal.oauth2;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@Order(1)
public class OauthSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Value("${user.oauth.user.username}")
    private String username;

    @Value("${user.oauth.user.password}")
    private String password;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .antMatcher("/**")
            .authorizeRequests()
            .antMatchers("/oauth/authorize**", "/login**", "/error**")
            .permitAll()
            .and()
            .authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin().permitAll();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
            .withUser(username).password(passwordEncoder().encode(password)).roles("USER");
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

上述类对我们的授权服务器进行认证请求。这就完成了我们授权服务器的核心设置。让我们创建一个受保护的资源来测试整个工作流程。

3. Oauth2保护的REST资源

为了测试我们的应用程序,让我们创建一个REST控制器。我们的控制器将根据用户ID来返回用户的详细信息。这个资源是安全的,没有有效的Oauth令牌将无法访问。如果我们在请求中没有传递有效的令牌,系统将不允许访问并向客户端抛出未经授权的异常。

package com.javadevjournal.controller;

import com.javadevjournal.data.CustomerData;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/customers")
public class CustomerController {

 @GetMapping("/customer/{userId}")
 public CustomerData getCustomerProfile(@PathVariable("userId") String userId) {
  return getCustomer(userId);
 }

 private CustomerData getCustomer(final String userId) {
  CustomerData customer = new CustomerData();
  customer.setEmail("[email protected]");
  customer.setFirstName("Demo");
  customer.setLastName("User");
  customer.setAge(21);
  customer.setId(userId);
  return customer;
 }
}

客户数据

package com.javadevjournal.data;

public class CustomerData {

 private String firstName;
 private String lastName;
 private int age;
 private String email;
 private String id;

 public CustomerData() {}

 public CustomerData(String firstName, String lastName, int age, String email, String id) {
  this.firstName = firstName;
  this.lastName = lastName;
  this.age = age;
  this.email = email;
  this.id = id;
 }

 public String getFirstName() {
  return firstName;
 }

 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }

 public String getLastName() {
  return lastName;
 }

 public void setLastName(String lastName) {
  this.lastName = lastName;
 }

 public int getAge() {
  return age;
 }

 public void setAge(int age) {
  this.age = age;
 }

 public String getEmail() {
  return email;
 }

 public void setEmail(String email) {
  this.email = email;
 }

 public String getId() {
  return id;
 }

 public void setId(String id) {
  this.id = id;
 }
}

4. 测试应用程序

在测试的第一部分,我将使用Postman。当任何第三方试图访问客户资料数据时,该服务需要oauth2令牌。

4.1 获取授权授予代码

这个过程的第一步是从资源所有者那里获得授权许可。要获得授权许可,请使用下面的URL(在现实世界的应用中,客户将被重定向到这个网站,并将被提示给予API访问某种资源的许可)。

http://localhost:8080/oauth/authorize?client_id=javadevjournal&response_type=code&scope=user_info<code>

这个URL带来一个登录页面。一旦客户提供了登录信息,系统就会重定向到授权访问页面。该页面为客户提供了一个选项,以批准/拒绝该请求或为第三方应用程序提供某些访问权限。(你还记得当你在你的Facebook上添加任何应用程序时,它将你重定向到一个页面来提供你的权限吗?)

提供用户名和密码(关于这些配置,请参考第2节)。

一旦我们批准请求,它将重定向到一个URL(检查user.oauth.redirectUris属性)。这个重定向的URL也将包含一个代码作为查询字符串的一部分(http://localhost:8081/login?code=13428u)。这个代码是第三方应用程序的授权代码。

4.2 获取访问令牌

一旦我们有了授权,下一步就是要获得访问令牌。为了得到访问令牌,我们需要传递在上一步中收到的代码。对于这个演示,发送一个简单的cURL请求。

curl -X POST \
  http://localhost:8080/oauth/token \
  -H 'authorization: Basic amF2YWRldmpvdXJuYWw6MTIzNCQjQCE=' \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/x-www-form-urlencoded' \
  -H 'postman-token: f24e14c3-a90a-4866-59ae-3691dfb3ea0a' \
  -d 'code=ntCgjD&grant_type=authorization_code&redirect_uri=http%3A%2F%2Flocalhost%3A8081%2Flogin&scope=user_info'

在一个成功的请求中,认证服务器将在响应中返回访问令牌

{
    "access_token": "791dccdf-c41f-42c1-9b88-93853ed5c87b",
    "token_type": "bearer",
    "refresh_token": "ec2ef96e-1792-4188-b397-87b1a2afdeb4",
    "expires_in": 122,
    "scope": "user_info"
}

一旦我们得到访问令牌,让我们通过在请求中传递访问令牌来获得用户信息。

下面是cURL请求

curl -X GET \
  http://localhost:8080/api/customers/customer/1 \
  -H 'Authorization: Bearer f4e93f7c-59c3-4ca3-a5c3-0e74582b1b18' \
  -H 'cache-control: no-cache'

[pullquote align="normal"] 在这篇文章中,为了保持简单,我省略了一些信息,比如将令牌存储在数据库中,或者注入一个用户服务,不使用内存中的用户。这些都可以使用Spring安全系统轻松配置 [/pullquote] 。

5. Maven的依赖性

以下是完整的pom.xml文件供你参考。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/>
        <!-- lookup parent from repository -->
    </parent>
    <groupId>com.javadevjournal</groupId>
    <artifactId>spring-boot-auth-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-auth-server</name>
    <description>Spring Boot Authorization Server</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.4.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

总结

在这篇文章中,我们讨论了如何用Spring Boot配置和启用Oauth2。我们研究了使用Spring Boot和Spring Security的步骤,以便为我们基于REST的应用程序启用Oauth2支持。这篇文章的源代码可以在GitHub上找到。

相关文章