我們知道
spring secuity是控制URL的訪問權(quán)限的,那么spring secuity是怎樣攔截匹配URL,我們先看一個接口RequestMatcher
匹配HttpServletRequest的簡單策略接口RequestMatcher,其下定義了matches方法,如果返回是true表示提供的請求與提供的匹配規(guī)則匹配,如果返回的是false則不匹配。

RequestMatcher其實現(xiàn)類:
- AntPathRequestMatcher:重點
- MvcRequestMatcher:重點
- RegexRequestMatcher: 根據(jù)正則模式進行匹配
- 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將預(yù)先定義的ant風格與URL進行比較(一個HttpServletRequest的servletPath + pathInfo)。 查詢字符串網(wǎng)址被忽略,匹配是否區(qū)分大小寫具體取決于傳遞給構(gòu)造函數(shù)的參數(shù)(詳細可查看AntPathRequestMatcher的三個入?yún)⒌臉?gòu)造函數(shù))
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.
使用/** 或 ** 的模式值被視為通用匹配,這將匹配任何請求。 以/** 結(jié)尾的模式(并且沒有其他通配符)比如 /aaa/** 的模式將匹配/aaa,/aaa/和任何子目錄,例如/aaa/bbb/ccc。
所謂Apache Ant的樣式路徑,有三種通配符的匹配方式
- ? 匹配任意單個字符
- 匹配0或者任意數(shù)量的字符
- ** 匹配0或者更多的目錄
做url匹配的時候,不需要加上context path,只匹配servletPath + pathInfo,關(guān)于context path,servletPath,pathInfo的講解在第一節(jié)已經(jīng)講解過了,想了解詳細可以去查看。
示列
- 定義依賴
定義相關(guān)的依賴及jetty插件,設(shè)置了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>
- 定義系統(tǒng)啟動類:
定義了servletPath為/v1
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
//系統(tǒng)啟動的時候的根類
@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";
}
}
- 權(quán)限用戶名密碼的具體配置
@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請求都不需要權(quán)限認證
//http.authorizeRequests().antMatchers(HttpMethod.GET).access("permitAll");
//對/hello 進行匹配,不管HTTP METHOD是什么
//http.authorizeRequests().antMatchers("/v1/hello").hasRole("USER");
//匹配/hello,且http method是POST,需要權(quán)限認證
//http.authorizeRequests().antMatchers(HttpMethod.POST, "/v1/world").hasRole("USER");
//匹配 /hello,且http method是GET,不需要權(quán)限認證
//http.authorizeRequests().antMatchers(HttpMethod.GET, "/v1/hello").access("permitAll");
//匹配/admin,并且http method不管是什么,需要admin權(quán)限
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();
}
}
- 驗證
發(fā)現(xiàn)雖然我們配置了context path,但是在權(quán)限配置中是不需要配置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路徑這一點是非常重要的。 這意味著如果您已經(jīng)將任何servlet映射到以“/String”開頭,則還應(yīng)該指定#setServletPath(String)屬性以區(qū)分相關(guān)映射匹配。
MvcRequestMatcher是根據(jù)server_path來匹配的。
示列
配置類和上面都差不多,不一樣的是下面的類,
- 定義系統(tǒng)啟動類:
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
//系統(tǒng)啟動的時候的根類
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{WebAppConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
@Override
protected String[] getServletMappings() {
return new String[]{"/v1/*","/v2/*"};
}
}
- 權(quán)限用戶名密碼的具體配置
@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方式,需要權(quán)限認證之后才能訪問
//http.authorizeRequests().mvcMatchers(HttpMethod.POST,"hello").hasRole("USER");
//我們配置了servletPath,多個servletPath的時候這種配置也能進行url訪問的更加細粒度化,
//http://localhost:8001/web/v1/hello不需要權(quán)限認證
//http://localhost:8001/web/v2/hello需要權(quán)限認證
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();
}
}
總結(jié)
servletPath配置的三種模式,
第一種配置方式 servlet Path 為空
第二種配置方式 servlet Path /aaa, /aaa/bbb/
第三種配置方式servlet Path /.do, /.action
這三種情況只有第二種需要去配置servletPath,其他二種都不需要。具體配置的時候自己去調(diào)試一下。
MvcRequestMatcher和AntPathRequestMatcher的區(qū)別:
如果配置了servlet_path,那么AntPathRequestMatcher在配置antMatchers的時候需要加上servlet_path,比如如果加了
@Override
protected String[] getServletMappings() {
return new String[]{"/v1/*","/v2/*"};
}
那么antMatchers("/v1/hello"),而mvcMatchers不需要加上servlet_path
參考資料
AnyRequestMatcher
匹配所有的請求
示列
- 權(quán)限用戶名密碼的具體配置
@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();
//匹配所有請求都不需要權(quán)限控制
//http.authorizeRequests().anyRequest().access("permitAll");
//匹配所有的請求都需要USER權(quán)限
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都需要權(quán)限認證,但是不管是什么權(quán)限,不管是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
示列
- 權(quán)限用戶名密碼的具體配置
spring secuity提供了requestMatchers入口讓我們自定義自己的RequestMatcher實現(xiàn)類
@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");
//參數(shù)中type等于1的就不做權(quán)限認證,
// 當訪問的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();
}
}
參考資料
總結(jié)
- 做url匹配的時候,不需要加上
context_path - 順序很重要,適合的url先匹配到的先應(yīng)用上了。
- 我們在做權(quán)限控制的時候都在我們自己定義的
WebSecurityConfig的configure(HttpSecurity http)方法中去定義的,WebSecurityConfig繼承WebSecurityConfigurerAdapter
我們看看WebSecurityConfigurerAdapter類中的configure默認的實現(xiàn)是什么:
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的方式進行認證。