Spring Security in Action 第七章 配置授权:限制访问

news2024/9/21 4:40:02

本专栏将从基础开始,循序渐进,以实战为线索,逐步深入SpringSecurity相关知识相关知识,打造完整的SpringSecurity学习步骤,提升工程化编码能力和思维能力,写出高质量代码。希望大家都能够从中有所收获,也请大家多多支持。
专栏地址:SpringSecurity专栏
本文涉及的代码都已放在gitee上:gitee地址
如果文章知识点有错误的地方,请指正!大家一起学习,一起进步。
专栏汇总:专栏汇总

文章目录

    • 7.1 基于权限和角色的访问限制
      • 7.1.1 根据用户权限限制api的访问
      • 7.1.2 根据用户角色限制所有api的访问权限
      • 7.1.3 限制对所有api的访问

本章包括

  • 定义权限和角色
  • 在controller上应用授权规则

几年前,我在美丽的喀尔巴阡山脉滑雪时,目睹了一个有趣的场景。有一群人在排队进入机舱,准备去滑雪场的顶端。一位知名的大网红在两名保镖的陪同下出现了。他自信满满地走上前去,期待着以他的名人身份跳过排队。走到队伍的最前面,他得到了一个惊喜。"请出示车票!"管理登机的人说:"你首先需要一张车票,其次,这次登机没有优先队,对不起,请站在后面排队.。"他指了指队伍的尽头。正如生活中的大多数情况,你是谁并不重要。对程序来说也正是如此。当试图访问一个特定的函数或数据时,你是谁并不重要!

到目前为止,我们只讨论了认证,正如你所了解的,这是应用程序识别资源调用者的过程。在前几章的例子中,我们并没有实现任何规则来决定是否批准一个请求。我们只关心系统是否认识这个用户。在大多数应用中,并不是一个用户可以访问系统中的每一个资源。在本章中,我们将讨论授权。授权是一个过程,在这个过程中,系统决定一个被识别的客户是否具有访问所请求资源的权限。

img
图7.1 授权是一个过程,在这个过程中,应用程序决定是否允许经过认证的实体访问一个资源。授权总是发生在认证之后。

在Spring Security中,一旦应用程序结束认证流程,它就会将请求委托给授权过滤器。该过滤器根据配置的授权规则允许或拒绝该请求(图7.2)。

image-20230119145419680

图7.2 当客户端发出请求时,认证过滤器对用户进行认证。认证成功后,认证过滤器将用户的详细信息存储在安全上下文中,并将请求转发给授权过滤器。授权过滤器决定该呼叫是否被允许。为了决定是否授权该请求,授权过滤器使用安全上下文中的细节。

7.1 基于权限和角色的访问限制

在这一节中,你将了解到授权和角色的概念,并用这些来保护你的应用程序的所有api。只有了解这些概念,然后才能将它们应用于现实世界的场景中,在这些场景中,不同的用户有不同的权限。根据用户拥有的权限,他们只能执行一个特定的动作。

在第三章中,我们实现了GrantedAuthority接口。当时我们没有使用GrantedAuthority,这个接口主要与授权过程有关。现在我们可以回到GrantedAuthority来研究它的目的。图7.3展示了UserDetails接口的约定和GrantedAuthority接口之间的关系。 一旦我们讨论完这个接口,我们将学会如何单独使用这些规则或为特定的请求使用。

image-20230119151303574

图7.3 一个用户有一个或多个权限(用户可以做的动作)。在认证过程中,UserDetailsService获得了关于用户的所有细节,包括权限。 应用程序在成功认证用户后,使用GrantedAuthority接口所代表的权限进行授权。

代码清单7.1显示了GrantedAuthority接口的定义。每个授权都表示用户可以对一系列程序资源操作的权限。每个权限都有对应的名字,对象的getAuthority()行为将其作为一个字符串返回。通常情况下,一个授权规则可以是这样的:"允许Jane删除产品记录,"或 “允许John读取文档记录”。在这些情况下,删除和读取是被授予的权限。应用程序允许用户Jane和John执行这些操作,这些操作的名称通常是读、写或删除。

代码清单 7.1 GrantedAuthority 接口 The GrantedAuthority contract

