Spring Security in Action 第八章 配置授权:api授权

news2024/11/15 12:49:23

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

文章目录

    • 8.1 使用匹配器方法来选择端点
    • 8.2 使用MVC matchers选择需要授权的请求
    • 8.3 使用Ant matchers选择需要授权的请求
    • 8.4 使用regex matchers选择需要授权的请求

本章包括

  • 选择使用匹配器方法限制请求

  • 学习每种匹配器方法的最佳实践

在第7章中,我们学会了如何根据权限和角色来配置访问,但我们只对所有的端点应用了配置。在本章中,我们将学习如何对一组特定的请求应用授权约束。 在开发中,我们不太可能对所有请求应用相同的规则。例如某些api,只有一些特定的用户可以调用,而其他api可能是所有人都可以访问的。每个应用程序,根据业务需求,都有自己的自定义授权配置。让我们来讨论一下,当我们写访问配置时,你有哪些选项可以参考不同的请求。

之前我们使用的第一个匹配器方法是anyRequest()方法。首先,我们来谈谈按路径选择请求的问题;然后,我们也可以把HTTP方法加入到这个场景中。为了选择我们应用授权配置的请求,我们使用匹配器方法。Spring Security为我们提供了三种类型的匹配器方法。

  • MVC matchers-使用MVC表达式的路径来选择api。
  • Ant matchers-使用Ant表达式的路径来选择api。
  • regex matchers-你使用正则表达式(regex)的路径来选择api。

8.1 使用匹配器方法来选择端点

在本节中,我们将学习如何使用一般的匹配器方法。先从一个简单的例子开始,我们创建一个暴露两个api的应用程序:/hello和/ciao。我们想确保只有拥有ADMIN角色的用户才能调用/hello端点。同样地,我们要确保只有拥有MANAGER角色的用户可以调用/ciao端点。你可以在项目sia-ch8-ex1中找到这个例子。下面的列表提供了控制器类的定义。

在配置类中,我们声明一个InMemoryUserDetailsManager作为我们的UserDetailsService实例,并添加两个具有不同角色的用户。用户John拥有ADMIN角色,而Jane拥有MANAGER角色。在授权请求时,拥有ADMIN角色的用户可以调用端点/hello,我们使用mvcMatchers()方法。下一个代码清单介绍了Configuration类的定义。

代码清单8.2 配置类的定义

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("ADMIN")
                .build();

        UserDetails user2 = User.withUsername("jane")
                .password("12345")
                .authorities("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();
        http.authorizeRequests()
                .mvcMatchers("/hello").hasRole("ADMIN")
                .mvcMatchers("/ciao").hasRole("MANAGER");
    }
}

当用户John调用api /hello时,会得到一个成功的响应。但如果是用户Jane调用同一个api,响应状态返回HTTP 403 Forbidden。同样地,对于api /ciao,只能用Jane来获得一个成功的结果。对于用户John,响应状态会返回HTTP 403 Forbidden。

如果给应用程序添加其他的api,它默认是可以被任何人访问的,甚至是未认证的用户。例如一个新的api /hola,如下面的列表所示。

代码清单8.3 在应用程序中为路径/hola添加一个新的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("/hola")
    public String hola(){
        return "Hola!";
    }
}

当访问这个新的api时,可以看到,无论是否有一个有效的用户,它都可以被访问。

可以通过使用 permitAll() 方法使这种行为更加明显,例如通过在请求授权的配置链末端使用 anyRequest() 匹配器方法来做到这一点,如代码清单 8.4 所示。

清单8.4 将额外的请求明确标记为无需认证即可访问

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()
                .mvcMatchers("/hello").hasRole("ADMIN")
                .mvcMatchers("/ciao").hasRole("MANAGER")
                .anyRequest().permitAll();
    }
}

注意 当你使用匹配器来指代请求时,规则的顺序应该是由特殊到一般。这就是为什么anyRequest()方法不能在像mvc- Matchers()这样更具体的匹配器方法之前被调用。

未认证与认证失败

