Spring Security 是一个功能强大且高度可定制的身份验证和访问控制框架。在 Spring Security 中,可以通过配置方法来控制访问权限。认证是实现授权的前提和基础,在执行授权操作前需要明确目标用户,只有明确目标用户才能明确它所具备的角色和权限。Spring Security 中所采用的授权模型也是由用户、角色和权限组成的。
Spring Security 实现配置方法控制访问权限很简单,只需要使用基于 HttpSecurity 对象提供的一组工具方法就能实现复杂场景下的访问控制。
Spring Security 中常见的配置方法及其作用:
配置方法 | 作用 |
---|---|
anonymous() | 允许匿名访问。 |
anyRequest() | 匹配所有的请求。 |
authenticated() | 所有匹配的 URL 都需要被认证才能访问。 |
permitAll() | 无条件允许一切用户访问。 |
hasAuthority(String) | 允许具有特定权限的用户访问。 |
hasAnyAuthority(String) | 允许具有任一权限的用户访问。 |
hasRole(String) | 允许具有特定角色的用户访问。 |
hasAnyRole(String) | 允许具有任一角色的用户访问。 |
hasIpAddress() | 允许来自特定 IP 地址的用户访问。 |
denyAll() | 无条件禁止一切访问。 |
access() | 该方法允许开发人员传入一个表达式进行更加细颗粒度的权限控制。 |
mvcMatchers(String) | 通过 MVC 匹配器,匹配 HTTP 端点的访问路径。 |
antMatchers(String) | 通过 Ant 匹配器,匹配 HTTP 端点的访问路径。 |
regexMatchers(String) | 通过正则表达式匹配器,匹配 HTTP 端点的访问路径。 |
综合实例:
Spring Security 的核心配置类,WebSecurityConfig 类(Spring Security 配置类),并添加 @EnableWebSecurity 注解和继承 WebSecurityConfigurerAdapter 类。
java">/*** Spring Security 配置类* @author pan_junbiao**/
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{//公开权限的路径(白名单)private static final String[] WHITE_LIST = {"/admin/getLoginAdmin","/js/**","/captcha.jpg"};@Overrideprotected void configure(HttpSecurity http) throws Exception{http.authorizeRequests() //返回一个URL拦截注册器.antMatchers(WHITE_LIST).permitAll() //公开其权限(公开的权限必须放在最上面).antMatchers("/account/**").hasAnyRole("SUPER_ADMIN","GENERAL_ADMIN") //设置授权角色(普通管理员).antMatchers("/log/**").hasAnyRole("SUPER_ADMIN","GENERAL_ADMIN") //设置授权角色(普通管理员).antMatchers("/**").hasRole("SUPER_ADMIN") //设置授权角色(超级管理员).anyRequest() //匹配所有的请求.authenticated() //所有匹配的URL都需要被认证才能访问.and() //结束当前标签,让上下文回到 HttpSecurity.formLogin() //启动表单认证.loginPage("/myLogin.html") //自定义登录页面.loginProcessingUrl("/auth/form") //指定处理登录请求路径.permitAll() //使登录页面不设限访问.and().csrf().disable(); //关闭CSRF的防御功能}
}
1、Spring Security 中的权限和角色
在权限和角色的概念中,两者都是用于管理系统中用户访问权限的重要机制,但它们有着不同的含义和用途。角色代表用户在系统中的身份或职位。它是权限的一种组合方式,通常用于描述用户在组织或系统中的职责和权限级别。权限表示用户可以执行的具体操作或访问的具体资源。它是更细粒度的控制机制,用于描述用户在系统中的具体操作权限。
Spring Security 的核心配置类,WebSecurityConfig 类中,configure(HttpSecurity http) 方法,如下所示。
java">@Override
protected void configure(HttpSecurity http) throws Exception
{http.authorizeRequests() //返回一个URL拦截注册器.anyRequest() //匹配所有的请求.authenticated() //所有匹配的URL都需要被认证才能访问.and() //结束当前标签,让上下文回到 HttpSecurity.formLogin() //启动表单认证.and().httpBasic();
}
1.1 基于权限进行访问配置
基于权限进行访问配置是确保系统安全性的重要手段。权限表示用户可以执行的具体操作或访问的具体资源。
Spring Security 提供了一组权限的配置方法,如下:
- hasAuthority(String):允许具有特定权限的用户访问。
- hasAnyAuthority(String):允许具有任一权限的用户访问。
可以使用上述方法来判断用户是否具备对应的访问权限。
【示例】使用 hasAuthority(String) 方法,配置具有特定权限的用户访问。
java">@Override
protected void configure(HttpSecurity http) throws Exception
{http.authorizeRequests() //返回一个URL拦截注册器.antMatchers("/user/**").hasAuthority("user:info"); //配置权限:允许具有特定权限的用户访问
}
【示例】使用 hasAnyAuthority(String) 方法,配置具有任一权限的用户访问。
java">@Override
protected void configure(HttpSecurity http) throws Exception
{http.authorizeRequests() //返回一个URL拦截注册器.antMatchers("/user/**").hasAnyAuthority("user:add","user:edit","user:delete"); //配置权限列表:允许具有任一权限的用户访问
}
1.2 基于角色进行访问配置
角色是权限的一种组合方式,用于描述用户在系统中的身份或职责,角色可以包含多个权限,但权限不一定属于某个角色(可以独立存在)。
Spring Security 提供了一组角色的配置方法,如下:
- hasRole(String):允许具有特定角色的用户访问。
- hasAnyRole(String):允许具有任一角色的用户访问。
可以使用上述方法来判断用户是否具备对应的访问角色。
【示例】使用 hasRole(String) 方法,配置具有特定角色的用户访问。
java">@Override
protected void configure(HttpSecurity http) throws Exception
{http.authorizeRequests() //返回一个URL拦截注册器.antMatchers("/user/**").hasRole("USER_INFO"); //配置角色:允许具有特定角色的用户访问
}
【示例】使用 hasAnyRole(String) 方法,配置具有任一角色的用户访问。
java">@Override
protected void configure(HttpSecurity http) throws Exception
{http.authorizeRequests() //返回一个URL拦截注册器.antMatchers("/user/**").hasAnyRole("USER_INFO", "SUPER_ADMIN", "GENERAL_ADMIN"); //配置角色:允许具有任一角色的用户访问。
}
1.3 使用 access() 实现更加细颗粒度的权限控制
由于 hasAuthority(String)、hasAnyAuthority(String)、hasRole(String)、hasAnyRole(String) 方法都比较简单,但局限性也很大,因此无法基于一些环境和业务参数灵活控制范围规则。为此,Spring Security 提供了 access() 方法,该方法允许开发人员传入一个表达式进行更加细颗粒度的权限控制。这里将引入 SpEL( Spring 表达式语言,Spring Expression Language)表达式,它是 Spring 框架提供的一种动态表达式语言。基于 SpEL,只要该表达式的返回值为 true,那么 access() 方法允许用户访问。
【示例】使用 access() 实现更加细颗粒度的权限控制。
java">@Override
protected void configure(HttpSecurity http) throws Exception
{//定义权限规则:只能拥有 “user:add” 权限,且不拥有 “user:delete” 权限的用户才能访问String expression = "hasAuthority('user:add') and !hasAuthority('user:delete')";//设置权限http.authorizeRequests() //返回一个URL拦截注册器.antMatchers("/user/**").access(expression);
}
上述代码的执行效果是只能拥有 “user:add” 权限,且不拥有 “user:delete” 权限的用户才能访问。
2、使用配置方法控制访问权限
确保请求安全的手段是对访问进行限制,只有那些具有访问限制的请求才能被服务器处理。那么,如何让 HTTP 请求与权限控制过程产生关联呢?答案还是使用 Spring Security 提供的配置方法。Spring Security 提供了三种强大的匹配器(Matcher)来实现这一目标,分别是 MVC 匹配器、Ant 匹配器及正则表达式匹配器。
2.1 MVC 匹配器
在三种匹配器中,MVC 匹配器的使用方法比较简单,基于 HTTP 端点的访问路径进行匹配即可。
【示例】使用 MVC 匹配器,基于 HTTP 端点的访问路径匹配权限访问。
java">@Override
protected void configure(HttpSecurity http) throws Exception
{http.authorizeRequests().mvcMatchers("/user/*").hasRole("USER").mvcMatchers("/admin").hasRole("ADMIN").anyRequest().authenticated();
}
现在又有一个新的问题,如果一个 Controller 中存在两个路径完全一样的 HTTP 端点呢?这种情况是存在的。对于 HTTP 端点而言,就算路劲一样,只要所使用的 HTTP 方法不同,那就是不同的两个端点。
【示例】针对上述问题,MVC 匹配器还提供了重载的 mvcMatchers() 方法。
java">@Override
protected void configure(HttpSecurity http) throws Exception
{http.authorizeRequests().mvcMatchers(HttpMethod.POST, "/hello").authenticated().mvcMatchers(HttpMethod.GET, "/hello").permitAll().anyRequest().denyAll(); //拒绝
}
同时,如果想对某个路径下的所有子路径都指定同样的访问控制,在该路径后面添加“*”符号即可。
【示例】使用“*”符号,配置某个路径下的所有子路径。
java">@Override
protected void configure(HttpSecurity http) throws Exception
{http.authorizeRequests().mvcMatchers(HttpMethod.POST, "/user/*").authenticated();
}
2.2 Ant 匹配器
Ant 匹配器的表现形式和使用方法与前面介绍的 MVC 匹配器非常类似,也提供了如下的三个方法来完成请求与 HTTP 端点地址之间的匹配关系。
- antMatchers(String... antPatterns)
- antMatchers(HttpMethod method)
- antMatchers(HttpMethod method, String... antPatterns)
【示例】使用 Ant 匹配器,匹配 HTTP 端点地址。
java">@Override
protected void configure(HttpSecurity http) throws Exception
{http.authorizeRequests().antMatchers("/user").hasRole("USER").antMatchers(HttpMethod.POST, "/admin").hasRole("ADMIN").anyRequest().authenticated();
}
注意:在浏览器的地址栏最后面添加“/”符号,如:http://localhost:8080/user/,这样才会得到正确的访问结果。
显然,Ant 匹配器处理请求地址的方式有点让人感到困惑,而使用 MVC 匹配器则没有这个问题,无论请求地址的末尾是否存在“/”符号,它都是正确匹配。
所以在日常开发过程中,我们更倾向于使用 MVC 匹配器而非 Ant 匹配器,原因在于 Ant 匹配器在匹配路径上存在一定风险。
2.3 正则表达式匹配器
最后要介绍的匹配器是正则表达式匹配器。它提供的两个配置方法如下:
- regexMatchers(String... regexPatterns)
- regexMatchers(HttpMethod method, String... regexPatterns)
使用正则表达式匹配器的主要优势在于它能够基于复杂的正则表达式对请求地址进行匹配。
【示例】使用正则表达式匹配器,匹配常见的邮箱地址。
java">@Override
protected void configure(HttpSecurity http) throws Exception
{http.authorizeRequests().regexMatchers("/email/{email:.*(.+@.+\\.com)}").permitAll().anyRequest().denyAll();
}