public interface GrantedAuthority extends Serializable {
	String getAuthority();
}

UserDetails是Spring Security中描述用户的接口,它有一个GrantedAuthority实例的集合,如图7.3所示。你可以允许一个用户拥有一个或多个权限。getAuthorities()方法返回GrantedAuthority实例的集合。在代码清单7.2中,我们可以查看UserDetails接口中的这个方法。我们可以实现这个方法,使其返回所有授予用户的权限。在认证结束后,这些授权是关于登录用户的细节的一部分,应用程序可以用它来授予权限。

代码清单7.2 来自UserDetails接口的getAuthorities(方法

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();
    // 剩余代码省略
}

7.1.1 根据用户权限限制api的访问

在本节中,我们将讨论限制特定用户对api的访问。到目前为止,在我们的例子中,任何经过认证的用户都可以调用应用程序的任何api。从现在开始,你将学习如何定制这种访问。我们将写几个例子,让你了解用Spring Security应用这些限制的各种方法。

image-20230119152522042

图7.4 授权是用户在应用程序中可以执行的动作。基于这些操作,你可以实现授权规则。只有拥有特定权限的用户才能向一个端点提出特定的请求。例如,Jane只能读取和写入端点,而John可以读取、写入、删除和更新端点。

现在我们已经了解了UserDetails和GrantedAuthority接口以及它们之间的关系,现在是时候写一个应用授权规则的小程序了。通过这个例子,我们可以学到一些替代方案,根据用户的权限来配置对终端的访问。我们开始一个新项目,我把它命名为ch07-001-authorization。这里展示三种方法,你可以使用这些方法配置所提到的api:

  • hasAuthority() 只有拥有该权限的用户才能调用该api。
  • hasAnyAuthority() 可以接收一个以上的权限。用户必须至少拥有有一个指定的权限才能访问请求。

我推荐使用这个方法或hasAuthority()方法,因为它们很简单,这取决于你分配给用户的权限数量。这些都是简单的阅读配置,使我们的代码更容易理解。

  • access() 提供了配置访问权限的无限可能,因为应用程序是基于Spring表达式语言(SpEL)来构建授权规则的。然而,它使代码更难阅读和调试。由于这个原因,我推荐它作为较小的解决方案,而且只有在你不能应用hasAnyAuthority()或hasAuthority()方法的情况下使用。

pom.xml文件中唯一需要依赖的是spring-boot-starter-web和spring-boot-starter-security。这些依赖关系足以接近之前列举的所有三种解决方案。你可以在项目ch07-001-authorization中找到这个例子。

	<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>

我们还在应用程序中添加一个api来测试我们的授权配置。

package com.hashnode.proj0001firstspringsecurity.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello(){

        return "Hello!";
    }
}

在一个配置类中,我们声明一个InMemoryUserDetailsManager作为我们的UserDetailsService,并添加两个用户,John和Jane,由这个实例来管理。每个用户都有不同的权限。你可以在下面的列表中看到如何做到这一点。

代码清单7.3 声明UserDetailsService并分配用户

package com.hashnode.proj0001firstspringsecurity.controller;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;


@Configuration
public class ProjectConfig {
    @Bean
    public UserDetailsService userDetailsService(){
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        UserDetails user1 = User.withUsername("john").password("12345").authorities("READ").build();
        UserDetails user2 = User.withUsername("jane").password("12345").authorities("WRITE").build();

        manager.createUser(user1);
        manager.createUser(user2);
        return manager;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}

image-20230119154528588

接下来要做的是添加授权配置。在第2章中,我们配置了如何使所有的api对每个人都能访问。为了做到这一点,我们扩展了WebSecurityConfigurerAdapter类,并重载了configure()方法,如代码清单7.4所示:

代码清单7.4 使每个人都能访问所有的端点,而不需要认证

image-20230119155003820

authorizeRequests()方法可以让我们在api上指定授权规则。anyRequest()方法表示该规则适用于所有的请求,无论使用的是什么URL或HTTP方法。permitAll()方法允许访问所有请求,无论是否经过验证。

比方说,我们想确保只有拥有WRITE权限的用户才能访问所有的端点。对于我们的例子,这意味着只有Jane可以访问。这次我们可以实现我们的目标,根据用户的权限来限制访问。看看下面列表中的代码吧。

清单7.5 限制只有拥有WRITE权限的用户才能访问

package com.hashnode.proj0001firstspringsecurity.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
 * @author Guowei Chi
 * @date 2023/1/19
 * @description:
 **/
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
    //省略部分代码

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic();
        http.authorizeRequests()
                .anyRequest()
                .hasAuthority("WRITE");  //限制只有 WRITE 权限的用户可以访问
    }
}