如果程序中设计了一个任何人都可以访问的api,则可以在不提供用户名和密码认证的情况下调用它。在这种情况下,Spring Security不会进行认证。然而,如果提供了一个用户名和密码,Spring Security会在认证过程中校验它们。如果它们是错误的(不被系统所知),认证就会失败,响应状态将是401未授权。更准确地说,如果调用代码清单8.4中的配置的/hola端点,应用程序会按照预期返回主体 “Hola!”,响应状态是200 OK。

但如果用无效的凭证调用api,响应的状态是401未授权。

curl -u bill:abcde http://localhost:8080/hola

返回的json为:

{
“status”:401,
“error”:“Unauthorized”,
“message”:“Unauthorized”,
“path”:“/hola”
}

框架的这种行为可能看起来很奇怪,但它是有意义的,因为如果你在请求中提供了用户名和密码,框架就会校验它们。正如你在第7章中学到的,应用程序总是在授权之前进行认证,如图所示。

在这里插入图片描述

授权过滤器允许任何对/hola路径的请求。但是,由于应用程序首先执行了认证逻辑,请求从未被转发到授权过滤器。 相反,认证过滤器以HTTP 401 Unauthorized进行回复。

总之,任何认证失败的情况都会产生一个状态为401 Unauthorized的响应,应用程序不会将调用转发给终端。permitAll()方法仅指授权配置,如果认证失败,将不允许进一步调用。

代码清单8.5 使所有认证的用户都能访问其他请求

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()
                .mvcMatchers("/hello").hasRole("ADMIN")
                .mvcMatchers("/ciao").hasRole("MANAGER")
                .anyRequest().authenticated();
    }
}

在大多数实际情况下,多个api可以有相同的授权规则,所以不必逐个端点地设置它们。同样,有时需要指定HTTP方法,而不仅仅是路径,就像我们之前所做的那样。 有时,只需要在一个api的路径被HTTP GET调用时为其配置规则。在这种情况下,需要为HTTP POST和HTTP DELETE定义不同的规则。在接下来的章节中,我们将对每种类型的匹配器方法进行详细讨论。

8.2 使用MVC matchers选择需要授权的请求

在本节中,我们将讨论MVC匹配器。使用MVC表达式是应用授权配置的请求的一种常见方法。

这个匹配器使用标准的MVC语法来引用路径。这种语法与使用@RequestMapping、@GetMapping、@PostMapping等注释编写api映射时使用的语法相同。可以用两种方法来声明MVC匹配器,如下所示:

  • mvcMatchers(HttpMethod method, String… patterns)-同时指定限制所适用的HTTP方法和路径。 如果你想对同一路径的不同HTTP方法适用不同的限制,这个方法很有用。
  • mvcMatchers(String…pattern)–如果只需要根据路径来进行应用授权,则更简单,更容易使用。这些限制可以自动适用于与路径一起使用的任何HTTP方法。

在这一节中,我们将讲解使用mvcMatchers()方法的多种方式。为了证明这一点,我们先写一个暴露了多个api的应用程序。

这是我们第一次编写可以用除GET以外的其他HTTP方法调用的端点。你可能已经注意到,直到现在,我还在避免使用其他的HTTP方法。其原因是Spring Security默认应用了对跨站请求伪造(CSRF)的保护。在第一章中,我描述了CSRF,它是Web应用程序最常见的漏洞之一。在很长一段时间里,CSRF都出现在OWASP的十大漏洞中。在第10章,我们将讨论Spring Security如何通过使用CSRF令牌来缓解这一漏洞。但为了使目前的例子更简单,并且能够调用所有的端点,包括那些用POST、PUT或DELETE暴露的api,我们需要在configure()方法中禁用CSRF保护。

http.csrf().disable();

我们首先定义了四个api,用于我们的测试。

  • /a using the HTTP method GET
  • /a using the HTTP method POST
  • /a/b using the HTTP method GET
  • /a/b/c using the HTTP method GET

通过这些api,我们可以考虑不同的授权配置方案。在代码清单8.6中,可以看到这些端点的定义。

