Spring Security配置权限:应用限制

举报
别团等shy哥发育 发表于 2023/01/09 18:51:46 2023/01/09
【摘要】 @[toc](Spring Security配置权限:应用限制) 1、匹配器说明要选择应用授权配置的请求,可以使用匹配器方法。Spring Security提供了3种类型的匹配方法。MVC匹配器:将MVC表达式用于路径以便选择端点。Ant匹配器:将Ant表达式用于路径以便选择端点。regex匹配器:将正则表达式(regex)用于路径以便选择端点。 2、使用匹配器方法选择端点  首先看一个简单...

@[toc](Spring Security配置权限:应用限制)

1、匹配器说明

要选择应用授权配置的请求,可以使用匹配器方法。Spring Security提供了3种类型的匹配方法。

  • MVC匹配器:将MVC表达式用于路径以便选择端点。
  • Ant匹配器:将Ant表达式用于路径以便选择端点。
  • regex匹配器:将正则表达式(regex)用于路径以便选择端点。

2、使用匹配器方法选择端点

  首先看一个简单的示例,我们要创建一个暴露两个端点的应用程序,这两个端点是/hello和/ciao。我们希望确保只有ADMIN角色的用户才能调用/hello端点。类似地,只有MANAGER角色的用户才能调用/ciao端点。

2.1 控制器类的定义

@RestController
public class HelloController {

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

    @GetMapping("/ciao")
    public String ciao() {
        return "Ciao!";
    }

}

2.2 配置类的定义

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {


    @Override
    @Bean
    public UserDetailsService userDetailsService() {
        var manager = new InMemoryUserDetailsManager();

        var user1 = User.withUsername("john")
                .password("12345")
                .roles("ADMIN")
                .build();

        var 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.httpBasic();

        http.authorizeRequests()
            	//只有当用户具有ADMIN角色时才能调用路径/hello
                .mvcMatchers("/hello").hasRole("ADMIN")
            	//只有当用户具有Manager角色时才能调用路径/ciao
                .mvcMatchers("/ciao").hasRole("MANAGER")
    }

}

  该配置类中声明了一个InMemoryUserDetailsManager作为UserDetailsService实例,并且添加了两个具有不同角色的用户。用户John具有ADMIN角色,而Jane具有MANAGER角色。为了指定只有具有ADMIN角色的用户才能在授权请求时调用端点/hello,需要使用mvcMatchers()方法。

2.3 测试

用户John调用端点/hello

image-20220220171217276

用户Jane调用端点/hello

image-20220220171307837

用户Jane调用端点/ciao

image-20220220171339243

用户John调用端点/ciao

image-20220220171412676

  上面的配置类还有点瑕疵,这样默认除过/hello和/ciao之外的其他路径都是可以被访问的,即使是未经身份验证的用户。

2.4 补充:让所有经过身份验证的用户都可以访问其他请求

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

    http.authorizeRequests()
            .mvcMatchers("/hello").hasRole("ADMIN")
            .mvcMatchers("/ciao").hasRole("MANAGER")
            .anyRequest().authenticated();
}

  ==当使用匹配器指向请求时,规则的顺序应该是从特殊到一般。这就是为什么在更具体的匹配器方法(例如mvcMatches())之前不能调用anyRequest()方法的原因。==

3、三种匹配器

3.1 使用MVC匹配器选择用于授权的请求

  这个匹配器使用标准的MVC语法指向路径。改语法与我们在编写带有@RequestMapping、@GetMapping、@PostMapping等注解的端点映射时所使用的语法相同。可声明用于MVC匹配器的两种方法如下。

  • mvcMatchers(HttpMethod method,String… patterns):允许指定要应用限制的HTTP方法和路径。如果希望对同一路径的不同HTTP方法应用不同的限制,此方法非常有用。
  • mvcMatchers(String… patterns):如果只需应用基于路径的授权限制,那么这个方法使用起来会更加简单。这些限制可以自动应用于该路径一起使用的任何HTTP方法。

  在默认情况下,Spring Security应用了防止跨站点请求伪造(CSRF)的保护。这里为了更加简单,并且能够调用所有端点,包括那些POST、PUT、或DELETE公开的端点,需要在onfigure()方法中禁用CSRF保护。

   注意:这里禁用CSRF保护只是为了更好的专注于mvc匹配器方法,实际开发中不要这样做。

