URL匹配RequestMatcher接口详解

我们知道spring secuity是控制URL的访问权限的,那么spring secuity是怎样拦截匹配URL,我们先看一个接口RequestMatcher

匹配HttpServletRequest的简单策略接口RequestMatcher,其下定义了matches方法,如果返回是true表示提供的请求与提供的匹配规则匹配,如果返回的是false则不匹配。

img

匹配HttpServletRequest的简单策略接口

RequestMatcher其实现类:

  • AntPathRequestMatcher:重点
  • MvcRequestMatcher:重点
  • RegexRequestMatcher: 根据正则模式进行匹配
  • AnyRequestMatcher

AntPathRequestMatcher

其javadoc描述如下:

Matcher which compares a pre-defined ant-style pattern against the URL (servletPath + pathInfo) of an HttpServletRequest. The query string of the URL is ignored and matching is case-insensitive or case-sensitive depending on the arguments passed into the constructor.
Matcher将预先定义的ant风格与URL进行比较(一个HttpServletRequest的servletPath + pathInfo)。 查询字符串网址被忽略,匹配是否区分大小写具体取决于传递给构造函数的参数(详细可查看AntPathRequestMatcher的三个入参的构造函数)

Using a pattern value of /** or ** is treated as a universal match, which will match any request. Patterns which end with /** (and have no other wildcards) are optimized by using a substring match — a pattern of /aaa/** will match /aaa, /aaa/ and any sub-directories, such as /aaa/bbb/ccc.
使用/** 或 ** 的模式值被视为通用匹配,这将匹配任何请求。 以/** 结尾的模式(并且没有其他通配符)比如 /aaa/** 的模式将匹配/aaa,/aaa/和任何子目录,例如/aaa/bbb/ccc。

所谓Apache Ant的样式路径,有三种通配符的匹配方式

  1. ? 匹配任意单个字符
  2. * 匹配0或者任意数量的字符
  3. ** 匹配0或者更多的目录

做url匹配的时候,不需要加上context path,只匹配servletPath + pathInfo

示列

  • 定义系统启动类:

定义了servletPath/v1

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    //系统启动的时候的根类
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{WebAppConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return null;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/v1/*"};
    }

}
  • web入口类
@EnableWebMvc
@EnableWebSecurity
@ComponentScan("com.zhihao.miao.secuity")
public class WebAppConfig extends WebMvcConfigurerAdapter {

    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}
  • spring security配置类
public class WebAppSecurityInitializer extends AbstractSecurityWebApplicationInitializer {

    protected String getDispatcherWebApplicationContextSuffix() {
        return AbstractDispatcherServletInitializer.DEFAULT_SERVLET_NAME;
    }
}
  • 具体的Controller
@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        return "hello spring secuity";
    }

    @PostMapping("/world")
    public String world(){
        return "world spring secuity";
    }

    @GetMapping("/home")
    public String home(){
        return "home spring secuity";
    }

    @GetMapping("/admin")
    public String admin(){
        return "admin spring secuity";
    }
}
  • 权限用户名密码的具体配置
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("zhangsan").password("123456").roles("GUEST");
        auth.inMemoryAuthentication().withUser("zhihao.miao").password("123456").roles("USER");
        auth.inMemoryAuthentication().withUser("lisi").password("12345678").roles("USER", "ADMIN");
    }

    protected void configure(HttpSecurity http) throws Exception {

        //post请求默认的都开启了csrf的模式,所有post请求都必须带有token之类的验证信息才可以进入登陆页面,这边是禁用csrf模式
        http.csrf().disable();

        //表示所有的get请求都不需要权限认证
        //http.authorizeRequests().antMatchers(HttpMethod.GET).access("permitAll");

        //对/hello 进行匹配,不管HTTP METHOD是什么
        //http.authorizeRequests().antMatchers("/v1/hello").hasRole("USER");

        //匹配/hello,且http method是POST,需要权限认证
        //http.authorizeRequests().antMatchers(HttpMethod.POST, "/v1/world").hasRole("USER");

        //匹配 /hello,且http method是GET,不需要权限认证
        //http.authorizeRequests().antMatchers(HttpMethod.GET, "/v1/hello").access("permitAll");

        //匹配/admin,并且http method不管是什么,需要admin权限
        http.authorizeRequests().antMatchers("/v1/admin").hasRole("ADMIN");


        http.authorizeRequests().antMatchers("/**/*.html").access("permitAll");
        http.authorizeRequests().antMatchers("/**/*.css").access("permitAll");
        http.authorizeRequests().antMatchers("/**/*.js").access("permitAll");
        http.authorizeRequests().antMatchers("/**/*.png").access("permitAll");

        http.authorizeRequests().anyRequest().authenticated();

        http.formLogin();
    }
}
  • 验证