代码清单8.6 我们为其配置授权的四个端点的定义

package com.hashnode.proj0001firstspringsecurity.controller;

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

@RestController
public class TestController {
    @PostMapping("/a")
    public String postEndpointA() {
        return "Works!";
    }
    @GetMapping("/a")
    public String getEndpointA() {
        return "Works!";
    }
    @GetMapping("/a/b")
    public String getEnpointB() {
        return "Works!";
    }
    @GetMapping("/a/b/c")
    public String getEnpointC() {
        return "Works!";
    }
}

我们还需要几个具有不同角色的用户。为了保持简单,我们继续使用一个InMemoryUserDetailsManager。在下一个代码清单中,你可以看到配置类中的UserDetailsService的定义。

清单8.7 UserDetailsService的定义

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_前缀,表示角色
                .roles("ADMIN")
                .build();

        UserDetails user2 = User.withUsername("jane")
                .password("12345")
                .roles("MANAGER")
                .build();

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

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

}

让我们从第一种情况开始。对于使用HTTP GET方法对/a路径进行的请求,应用程序需要对用户进行认证。对于/a请求,使用HTTP POST方法不需要认证,除此之外的其他请求被拒绝。下面的代码显示了需要编写的配置,以实现这一设置。

代码清单8.8 第一个方案的授权配置,/a

package com.hashnode.proj0001firstspringsecurity.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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_前缀,表示角色
                .roles("ADMIN")
                .build();

        UserDetails user2 = User.withUsername("jane")
                .password("12345")
                .roles("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.csrf().disable();
        http.httpBasic();
        http.authorizeRequests()
                .mvcMatchers(HttpMethod.GET,"/a")
                .authenticated()
                .mvcMatchers(HttpMethod.POST,"/a")
                .permitAll()
                .anyRequest().denyAll();
    }
}

通过这个例子,我们知道如何根据HTTP方法来区分请求了。但是,如果多个路径有相同的授权规则怎么办?当然,我们可以列举所有适用授权规则的路径,但如果我们有太多的路径,这就会使阅读代码时感到不舒服。同样,我们可能一开始就知道,一组具有相同前缀的路径总是具有相同的授权规则。我们要确保,如果一个开发者在同一组中添加了一个新的路径,它不会同时改变授权配置。为了管理这些情况,我们使用路径表达式。

对于目前的项目,我们要确保对以/a/b开头的路径的所有请求适用相同的规则。在我们的例子中,这些路径是/a/b和/a/b/c。为了实现这一点,我们使用**操作符。(Spring MVC从Ant那里借用了路径匹配的语法。)

代码清单8.9 多个路径的配置类的变化

package com.hashnode.proj0001firstspringsecurity.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.csrf().disable();
        http.httpBasic();
        http.authorizeRequests()
                .mvcMatchers("/a/b/**")
                .authenticated()
                .anyRequest().permitAll();
    }
}

/**正如前面的例子中所介绍的,操作符指的是任何数量的路径名称。你可以像我们在上一个例子中那样使用它,这样你就可以匹配有已知前缀的路径的请求。同时也可以在中间使用,例如/a/**/c。因此,/a/**/c不仅可以匹配/a/b/c,还可以匹配/a/b/d/c和a/b/c/d/e/c,等等。如果你只想匹配一个路径名,那么你可以使用单个*。例如,a/*/c将匹配a/b/c和a/d/c,但不匹配a/b/d/c。
**/

现在让我们来看看另外一个例子。 假设一个api想拒绝所有参数值不是数字的其他请求,如下所示:

代码清单8.10 在控制器类中定义一个带有路径变量的端点

package com.hashnode.proj0001firstspringsecurity.controller;

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

@RestController
public class ProductController {
    @GetMapping("/product/{code}")
    public String productCode(@PathVariable String code){
        return code;
    }
}

下一个代码清单显示了如何配置授权,使得只有那些数值只包含数字的访问总是被允许的,而所有其他的访问都被拒绝。

代码清单8.11 配置授权,只允许特定的数字