可以看到,这里使用hasAuthority()方法替换了 permitAll()方法,这里给出了允许用户使用的权限名称作为hasAuthority()方法的参数。应用程序首先需要对请求进行认证,然后根据用户的权限,决定是否允许该调用。

接下来测试应用程序,分别使用不同用户调用api。当我们用用户Jane调用api时,HTTP响应状态是200 OK,我们看到的响应体是 "Hello!"当我们用用户John调用时,HTTP响应状态是403 Forbidden,我们得到一个空的响应体。

curl -u jane:12345 http://localhost:8080/hello
Hello!
curl -u john:12345 http://localhost:8080/hello
{
"status":403,
"error":"Forbidden",
"message":"Forbidden",
"path":"/hello"
}

以类似的方式,还可以使用hasAnyAuthority()方法,这个方法可以接收多个权限名称,表示只要用户有任意权限就可以访问某些api。

可以用hasAnyAuthority(“WRITE”)替换前面的hasAuthority(),在这种情况下,程序以同样的方式工作。然而,如果你将hasAuthority()替换为hasAnyAuthority(“WRITE”, “READ”),那么来自具有两种权限的用户的请求都会被接受。 在我们的例子中,应用程序允许来自John和Jane的请求。在下面的列表中,你可以看到如何应用hasAnyAuthority()方法。

代码清单 7.6 应用 hasAnyAuthority() 方法

package com.hashnode.proj0001firstspringsecurity.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;


@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
    @Bean
    @Override
    public UserDetailsService userDetailsService(){
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();

        UserDetails user1 = User.withUsername("john")
                .password("12345").
                authorities("READ").
                build();

        UserDetails user2 = User.withUsername("jane")
                .password("12345")
                .authorities("WRITE")
                .build();

        manager.createUser(user1);
        manager.createUser(user2);
        return manager;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic();

        //允许具有WRITE或READ权限的用户访问
        http.authorizeRequests()
                .anyRequest()
                .hasAnyAuthority("WRITE","READ");
    }
}

为了指定基于用户权限的访问,第三种方式是access()方法。然而,access()方法更为通用。它接收一个指定授权条件的Spring表达式(SpEL)作为参数。这个方法很强大,而且它不仅仅指的是授权。然而,这个方法也使代码更难阅读和理解。出于这个原因,我推荐它作为最后的选择,而且只有在你不能应用本节前面介绍的hasAuthority()或hasAnyAuthority()方法之一的情况下。

为了使这个方法更容易理解,我首先把它作为用hasAuthority()和hasAnyAuthority()方法指定权限的替代方法。该方法必须提供一个Spring表达式作为方法的参数。然而,access()方法的优点是允许你通过你提供的表达式作为参数来定制规则。

注意 在大多数情况下,可以用hasAuthority()和hasAnyAuthority()方法实现所需的限制,推荐使用这些方法。只有在其他两个选项不合适,并且你想实现更多的通用授权规则时,才使用access()方法。

我们从一个简单的例子开始,以配合前面案例中的相同要求。 如果只需要测试用户是否有特定的权限,需要与access()方法一起使用的表达式可以是以下的一种。

  • hasAuthority(‘WRITE’)-提示用户需要WRITE授权来调用端点。
  • hasAnyAuthority(‘READ’, ‘WRITE’)-指定用户需要READ或WRITE权限中的一个。通过这个表达式,你可以列举出你想允许访问的所有权限。

请注意,这些表达式的名称与本节前面介绍的方法相同。下面的代码演示了如何使用access()方法。

代码清单7.7 使用access()方法来配置对api的访问

image-20230119162909952