http.csrf().disable();

3.1.1 为其配置授权的4个端点的定义

  • /a使用HTTP方法GET
  • /a使用HTTP方法POST
  • /a/b使用HTTP方法GET
  • /a/b/c使用HTTP方法GET
@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。

3.1.2 UserDetailsService定义

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {

    @Override
    @Bean
    public UserDetailsService userDetailsService() {
        var manager = new InMemoryUserDetailsManager();

        var user1 = User.withUsername("john")
                .password("12345")
                .roles("ADMIN")
                .build();

        var user2 = User.withUsername("bill")
                .password("12345")
                .roles("MANAGER")
                .build();

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

        return manager;
    }

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

   //...
}

3.1.3 用于第一个场景/a的授权配置

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {

    @Override
    @Bean
    public UserDetailsService userDetailsService() {
        var manager = new InMemoryUserDetailsManager();

        var user1 = User.withUsername("john")
                .password("12345")
                .roles("ADMIN")
                .build();

        var user2 = User.withUsername("bill")
                .password("12345")
                .roles("MANAGER")
                .build();

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

        return manager;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
	
    //用于第一个场景/a的授权配置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic();

        http.authorizeRequests()
            //对于使用HTTP Get方法调用的路径/a的请求,应用程序需要对用户进行身份验证
                .mvcMatchers(HttpMethod.GET, "/a").authenticated()
            //允许任何人通过HTTP POST方法调用路径/a的请求
                .mvcMatchers(HttpMethod.POST, "/a").permitAll()
            //拒绝对其他任何路径的其他任何请求
                .anyRequest().denyAll();

        //暂时禁用CSRF保护,以启用使用HTTP POST方法对/a路径的调用
        http.csrf().disable();
    }
}

3.1.4 调用测试

使用POST请求调用路径/a而不进行身份验证

image-20220221185141562

使用GET请求调用路径/a而不进行身份验证时

image-20220221185238322

  可以看到,此时是不能访问该路径的,因为我们在配置中让其必须身份验证之后才能访问Get请求的/a路径。

  使用有效的身份进行验证

image-20220221185432099

但是用户John被禁止调用路径/a/b,所以用他的凭据对这个调用进行身份验证会生成403 Forbidden

image-20220221185542278

3.1.5 其他配置

  如果希望确保相同的规则适用于以/a/b开始的所有路径请求。为了实现这一点,需要使用**操作符(Spring MVC从Ant中借用了路径匹配语法)。

  对多个路径的配置类进行的更改

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

        http.authorizeRequests()
                .mvcMatchers( "/a/b/**").authenticated()
                .anyRequest().permitAll();

        http.csrf().disable();
    }

用于使用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

3.2 使用Ant匹配器选择用于授权的请求

使用Ant匹配器的3种方法如下:

  • antMatchers(HttpMethod method,String patterns):允许指定应用限制的HTTP方法和指向路径的Ant模式。如果希望对同一组路径的不同HTTP方法应用不同的限制,则此方法非常有用。
  • antMatchers(String patterns):如果只需要应用基于路径的授权限制,则这一方法使用起来更加简单。这些限制会自动适用于任何HTTP方法。
  • antMatchers(HttpMethod method):他等同于antMatchers(httpMethod,"/**"),允许特定的HTTP方法,而不考虑路径。

MVC匹配器与Ant匹配器哪个好用?

MVC匹配器指的是Spring应用程序如何理解将请求与控制器相匹配。有时多个路径可以被Spring解析为匹配相同的操作。

例如:如果再路径之后添加一个/,那么指向相同操作的任何路径(例如/hello)都可以由Spring解析。在这种情况下,/hello和/hello/会调用相同的方法。如果使用MVC匹配器并且为/hello路径配置安全性,则它会自动使用相同的规则保护/hello/路径/。这会产生巨大的影响!开发人员如果不知道这一点,并且使用Ant匹配器,则可能会在毫不知情的情况下让路径不受保护。

下面以一个示例说明。

3.2.1 控制器类种/hello端点的定义

@RestController
public class HelloController {

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

3.2.2 使用MVC匹配器的配置类

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