package com.hashnode.proj0001firstspringsecurity.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.csrf().disable();
        http.httpBasic();
        http.authorizeRequests()
                .mvcMatchers("/product/{code:^[0-9]*$}")
                .permitAll()
                .anyRequest().denyAll();
    }
}

注意 当使用参数表达式与一个正则表达式时,确保在参数名称、冒号(:)和正则表达式之间没有空格。

运行这个例子,当路径变量值只有数字时,应用程序才会接受调用。

我们讨论了很多,并包括了很多关于如何使用MVC匹配器来引用请求的例子。表8.1是对在本节中使用的MVC表达式的复习。

表8.1 用MVC匹配器进行路径匹配的常用表达式

/a只有路径/a。
/a/**操作符替换了一个路径名。在这种情况下,它匹配/a/b或/a/c,但不匹配/a/b/c。
/a/****操作符替换了多个路径名。在这种情况下,/a以及/a/b和/a/b/c是这个表达式的匹配对象。
/a/{param}这个表达式适用于带有给定路径参数的路径/a。
/a/{param:regex}只有当参数的值与给定的正则表达式相匹配时,这个表达式才适用于带有给定路径参数的路径/a。

8.3 使用Ant matchers选择需要授权的请求

在这一节中,我们将讨论Ant matchers,用于匹配相应规则的权限。因为Spring从Ant那里借用了MVC表达式来匹配api的路径,所以可以使用Ant matchers的语法与8.2节看到的语法相同。

在开发中,建议使用MVC matchers而不是Ant matchers。使用Ant匹配器时的三种方法是:

  • antMatchers(HttpMethod method, String patterns)-允许同时指定限制所适用的HTTP方法和指向路径的Ant patterns。如果你想对同一组路径的不同HTTP方法应用不同的限制,这个方法很有用。
  • antMatchers(String patterns)–如果只需要根据路径来应用授权限制,这些策略可以自动应用于HTTP方法。
  • antMatchers(HttpMethod method),相当于antMatchers(httpMethod, “/**”)–可以使我们不考虑路径而引用一个特定的HTTP方法。

应用这些匹配器的方式与上一节中的MVC matchers类似。 同时,我们用于引用路径的语法也是一样的。那么有什么不同呢? MVC匹配器是指你的Spring应用程序如何理解将请求与控制器动作相匹配。但是如果在路径后面再加上一个/,那么任何通往同一个动作的路径都可以被Spring解释。在这种情况下,/hello和/hello/调用的是同一个方法。如果你使用MVC匹配器并为/hello路径配置安全,它会自动用同样的规则保护/hello/路径,但是如果使用Ant matchers 则会导致/hello需要认证,而/hello/不需要认证。如果不清楚这一点并使用Ant匹配器的开发者可能会在没有注意到的情况下留下一个未受保护的路径,这将为应用程序带来重大的安全漏洞。

让我们用一个例子来测试这个行为。

代码清单8.12 控制器类中/hello端点的定义

@RestController
public class HelloController {
    @GetMapping("/hello")
    	public String hello() {
    	return "Hello!";
    }
}

代码清单8.13描述了这个配置类。在这种情况下,我使用一个MVC matchers来定义/hello路径的授权配置,对这个api的任何请求都需要认证。我在例子中省略了UserDetailsService和PasswordEncoder的定义,因为这些与代码清单8.7中的定义相同。

代码清单8.13 使用MVC匹配器的配置类

package com.hashnode.proj0001firstspringsecurity.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.csrf().disable();
        http.httpBasic();
        http.authorizeRequests().mvcMatchers("/hello").authenticated();
    }
}

如果启动该应用程序并进行测试,会发现/hello和/hello/路径都需要认证。这可能是你所期望的结果。接下来的代码片段显示了用cURL对这些路径进行的请求。调用未认证的/hello api看起来像这样。

curl http://localhost:8080/hello
{
"status":401,
"error":"Unauthorized",
"message":"Unauthorized",
"path":"/hello"
}

curl http://localhost:8080/hello/
{
"status":401,
"error":"Unauthorized",
"message":"Unauthorized",
"path":"/hello"
}

curl -u jane:12345 http://localhost:8080/hello
Hello!

curl -u jane:12345 http://localhost:8080/hello/
Hello!

所有这些返回值都是我们期望的。但让我们看看如果我们改变实现方式,使用Ant匹配器会发生什么。如果你只是改变配置类,对相同的表达式使用Ant matchers,结果就会改变。如前所述,该应用并没有为/hello/路径应用授权配置。在这种情况下,/hello并不作为Ant表达式应用于/hello/路径。如果你也想确保/hello/路径的安全,你必须单独添加它,或者写一个Ant表达式来匹配它。下面的列表显示了在配置类中使用Ant匹配器而不是MVC匹配器所做的改变。

package com.hashnode.proj0001firstspringsecurity.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.csrf().disable();
        http.httpBasic();
        http.authorizeRequests().antMatchers("/hello").authenticated();
    }
}

测试结果如下

curl http://localhost:8080/hello
{
"status":401,
"error":"Unauthorized",
"message":"Unauthorized",
"path":"/hello"
}

curl http://localhost:8080/hello/
Hello!

所以推荐使用MVC匹配器。使用MVC匹配器,你可以避免Spring将路径映射到动作的方式带来的一些风险。这是因为你知道在授权规则中解释路径的方式与Spring本身在将路径映射到端点时解释的方式相同。当你使用Ant匹配器时,要谨慎行事,确保你的表达式确实与你需要应用授权规则的所有内容相匹配。

沟通和知识共享的影响

我一直鼓励以所有可能的方式分享知识:书籍、文章、会议、视频等等。有时,即使是简短的讨论也能提出问题,从而推动巨大的改进和变化。我将通过几年前我讲的关于Spring课程的一个故事来说明我的意思。

这次培训是为一群为特定项目工作的中级开发人员设计的。它与Spring Security没有直接关系,但在某些时候,我们开始在培训中使用匹配器方法,作为培训的一部分,我们正在处理一个例子。

我开始用MVC matchers配置api授权规则,而没有先教学员MVC matchers的知识。我认为他们已经在他们的项目中使用了这些规则;我认为没有必要先解释它们。当我正在进行配置和讲授我正在做的事情时,一个与会者学院问了一个问题。我仍然记得那位女士羞涩的声音说:“你能介绍一下你所使用的这些MVC方法吗?我们正在用一些Ant-something方法来配置我们的api的安全”。

我当时意识到,学员可能没有意识到他们在使用什么。他们确实在使用Ant matchers,但并不了解这些配置,很可能是在机械地使用它们。复制和粘贴编程是一种危险的方法,不幸的是,这种方法经常被使用,特别是被初级开发人员使用。你永远不应该在不了解它的作用的情况下使用它。

在我们讨论这个新话题的时候,这位女士在他们的讨论中发现了Ant匹配器被错误地应用的情况。培训结束后,他们的团队安排了一个完整的流程来验证和纠正这种错误,这可能会导致他们的应用程序出现非常危险的漏洞。

8.4 使用regex matchers选择需要授权的请求

在本节中,我们讨论正则表达式(regex)。你应该已经知道什么是正则表达式,但你不需要成为这方面的专家。你可以从中更深入地了解这个主题。对于编写正则表达式,我也经常使用在线生成器,如https://regexr.com/(图8.1)。

在这里插入图片描述

图8.1 让你的猫在键盘上玩耍并不是生成正则表达式(regex)的最佳方案。要学习如何生成正则表达式,你可以使用一个在线生成器,如https://regexr.com/。

我们在第8.2节和第8.3节中了解到,在大多数情况下,你可以使用MVC和Ant结合来引用你应用授权配置的请求。然而,在某些情况下,你可能有更特殊的要求,而你无法用Ant和MVC表达式来解决这些问题。这种需求的一个例子是这样的。“当路径包含特定的符号或字符时,拒绝所有请求”。对于这些情况,你需要使用一个更强大的表达式,如正则表达式。

你可以用正则表达式来表示字符串的任何格式,所以它们在这个问题上提供了无限的可能性。

你可以用以下两种方法来实现regex匹配器:

  • regexMatchers(HttpMethod method, String regex)-指定适用限制的HTTP方法和参考路径的regexes。如果你想对同一组路径的不同HTTP方法应用不同的限制,这个方法很有用。
  • regexMatchers(String regex)–如果你只需要根据路径应用授权限制,则更简单,更容易使用。这些限制会自动适用于任何HTTP方法。

为了证明正则表达式是如何工作的,让我们通过一个例子来演示:建立一个向用户提供视频内容的应用程序。预设视频的应用程序通过api /video/{country}/{language}获得其内容。 在这个例子中,应用程序从用户提出请求的地方接收两个路径变量中的国家和语言。我们认为,如果请求来自美国、加拿大或英国,或者他们使用英语,任何经过授权的用户都可以看到视频内容。

我们需要保护的api有两个路径变量,如以下代码清单所示。这使得用Ant或MVC匹配器实现这一需求变得复杂。

package com.hashnode.proj0001firstspringsecurity.controller;

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

/**
 * @author Guowei Chi
 * @date 2023/1/20
 * @description:
 **/