从代码清单7.7中的例子可以看出,如果你将access()方法用于直接的要求,那么它是如何使语法复杂化的。在这种情况下,应该直接使用 hasAuthority() 或 hasAnyAuthority() 方法。但是access()方法并不全是不可取的,正如前面所说,它为你提供了灵活性。在现实世界的场景中,你可以用它来写更复杂的表达式,根据这些表达式,应用程序授予访问权。如果没有access()方法,你就无法实现这些场景。

在代码清单7.8中,我们发现如果access()方法不应用了表达式,就不容易写出来这样的权限控制。准确地说,代码清单7.8中的配置定义了两个用户,John和Jane,他们有不同的权限。用户John只有读取权限,而Jane有读取、写入和删除权限。api应该被那些有阅读权限的用户所访问,而不是那些有删除权限的用户。

代码清单7.8 用一个更复杂的表达式来应用access()方法

image-20230119163317445

当然,这只是一个假设的例子,但它足够简单,容易理解,也足够复杂,可以证明为什么access()方法更强大。

7.1.2 根据用户角色限制所有api的访问权限

在本节中,我们将讨论根据角色来限制对api的访问。角色是指用户可以做什么的另一种方式(图7.5)。你在现实世界的应用中也会发现这些,所以这就是为什么理解角色以及角色和权限之间的区别很重要。在本节中,我们将应用几个使用角色的例子,这样你就会知道应用程序使用角色的所有实际情况,以及如何为这些情况编写配置。

image-20230119164523120

图7.5 角色是粗粒度的。每个拥有特定角色的用户只能做该角色所授予的动作。在授权中应用这种理念时,根据用户在系统中的目的来允许请求。只有拥有特定角色的用户才能调用某个api。

Spring Security将权限理解为细粒度的特权,并对其施加限制。角色赋予用户一组行动的权限。例如在你的程序中,一个用户要么只拥有读取权限,要么拥有所有:读取、写入和删除权限。在这种情况下,如果认为那些只能阅读的用户拥有一个名为READER的角色,而其他用户拥有ADMIN的角色,拥有ADMIN角色意味着应用程序授予你读、写、更新和删除权限。程序中可能有更多的角色。例如,如果在某个时候,还需要一个只允许读和写的用户,你可以为你的应用程序创建第三个角色,名为MANAGER。

注意 当在应用程序中使用带有角色的方法时,我们将不必再定义权限。但是在应用程序中,需要定义一个角色来涵盖一个或多个用户被授权的行为。

用户可以自定义角色的名字,与授权相比,角色是粗粒度的,一个角色含有多个授权。 当定义一个角色时,它的名字应该以ROLLE_的前缀开始。在实现层面上,这个前缀指定了角色和权限之间的区别。在下一个代码清单中,看看我对前面的例子所做的修改。

清单7.9 为用户设置角色

package com.hashnode.proj0001firstspringsecurity.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
 * @author Guowei Chi
 * @date 2023/1/19
 * @description:
 **/
@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
    @Bean
    @Override
    public UserDetailsService userDetailsService(){
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();

        UserDetails user1 = User.withUsername("john")
                .password("12345")
                //具有ROLE_前缀,表示角色
                .authorities("ROLE_ADMIN")
                .build();

        UserDetails user2 = User.withUsername("jane")
                .password("12345")
                .authorities("ROLE_MANAGER")
                .build();

        manager.createUser(user1);
        manager.createUser(user2);
        return manager;
    }

   //省略其他代码
}

要为用户角色设置约束,你可以使用以下方法之一。

  • hasRole()-接收一个参数,即应用程序授权请求的角色名称。
  • hasAnyRole()-接收作为参数的角色名称,应用程序为其批准请求。
  • access()-使用Spring表达式来指定应用程序授权请求的一个或多个角色。就角色而言,你可以使用hasRole()或hasAnyRole()作为SpEL表达式。

正如你所看到的,这些名称与第7.1.1节中介绍的方法类似。在下一个代码清单中,你可以看到configure()方法现在是什么样子。

package com.hashnode.proj0001firstspringsecurity.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;