        http.authorizeRequests()
                .mvcMatchers( "/hello").authenticated();
                //.antMatchers( "/hello").authenticated();
    }

3.2.3 mvc匹配器测试

http://localhost:8080/hello

image-20220221191440156

使用/hello/结尾的路径调用端点

http://localhost:8080/hello/

image-20220221191522455

调用http://localhost:8080/hello并进行身份验证。

image-20220221191635772

调用http://localhost:8080/hello/并进行身份验证

image-20220221191706460

   结果都是我们所期待的。但是如果使用Ant匹配器结果就会改变。实际上,Ant匹配器会为模式精确地应用给定的Ant表达式,但它无法触及Spring MVC的精细功能。在本示例中,/hello不会作为Ant表达式应用于/hello路径。如果还想保护/hello/路径,则必须单独添加它,或者编写一个匹配它的Ant表达式。

3.2.4 使用Ant匹配器的配置类

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

        http.authorizeRequests()
                .antMatchers( "/hello").authenticated();
    }

3.2.4 Ant匹配器测试

不进行身份验证访问http://localhost:8080/hello

image-20220221192115375

不进行身份验证访问http://localhost:8080/hello/

image-20220221192136780

  注意,此时居然访问成功了,所以Ant匹配器的粒度比较细,会出现我们不期待的结果。还需要为该路径再配置限制,所以平时使用MVC匹配器就可以了。

3.3 使用正则表达式匹配器选择用于授权的请求

  可以使用正则表达式表示字符串的任何格式,因此它们提供了无限的可能性。但缺点是难以阅读,即使应用于简单的场景也是如此。

实现正则表达式匹配器的两种方法如下:

  • regexMatchers(HttpMethod method,String regex):同时指定应用限制的HTTP方法和指向路径的正则表达式。如果希望对同一组路径的不同HTTP方法应用不同的限制,则此方法非常有用。

  • regexMatchers(String regex):如果只需要应用基于路径的授权限制,该方法使用起来会更加简单。这些限制将会自动适用于任何HTTP方法。

    以一个简单的示例说明

3.3.1 控制器类端点的定义

@RestController
public class VideoController {

    @GetMapping("/video/{country}/{language}")
    public String video(@PathVariable String country,
                        @PathVariable String language) {
        return "Video allowed for " + country + " " + language;
    }
}

3.3.2 使用正则表达式匹配器的配置类

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {

    @Override
    @Bean
    public UserDetailsService userDetailsService() {
        var uds = new InMemoryUserDetailsManager();

        var u1 = User.withUsername("john")
                     .password("12345")
                     .authorities("read")
                .build();

        var u2 = User.withUsername("jane")
                    .password("12345")
                    .authorities("read", "premium")
                .build();

        uds.createUser(u1);
        uds.createUser(u2);

        return uds;
    }

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

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

        http.authorizeRequests()
            //这里使用正则表达式来匹配其用户只需要通过身份验证的路径
                .regexMatchers(".*/(us|uk|ca)+/(en|fr).*")
                    .authenticated()
            //配置用户需要具有高级访问权限的其他路径
            .anyRequest().hasAuthority("premium");
    }
}

3.3.3 测试

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

  调用/video端点并验证US地区和English语言的用户John,如下所示:

image-20220221193454867

调用端点/video端点并验证用户John的FR区域和French语言,如下所示:

image-20220221193544706

下面使用用户Jane调用,该用户具有高级权限

http://localhost:8080/video/us/en

image-20220221193642643

http://localhost:8080/video/fr/fr

image-20220221193716444

  正则表达式是功能强大的工具,可以使用它们指向任何指定需求的路径。但是由于正则表达式难以阅读,并且可能变得很长,因此它们是我们的最后选择。只有当MVC和Ant表达式不能为所面临的问题提供解决方案时,才使用它们。

4、总结

  • 在实际场景中,经常会对不同的请求应用不同的授权规则。
  • 可以指定基于路径和HTTP方法配置授权规则的请求。为此,需要使用匹配器方法,它有3种风格:MVC、Ant和正则表达式。
  • MVC和Ant匹配器是类似的,通常可以选择其中一个选项指向应用授权限制的请求。
  • 当需求太复杂而无法使用Ant或MVC表达式解决问题时,则可以使用更强大的正则表达式实现它们。
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。