@RestController
public class VideoController {
    @GetMapping("/video/{country}/{language}")
    public String video(@PathVariable String country,
                        @PathVariable String language) {
        return "Video allowed for " + country + " " + language;
    }
}

对于一个单一路径变量的条件,我们可以直接在Ant或MVC表达式中写一个regex。我们在第8.3节中提到了这样一个例子,但我当时没有深入讨论它,因为我们当时没有讨论正则表达式。

假设有一个api /email/{email}。我们想用一个匹配器来应用一个规则,只适用于那些将以.com结尾的地址作为参数值的电子邮件的请求。在这种情况下,你要写一个MVC匹配器,如下面的代码片段所示。

http.authorizeRequests()
	.mvcMatchers("/email/{email:.*(.+@.+\\.com)}")
	.permitAll()
	.anyRequest()
	.denyAll();

需求有时是复杂的,当你发现像下面这样的情况时,你会发现使用regex匹配器会更方便。

  • 对所有含有电话号码或电子邮件地址的路径进行具体配置
  • 对所有具有某种格式的路径进行具体配置,包括通过所有路径变量发送的内容

回到我们的regex匹配器的例子:当需要写一个更复杂的规则,最终引用更多的路径模式和多个路径变量值时,写一个regex匹配器会更容易。在代码清单8.16中,可以找到配置类的定义,它使用了一个regex匹配器来解决为/video/{country}/{language}路径给出的要求。我们还添加了两个具有不同权限的用户来测试该实现。