发现虽然我们配置了context path,但是在权限配置中是不需要配置context path,servletPath需要配置

http://localhost:8001/web/v1/admin
http://localhost:8001/web/v1/hello

MvcRequestMatcher

其javadoc描述如下:

A RequestMatcher that uses Spring MVC’s HandlerMappingIntrospector to match the path and extract variables.
RequestMatcher使用Spring MVC的HandlerMappingIntrospector来匹配路径并提取变量。

It is important to understand that Spring MVC’s matching is relative to the servlet path. This means if you have mapped any servlet to a path that starts with “/” and is greater than one, you should also specify the #setServletPath(String) attribute to differentiate mappings.
对我们而言了解Spring MVC的匹配是相对于servlet路径这一点是非常重要的。 这意味着如果您已经将任何servlet映射到以“/String”开头,则还应该指定#setServletPath(String)属性以区分相关映射匹配。

MvcRequestMatcher是根据server_path来匹配的。

示列

配置类和上面都差不多,不一样的是下面的类,

  • 定义系统启动类:
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    //系统启动的时候的根类
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{WebAppConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return null;
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/v1/*","/v2/*"};
    }

}
  • 权限用户名密码的具体配置
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("zhangsan").password("123456").roles("GUEST");
        auth.inMemoryAuthentication().withUser("zhihao.miao").password("123456").roles("USER");
        auth.inMemoryAuthentication().withUser("lisi").password("12345678").roles("USER", "ADMIN");
    }

    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable();

        //http://localhost:8001/web/v1/hello get方式 可以访问
        //http.authorizeRequests().mvcMatchers(HttpMethod.GET,"hello").access("permitAll");
        //http://localhost:8001/web/v1/hello post方式,需要权限认证之后才能访问
        //http.authorizeRequests().mvcMatchers(HttpMethod.POST,"hello").hasRole("USER");

        //我们配置了servletPath,多个servletPath的时候这种配置也能进行url访问的更加细粒度化,
        //http://localhost:8001/web/v1/hello不需要权限认证
        //http://localhost:8001/web/v2/hello需要权限认证
        http.authorizeRequests().mvcMatchers(HttpMethod.GET,"hello").servletPath("/v1").access("permitAll");
        http.authorizeRequests().mvcMatchers(HttpMethod.GET,"hello").servletPath("/v2").access("hasRole('USER')");

        http.authorizeRequests().antMatchers("/**/*.html").access("permitAll");
        http.authorizeRequests().antMatchers("/**/*.css").access("permitAll");
        http.authorizeRequests().antMatchers("/**/*.js").access("permitAll");
        http.authorizeRequests().antMatchers("/**/*.png").access("permitAll");

        http.authorizeRequests().anyRequest().authenticated();

        http.formLogin();
    }
}

总结

servletPath配置的三种模式,
第一种配置方式 servlet Path 为空
第二种配置方式 servlet Path /aaa, /aaa/bbb/
第三种配置方式servlet Path /.do, /.action

这三种情况只有第二种需要去配置servletPath,其他二种都不需要。具体配置的时候自己去调试一下。