@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {
    @Bean
    @Override
    public UserDetailsService userDetailsService(){
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();

        UserDetails user1 = User.withUsername("john")
                .password("12345")
                //具有ROLE_前缀,表示角色
                .authorities("ROLE_ADMIN")
                .build();

        UserDetails user2 = User.withUsername("jane")
                .password("12345")
                .authorities("ROLE_MANAGER")
                .build();

        manager.createUser(user1);
        manager.createUser(user2);
        return manager;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic();

        //hasRole()方法现在指定了允许访问该api的角色。注意,ROLLE_前缀不会出现在这里。
        http.authorizeRequests()
                .anyRequest().hasRole("ADMIN");
    }
}

注意 需要注意是,我们只使用ROLE_前缀来声明角色。但当我们使用角色时,我们只用它的名字来做。

测试结果如下:

curl -u john:12345 http://localhost:8080/hello
Hello!
curl -u jane:12345 http://localhost:8080/hello
{
"status":403,
"error":"Forbidden",
"message":"Forbidden",
"path":"/hello"
}

注意 确保你为role()方法提供的参数不能包括ROLLE_前缀。如果不小心在role()参数中包含了该前缀,该方法会抛出一个异常。简而言之,当使用authorities()方法时,要包括ROLLE_前缀。当使用role()方法时,不要包括ROLLE_前缀。

在下面的代码清单中,你可以看到当设计基于角色的访问时,使用role()方法而不是authorities()方法的正确使用方式。

代码清单7.11 用role()方法设置角色

image-20230119170707960

在第7.1.1节和第7.1.2节中,我们学习了如何使用access()方法来应用提及权限和角色的授权规则。一般来说,在一个应用程序中,授权限制是与权限和角色相关的。但重要的是要记住,access()方法是通用的。在我介绍的例子中,我主要是教你如何将它应用于权限和角色,但在实践中,它可以接受任何SpEL表达。它不需要与权限和角色相关联。一个直接的例子是将对端点的访问配置为只允许在晚上12:00以后。要解决这样的问题,你可以使用下面的SpEL表达式:

T(java.time.LocalTime).now().isAfter(T(java.time.LocalTime).of(12, 0))

关于SpEL表达式的更多信息,请参见Spring文档:Core Technologies (spring.io)

通过access()方法,基本上可以实现任何种类的规则。这种可能性是无穷无尽的。只是别忘了,在应用程序中,我们总是努力使语法尽可能地简单。只有当你没有其他选择的时候,才会使你的配置复杂化。

7.1.3 限制对所有api的访问

在这一节中,我们将讨论限制对所有请求的访问。我们在第5章中了解到,通过使用permitAll()方法,可以允许对所有请求的访问。你还了解到,可以根据权限和角色来应用访问规则,不仅如此还可以拒绝所有请求。denyAll()方法与permitAll()方法正好相反。在接下来的代码清单中,你可以看到如何使用denyAll()方法。