代码清单8.16 使用正则表达式的配置类

package com.hashnode.proj0001firstspringsecurity.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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_前缀,表示角色
                .roles("read")
                .build();

        UserDetails user2 = User.withUsername("jane")
                .password("12345")
                .roles("read","premium")
                .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.csrf().disable();
        http.httpBasic();
        http.authorizeRequests().regexMatchers(".*/(us|uk|ca)+/(en|fr).*")
                .authenticated().anyRequest().hasAuthority("premium");
    }
}

运行并测试端点,确认应用程序正确应用了授权配置。用户John可以调用国家代码US和语言en的api,但由于我们配置的限制,他不能调用国家代码FR和语言fr的api。

curl -u john:12345 http://localhost:8080/video/us/en
Video allowed for us en

curl -u john:12345 http://localhost:8080/video/fr/fr
{
"status":403,
"error":"Forbidden",
"message":"Forbidden",
"path":"/video/fr/fr"
}

curl -u jane:12345 http://localhost:8080/video/us/en
Video allowed for us en

curl -u jane:12345 http://localhost:8080/video/fr/fr
Video allowed for fr fr

正则表达式是强大的工具。可以用它们来引用任何特定要求的路径。但是,由于正则表达式很难读,而且可能变得相当长,它们应该是你最后的选择。只有在MVC和Ant表达式不能为你提供问题的解决方案时,才使用这些工具。

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

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