MvcRequestMatcher和AntPathRequestMatcher的区别:
如果配置了servlet_path,那么AntPathRequestMatcher在配置antMatchers的时候需要加上servlet_path,比如如果加了

@Override
protected String[] getServletMappings() {
    return new String[]{"/v1/*","/v2/*"};
}

那么antMatchers(“/v1/hello”)需要,而mvcMatchers不需要 加上servlet_path

AnyRequestMatcher

匹配所有的请求

示列

  • 权限用户名密码的具体配置
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("zhangsan").password("123456").roles("GUEST");
        auth.inMemoryAuthentication().withUser("zhihao.miao").password("123456").roles("USER");
        auth.inMemoryAuthentication().withUser("lisi").password("12345678").roles("USER", "ADMIN");
    }

    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable();

        //匹配所有请求都不需要权限控制
        //http.authorizeRequests().anyRequest().access("permitAll");

        //匹配所有的请求都需要USER权限
        http.authorizeRequests().anyRequest().hasRole("USER");


        http.authorizeRequests().antMatchers("/**/*.html").access("permitAll");
        http.authorizeRequests().antMatchers("/**/*.css").access("permitAll");
        http.authorizeRequests().antMatchers("/**/*.js").access("permitAll");
        http.authorizeRequests().antMatchers("/**/*.png").access("permitAll");

        //我们平时写的authenticated,指的是上面配置没有匹配到的url都需要权限认证,但是不管是什么权限,不管是USER,GUEST,ADMIN都可以
        http.authorizeRequests().anyRequest().authenticated();

        http.formLogin();
    }
}
  • 验证
http://localhost:8001/web/v1/hello
http://localhost:8001/web/v1/home
http://localhost:8001/web/v1/admin

自定义RequestMatcher

示列

  • 权限用户名密码的具体配置

spring secuity提供了requestMatchers入口让我们自定义自己的RequestMatcher实现类

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("zhangsan").password("123456").roles("GUEST");
        auth.inMemoryAuthentication().withUser("zhihao.miao").password("123456").roles("USER");
        auth.inMemoryAuthentication().withUser("lisi").password("12345678").roles("USER", "ADMIN");
    }

    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable();

        //spring secuity提供了requestMatchers接口,等价于http.authorizeRequests().anyRequest().access("permitAll");
        //http.authorizeRequests().requestMatchers(AnyRequestMatcher.INSTANCE).access("permitAll");

        //参数中type等于1的就不做权限认证,
        // 当访问的url地址为http://localhost:8001/web/v1/hello?type=1,因为type值是1,所以匹配
        http.authorizeRequests().requestMatchers((RequestMatcher) request -> "1".equals(request.getParameter("type"))).access("permitAll");

        http.authorizeRequests().antMatchers("/**/*.html").access("permitAll");
        http.authorizeRequests().antMatchers("/**/*.css").access("permitAll");
        http.authorizeRequests().antMatchers("/**/*.js").access("permitAll");
        http.authorizeRequests().antMatchers("/**/*.png").access("permitAll");

        http.authorizeRequests().anyRequest().authenticated();

        http.formLogin();
    }
}

总结

  1. 做url匹配的时候,不需要加上context_path
  2. 顺序很重要,适合的url先匹配到的先应用上了。
  3. 我们在做权限控制的时候都在我们自己定义的WebSecurityConfigconfigure(HttpSecurity http)方法中去定义的,WebSecurityConfig继承WebSecurityConfigurerAdapter

我们看看WebSecurityConfigurerAdapter类中的configure默认的实现是什么:

    protected void configure(HttpSecurity http) throws Exception {
        logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");

        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin().and()
            .httpBasic();
    }

我们看到了任何请求需要认证以表单的形式,以httpbasic的方式进行认证

Leave a Reply

Your email address will not be published. Required fields are marked *

lWoHvYe 无悔,专一