spring secuity拦截匹配URL权限(RequestMatcher接口详解)

原文格式清晰,转载自:https://www.jianshu.com/p/4f9ee6007213

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

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

匹配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. ? 匹配任意单个字符
    • 匹配0或者任意数量的字符
  2. ** 匹配0或者更多的目录

做url匹配的时候,不需要加上context path,只匹配servletPath + pathInfo,关于context pathservletPathpathInfo的讲解在第一节已经讲解过了,想了解详细可以去查看。

示列

  • 定义依赖
    定义相关的依赖及jetty插件,设置了context path/web
<name>secuity-quickstart-url01</name>
    <artifactId>secuity-quickstart-url01</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.3.13.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>4.2.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>4.2.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.2</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>


    <build>
        <finalName>secuity-quickstart-url01</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.0.0</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-maven-plugin</artifactId>
                <version>9.4.3.v20170317</version>
                <configuration>
                    <httpConnector>
                        <port>8001</port>
                    </httpConnector>
                    <webApp>
                        <contextPath>/web</contextPath>
                    </webApp>
                </configuration>
            </plugin>
        </plugins>
    </build>
  • 定义系统启动类:

定义了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

参考资料

secuity-quickstart-url01

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

参考资料

secuity-quickstart-url02

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

参考资料

secuity-quickstart-url03

自定义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();
    }
}

参考资料

secuity-quickstart-url03

总结

  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的方式进行认证。

参考资料

官方文档
Spring Security 从入门到进阶系列教程

 

作为一个 AI,我没有个人观点和喜好。但是,我可以给你一些建议。 Spring Security 是一个功能强大的安全框架,提供了许多内置的安全特性和功能,比如身份验证、授权、角色管理等。它可以帮助开发者更方便地实现应用程序的安全保护。 拦截器是 Spring MVC 框架中的一部分,用于拦截和处理 HTTP 请求。它可以在请求的前后执行一些自定义的逻辑,比如权限校验、日志记录等。 使用 Spring Security 或者拦截器,都可以实现一定程度的安全保护。选择使用哪种方式取决于你的具体需求和项目规模。以下是一些考虑因素: 1. 功能需求:如果你的应用需要复杂的身份验证、授权和角色管理功能,那么 Spring Security 是一个更合适的选择。它提供了许多内置的特性,可以方便地实现这些功能。 2. 简易性:拦截器相对来说比较简单易用,特别适合一些小型项目或者对安全要求不高的项目。如果你只需要简单地校验请求参数或者记录请求日志,拦截器可能是一个更轻量级的选择。 3. 生态系统支持:Spring Security 是一个成熟的框架,有庞大的社区支持和各种扩展库。如果你需要集成其他安全相关的库或者服务,Spring Security 可能提供更多的选择。 综上所述,选择使用 Spring Security 还是拦截器取决于你的具体需求和项目规模。你可以根据项目的实际情况来做出决策,并在需要时结合两者使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值