代码清单7.12 使用denyAll(方法来限制对端点的访问

image-20230119171349472

那么,可以在什么地方使用这种限制呢?假设现在有一个api,以电子邮件地址作为入参,我们想要允许那些变量地址的值以.com结尾的请求,不希望应用程序接受任何其他格式的电子邮件地址。对于这个需求,我们可以使用一个正则表达式来分组符合规则的请求,然后使用denyAll()方法来使应用程序拒绝所有这些请求(图7.6)。

image-20230119171722316

图7.6 当用户调用端点并提供以.com结尾的参数值时,应用程序接受该请求。当用户调用端点并提供以.net结尾的电子邮件地址时,应用程序会拒绝该调用。为了实现这种行为,你可以对所有参数值不以.com结尾的端点使用denyAll()方法。

在微服务的场景,如图7.7所示,不同微服务有不同的功能,这些用例可以通过调用不同路径上的api进行访问。但是为了调用一个api,需要请求网关,在这个架构中,有两个网关服务。 在图7.7中,我把它们称为网关A和网关B。 客户端如果想访问/产品路径,就请求网关A。但对于/文章路径,客户必须请求网关B。每一个网关服务都被设计为拒绝所有对其他路径的请求,这些服务不为这些路径服务。这个简化的场景可以帮助你轻松理解denyAll()方法。在一个生产应用中,你可以在更复杂的架构中找到类似的情况。

image-20230119172207660

图7.7 通过网关A和网关B进行访问。每个网关只接收特定路径的请求,拒绝其他所有的请求。

总结

  • 授权是一个过程,在这个过程中,应用程序决定一个经过授权的请求是否被允许。授权总是发生在授权之后。
  • 在应用程序中,可以指定某些请求对未认证的用户是可以访问的。
  • 可以配置应用程序,通过使用denyAll()方法拒绝任何请求,或者使用permitAll()方法允许任何请求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/172332.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[leetcode 72] 编辑距离

题目 题目&#xff1a;https://leetcode.cn/problems/edit-distance/description/ 类似题目&#xff1a;[leetcode 583] 两个字符串的删除操作 解法 动态规划 这题应该是字符串dp的终极形态了吧&#x1f923;&#xff0c;不看答案完全不会…看了答案发现原来还是dp… 以例题…

未来的竞争是认知和执行力的竞争,只有认知高,强执行才能赚钱

之前很火的一句话是&#xff1a;你永远赚不到认知范围之外的钱所以只有持续不断地提升认知才能持续成长&#xff0c;持续提升&#xff0c;持续赚钱。未来的竞争从另一方面来说也是认知的竞争。不同的认知对待同一事物、信息有不同的理解&#xff1b;不同的认知对待同一事物、信…

固高科技在创业板提交注册:业绩开始下滑,实控人均为“学院派”

近日&#xff0c;固高科技股份有限公司&#xff08;下称“固高科技”&#xff09;在深圳证券交易所创业板递交注册。据贝多财经了解&#xff0c;固高科技于2021年12月在创业板递交上市申请&#xff0c;2022年8月17日获得上市委会议通过。 本次冲刺创业板上市&#xff0c;固高科…

【一道面试题】说一下Synchronized?

说一下Synchronized&#xff1f; Synchronized锁是Java中为了解决线程安全问题的一种方式&#xff0c;是一种悲观锁Synchronized可以用来修饰方法或者以代码块&#xff0c;用来保证线程执行方法或代码块时的原子性Java中任何一个类的对象都可以用来作为锁对象&#xff0c;但是…

docker-15-镜像Ubuntu20.04中安装python3.9

1 拉取并运行镜像 从docker hub 拉取镜像&#xff0c;以ubuntu20.04为例&#xff1a; docker pull ubuntu:20.04 docker run -it ubuntu:20.04 /bin/bash发现命令行变为root1234abcd5678:&#xff0c;这样就是进入docker容器里了。以下是docker常用的命令&#xff1a; # 以…

8086到80386汇编数据传送指令的扩展

80386及以上汇编的数据传送指令如下&#xff1b; MOV 传送字或字节. MOVSX 先符号扩展,再传送. MOVZX 先零扩展,再传送. PUSH 把字压入堆栈. POP 把字弹出堆栈. PUSHA 把AX,CX,DX,BX,SP,BP,SI,DI依次压入堆栈. POPA 把DI,SI,BP,SP,BX,DX,CX,A…

人大金仓数据库KSQL常用命令

第三章KSQL常用命令 登陆前显示ksql的帮助命令 Ksql --help 列出所有的SQL命令清单 test# \h 列出某个SQL命令语法大纲 \h <sql命令> 如&#xff1a;\h delect 查看ksql元命令的帮助 ..... 查看数据库列表 显示当前连接的数据库和登录用户 \c 显示当前test数据库的…

数学和统计方法

平均数&#xff0c;加权平均数&#xff0c;中位数&#xff0c;众数 1、平均数&#xff1a;所有数加在一起求平均 2、中位数&#xff1a;对于有限的数集&#xff0c;可以通过把所有观察值高低排序后找出正中间的一个作为中位数。如果观察值有偶数个&#xff0c;通常取最中间的 …

Spring Boot学习篇(十一)

Spring Boot学习篇(十一) shiro安全框架使用篇(三) 1.shiro过滤地址配置(部分地址必须要登录才能访问) 1.1 在controll包下创建CRUDController类(用于提供地址进行测试),其内容如下所示 package com.zlz.controller;import org.springframework.stereotype.Controller; imp…

回顾一次后台从war包启动到jar包启动的改造

一、背景描述 1.项目情况 有个项目后台一开始是war包部署到tomcat中部署的 配置文件放在项目中 考虑到这种部署方式相对spring boot项目内置tomcat部署不太便捷&#xff0c;配置也没有独立出来&#xff0c;考虑将原来的spring mvc项目稍微改造为spring boot项目。 2.要求 1&am…

Linux设备树的概念

一.设备树概念以及作用1.设备树概念设备树(Device Tree)&#xff0c;将这个词分开就是“设备”和“树”&#xff0c;描述设备树的文件叫做 DTS(DeviceTree Source)&#xff0c;这个 DTS 文件采用树形结构描述板级设备&#xff0c;也就是开发板上的设备信息&#xff0c;比如CPU …

flowable的Task使用

ReceiveTask UserTask ServiceTask ScriptTask ReceiveTask 执行到这个ReceiveTask会停下来&#xff0c;需要人工触发一下&#xff0c;才会继续执行 ClassPathResource classPathResource new ClassPathResource("processes/ReceiveTaskDemo.bpmn20.xml");String f…

C++——模板与STL标准模板库

目录 一、模板 1.1类型模板 1.2非类型模板 二、STL 2.1链表实现 2.2迭代器 2.3STL容器 2.4STL算法 三、模板特化的匹配规则 (1) 类模板的匹配规则 (2) 函数模板的匹配规则 一、模板 1.1类型模板 #include <stdio.h> #include <iostream>using namespac…

深度学习 GAN生成对抗网络-手写数字生成及改良

如果你有一定神经网络的知识基础&#xff0c;想学习GAN生成对抗网络&#xff0c;可以按顺序参考系列文章&#xff1a; 深度学习 自动编码器与生成模型 深度学习 GAN生成对抗网络-1010格式数据生成简单案例 深度学习 GAN生成对抗网络-手写数字生成 一、前言 在前面一篇文章&am…

877. 石子游戏

877. 石子游戏题目算法设计&#xff1a;奇偶算法设计&#xff1a;动态规划题目 算法设计&#xff1a;奇偶 最简单的情况&#xff0c;只有2堆石子&#xff08;石子奇数&#xff09;&#xff0c;先稳赢。 但是四堆情况不同了&#xff0c;如 [3 7 2 3]。 不能直接选最大的&…

2023年五大趋势预测 | 大数据分析、人工智能和云产业展望

随着我们迈入2023年&#xff0c;大数据分析、人工智能和云产业将迎来蓬勃的创新和发展阶段 以下是我们预测的&#xff0c;将对行业格局产生重大影响的五大趋势&#xff1a; 世界在剧变&#xff0c;我们需要尽快寻找行业中的方向&#xff0c;迅速重回轨道 2023年&#xff0c;全…

TryHackMe-NahamStore(常见web漏洞 大杂烩)

NahamStore 漏洞赏金web安全 NahamStore的创建是为了测试您在NahamSec的“漏洞赏金狩猎和Web应用程序黑客入门”Udemy课程中学到的知识。 部署计算机&#xff0c;获得 IP 地址后&#xff0c;进入下一步&#xff01; 写在前面 可能我的顺序&#xff0c;跟别人以及题目都不太一…

spring boot集成activemq(windows)

目录 1.环境配置 2.说明 3.服务启动 4.示例 导入依赖 配置文件 service层 配置类 监听器 5.总结 1.环境配置 下载地址&#xff1a;https://activemq.apache.org/components/classic/download/安装&#xff1a;解压缩即可注意每个版本对应的java版本不一样&#xff0c…

分享96个PHP源码,总有一款适合您

PHP源码 分享96个PHP源码&#xff0c;总有一款适合您 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c; 96个PHP源码下载链接&#xff1a;https://pan.baidu.com/s/1B-tNZlbfjT_D3n_Y6ZwfDw?pwduq19 提取码&#xff…

共享自助自习室棋p室茶室办公室电竞篮球馆小程序开发

共享自助自习室棋p室茶室办公室电竞篮球馆小程序开发 多场景应用的共享空间预约系统如:棋牌室;共享办公室&#xff0c;电竞篮球馆&#xff0c;自助民宿等。目前该应用已对接门锁和电控。 前端功能// 多场景应用、预约时间自定义、附近门店一目了然、支持门禁支持电控、首页门…