相关文章

并查集(Java实现)

基本实现 任务: 维护多个不相交的集合,支持两种操作:合并两个集合,查询一个元素所在的集合。 说明: 维护一个森林,每一棵树都代表一个集合,树根元素为这个集合的代表元。利用数组father[]查询记…

[标准库]STM32F103R8T6 串口的收发

前言 这篇记录一下怎么调用标准库的函数来初始化一个串口,并调库实现发数据和收数据,以及串口收中断的使用。 越往深处学习越感觉其实32就是一个功能更加齐全和强大的MCU,其实跟51没有什么本质上的区别。很多设置的地方都是同质化的。比如需…

JVM知识点整理(整理中)

JVM知识点整理1、JVM与java体系结构1.1、java的体系结构1.2、JVM1.2.1、从跨平台的语言到跨语言的平台1.2.2、常用的JVM实现1.2.3、JVM的位置1.2.4、JDK、JER、JDK1.2.5、JVM的整体结构1.2.6、java代码的执行流程1.2.7、JVM的代码模型1.2.8、JVM的生命周期2、类加载子系统2.1、…

ARM NandFlash 介绍

一、NandFlash 的接口 1、Nand 的型号与命名 (1) Nand 的型号命名都有含义,就拿 K9F2G08 来示例分析一下:K9F 表示是三星公司的 NandFlash 系列。2G 表示 Nand 的大小是 2Gbit(256MB)。08 表示 Nand 是 8 位的( 8 位…

员工入职管理系统|员工管理系统|基于SpringBoot+Vue的企业新员工入职系统

作者主页:编程指南针 作者简介:Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容:Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 收藏点赞不迷路 关注作者有好处 文末获取源…

SICTF2023 WP

前言 新年前的最后一场比赛&#xff0c;感谢shenghuo2师傅提供的misc和密码的wp&#xff0c;把misc和密码ak了&#xff0c;太强了 web 兔年大吉 源码 <?php highlight_file(__FILE__); error_reporting(0);class Happy{private $cmd;private $content;public function _…

Registration Center

CAP●一致性(Consistency)&#xff1a;所有节点在同一时间具有相同的数据&#xff1b;●可用性(Availability) &#xff1a;保证每个请求不管成功或者失败都有响应&#xff1b;某个系统的某个节点挂了&#xff0c;但是并不影响系统的接受或者发出请求。●分隔容忍(Partition to…

python循环语句

Python循环语句 文章目录Python循环语句一、实验目的二、实验原理三、实验环境四、实验内容五、实验步骤1.While循环结构2.While无限循环3.For循环语法4.break语句和continue语句一、实验目的 掌握循环结构的语法 二、实验原理 Python中的循环语句有 for 和 while。 Python…

AcWing蓝桥杯AB组辅导课07、贪心

文章目录前言一、贪心模板题例题1&#xff1a;AcWing 104. 货仓选址&#xff08;贪心&#xff0c;简单&#xff0c;算法竞赛进阶指南&#xff09;分析题解&#xff1a;贪心思路例题例题1&#xff1a;AcWing 1055. 股票买卖 II&#xff08;贪心、状态机&#xff0c;简单&#xf…

[ESP][驱动]GT911 ESP系列驱动

GT911ForESP GT911在ESP系列上的驱动&#xff0c;基于IDF5.0&#xff0c;ESP32S3编写 本库使用面向对象思想编写&#xff0c;可创建多设备多实例 Github&#xff0c;Gitee同步更新&#xff0c;Gitee仅作为下载仓库&#xff0c;提交Issue和Pull request请到Github Github: h…

具体芯片的I2C_Adapter驱动分析

具体芯片的I2C_Adapter驱动分析 文章目录具体芯片的I2C_Adapter驱动分析参考资料&#xff1a;一、 I2C控制器内部结构1.1 通用的简化结构1.2 IMX6ULL的I2C控制器内部结构二、 I2C控制器操作方法三、 分析代码3.1 设备树3.2 驱动程序分析致谢参考资料&#xff1a; Linux内核真正…

03_筛选标记2.0版和3.0版FIND及ColorIndex

文章目录2.0版工作簿筛选标记筛选sheet标记取消筛选标记3.0版ColorIndex 下标代码特别鸣谢,大佬的分享FIND方法的使用2.0版 工作簿筛选标记 Option Explicit Sub 自动筛选()Dim Town As StringDim wsh As WorksheetCall 初始化 初始化表格状态Town InputBox("请输入街…

SLAM笔记——turtlebot传感器ekf实验实验

这里写目录标题实验内容实验准备msg数据类型给uwb和odom增加噪声robot_pose_ekf发布路径实验结果实验内容 本实验将在gazebo仿真环境中使用ekf进行传感器数据融合。本文使用turtlebot3进行实验&#xff0c;turtlebot本身会发布odom和imu。imu的误差可以在urdf文件中进行调整&a…

追梦之旅【数据结构篇】——对数据结构的认知 + 初识时间复杂度和空间复杂度~

详解C语言函数模块知识(下篇&#xff09;&#x1f60e;前言&#x1f64c;浅谈数据结构~&#x1f64c;1、什么是数据结构&#xff1f;(ˇˍˇ) 想&#xff5e;2、什么是算法&#xff1f;ˇˍˇ) 想&#xff5e;3、数据结构和算法的重要性&#x1f60a;4、如何才能学好数据结构呢…

初识 NodeJS(基于 Chrome V8 引擎的 JavaScript 运行时环境)

初识 NodeJS&#xff08;基于 Chrome V8 引擎的 JavaScript 运行时环境&#xff09;参考描述NodeJSNodeJS 可以做什么&#xff1f;特点用武之地获取检测运行JavaScript 运行时环境JavsScript 引擎浏览器中的 JavaScript 运行时环境Chrome 浏览器运行时环境NodeJS 中的 JavaScri…

【着色器实现海面效果_菲尼尔/Unlit/Transparent】

1.水体颜色 2.反射,水面波纹流动 3.折射、水底、水底透明度和折射 4.焦散,在水底接近岸边的水域 5.岸边泡沫,水花接近岸边的泡沫 6.水体运动,顶点动画 用灯光模式是Light Model :Unilt Render Type:Transparent 获取水面深度 利用这个节点,从深度图获取世界空间的位…

如何做流程图?这几个实用的制作流程图方法分享给你

说到流程图的制作&#xff0c;相信大家都并不陌生&#xff0c;在日常的工作和学习中&#xff0c;我们都会根据需求接触到各种各样的流程图&#xff0c;有时还要自己动手绘制流程图并使用&#xff0c;但你是否会因为不会绘制流程图而感到苦恼呢&#xff1f;没关系&#xff0c;今…

vue中利用ref实现更灵活的子向父传值

目录前言一&#xff0c;基础代码二&#xff0c;层次递进的讲解用法2.1 给子组件设置ref2.2 自定义事件2.3 给子组件设置一个自定义事件三&#xff0c;灵活性四&#xff0c;注意后记前言 目前我们熟知的子向父传值有两种方式&#xff1a; 一种是在父组件中定义函数&#xff0c;…

【AI】Windows配置GPU Cuda驱动和Pytorch框架

目录 1、Cuda驱动安装 1.1 查看显卡硬件 1.2 查看cuda版本 2、Annaconda python环境准备 2.1 创建pytorch_gpu 2.2 查看python版本 3、Pytorch和torchVsion软件安装 4、验证测试 在进行AI项目开发的时候&#xff0c;经常要在GPU环境中运行代码&#xff0c;对于没有配置…

动手深度学习-pytorch线性代数

标量简单操作长度向量简单操作长度其他操作矩阵简单操作乘法&#xff08;矩阵*向量&#xff09;乘法&#xff08;矩阵*矩阵&#xff09;范数取决于如何衡量b和c的长度常见范数矩阵范数&#xff1a;最小的满足的上面公式的值Frobenius范数特殊矩阵对称和反对称正定正交矩阵置换矩…