Spring Security快速入门(四)访问控制
一、访问控制url匹配
在前面讲解了认证中所有常用配置,主要是对`http.formLogin()`进行操作。而在配置类中`http.authorizeRequests()`主要是对url进行控制,也就是我们所说的授权(访问控制)。http.authorizeRequests()
也支持连缀写法,总体公式为:
- url 匹配规则.权限控制方法
通过上面的公式可以有很多 url 匹配规则和很多权限控制方法。
这些内容进行各种组合就形成了Spring Security中的授权。
在所有匹配规则中取所有规则的交集。配置顺序影响了之后授权效果,越是具体的应该放在前面,越
是笼统的应该放到后面。
1. anyRequest()
在之前认证过程中我们就已经使用过 anyRequest(),表示匹配所有的请求。一般情况下此方法都会
使用,设置全部内容都需要进行认证。
// 表示所有请求,都必须经过登录认证后才能被访问。
http.anyRequest().authenticated();
2. antMatchers()
antMatchers()有三个重载方式,可以指定Http请求方法,也可以指定ant表达式,用于匹配URL规则。
规则如下:
- ? :匹配一个字符
- *: 匹配 0 个或多个字符
- ** :匹配 0 个或多个目录
在实际项目中经常需要放行所有静态资源,下面演示放行 js 文件夹下所有脚本文件。
http.antMatchers("/js/**", "/css/**").permitAll();
还有一种配置方式是只要是.js 文件都放行。
http.antMatchers("/**/*.js").permitAll();
3. regexMatchers()
使用正则表达式进行匹配。和 antMatchers() 主要的区别就是参数, antMatchers() 参数是 ant表达式,
regexMatchers() 参数是正则表达式。
演示所有以.js 结尾的文件都被放行。
http.regexMatchers(". + [.]js").permitAll();
无论是antMatchers() 还是regexMatchers() 都具有两个参数的方法,
其中第一个参数都是HttpMethod ,表示请求方式,
当设置了HttpMethod 后表示只有设定的特定的请求方式才执行对应的权限设置。
4. mvcMatchers()
mvcMatchers()适用于配置了 servletPath 的情况。
servletPath 就是所有的 URL 的统一前缀。在 SpringBoot 整合SpringMVC 的项目中可以在
application.properties
中添加下面内容设置 servletPath
:
spring.mvc.servlet.path=/xxxx
在 Spring Security 的配置类中配置.servletPath() 是 mvcMatchers()返回值特有的方法,
antMatchers()和 regexMatchers()没有这个方法。在servletPath() 中配置了servletPath 后,
mvcMatchers()直接写 SpringMVC 中@RequestMapping()中设置的路径即可。
http.mvcMatchers("/demo").servletPath("/xxxx").permitAll();
如果不习惯使用 mvcMatchers()也可以使用 antMatchers(),下面代码和上面代码是等效:
http.antMatchers("/xxxx/demo").permitAll();
二、访问控制方法
在匹配到URL之后,我们就需要对这些URL进行控制,那些是可以直接放行的,哪些是需要认证之后才能访问的,哪些是需要特定权限的等等。
1. 内置访问控制方法
permitAll()
:表示所匹配的 URL 任何人都允许访问。authenticated()
:表示所匹配的URL都需要被认证才能访问。anonymous()
:表示只允许匿名用户访问,已登录用户不能访问。denyAll()
:表示所匹配的 URL 都不允许被访问。rememberMe()
:被remember me
的用户允许访问。fullyAuthenticated()
:如果用户不是被 remember me 的,才可以访问。
2. 基于角色权限判断
除了上面的内置权限控制。Spring Security 中还支持很多其他权限控制。这些方法一般都用于用户已经被认证后,
判断用户是否具有特定的要求。
①:hasAuthority(String)
判断用户是否具有特定的权限,用户的权限是在自定义登录逻辑中loadUserByUsername()
创建 User 对象时指定的。一般是从数据库中查询出来的权限。
下图中admin和normal 就是用户的权限。admin和normal 严格区分大小写。
在配置类中通过 hasAuthority(“admin”)设置具有 admin 权限时才能访问。
http.antMatchers("/index.html").hasAuthority("admin")
②:hasAnyAuthority(String …)
如果用户具备给定权限中某一个,就允许访问。
下面代码中由于大小写和用户的权限不相同,所以用户无权访问
http.antMatchers("/index.html").hasAnyAuthority("adMin","admiN")
③:hasRole(String)
如果用户具备给定角色就允许访问。否则出现 403。
参数取值同样来源于自定义登录逻辑 UserDetailsService 实现类中返回的 User 对象中给 User 赋予的授
权。
在给用户赋予角色时角色需要以: ROLE_开头,后面添加角色名称。例如:ROLE_abc 其中 abc 是角
色名,ROLE_是固定的字符开头。同样,角色也是保存在数据库中的。
注意:使用 hasRole()时参数只写 abc 即可。否则启动报错。
在配置类中直接写 abc 即可。
④:hasAnyRole(String …)
如果用户具备给定角色的任意一个,就允许被访问
⑤:hasIpAddress(String)
如果请求是指定的 IP 就运行访问。
可以通过 request.getRemoteAddr() 获取 ip 地址。
3. 基于表达式的访问控制
在1. 内置访问控制方法
和2. 基于角色权限判断
中,实际上底层实现都是调用access(表达式)这样的一个方式。
可以通过access() 实现和之前学习的权限控制完成相同的功能。
以 hasRole 和 和 permitAll 举例:
我们可以使用自定义方法。
实现一个判断登录用户是否具有访问当前 URL 权限。
新建一个接口和它的实现类,实现判断。
接口如下:
package com.linqibin.springsecuritystudy.service;
import org.springframework.security.core.Authentication;
import javax.servlet.http.HttpServletRequest;
public interface MyAccessService {
boolean hasUriPermission(HttpServletRequest request, Authentication authentication);
}
实现类:
package com.linqibin.springsecuritystudy.service.impl;
import com.linqibin.springsecuritystudy.service.MyAccessService;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
@Service
public class MyAccessServiceImpl implements MyAccessService {
@Override
public boolean hasUriPermission(HttpServletRequest request, Authentication authentication) {
Object obj = authentication.getPrincipal();
if (obj instanceof UserDetails) {
UserDetails userDetails = (UserDetails) obj;
Collection<? extends GrantedAuthority> authorities =
userDetails.getAuthorities();
return authorities.contains(new
SimpleGrantedAuthority(request.getRequestURI()));
}
return false;
}
}
修改配置类,在 access 中通过@bean的id名.方法(参数)的形式进行调用配置类中修改如下:
接下来,我们去修改一下UserDetailServiceImpl中的授权:
4. 基于注解的访问控制
在 Spring Security 中提供了一些访问控制的注解。这些注解都是默认是都不可用的,需要通过
@EnableGlobalMethodSecurity 进行开启后使用。
如果设置的条件允许,程序正常执行。如果不允许会报 500。
这些注解可以写到 Service 接口或方法上,也可以写到 Controller或 Controller 的方法上。通常情况下
都是写在控制器方法上的,控制接口URL是否允许被访问。
①:@Secured
@Secured 是专门用于判断是否具有角色的。能写在方法或类上。**注意:参数要以 ROLE_开头。**此处与基于权限判断的方式不同。
在 启 动 类 ( 也 可 以 在 配 置 类 等 能 够 扫 描 的 类 上 ) 上 添 加@EnableGlobalMethodSecurity(securedEnabled = true)
在控制器方法上添加@Secured 注解
这样,想要访问这个方法,认证过后的用户就必须用友abc角色。
②:@PreAuthorize/@PostAuthorize
@PreAuthorize 和@PostAuthorize 都是方法或类级别注解。
@PreAuthorize
表示访问方法或类在执行之前先判断权限,大多情况下都是使用这个注解,注解
的参数和access()方法参数取值相同,都是权限表达式。@PostAuthorize
表示方法或类执行结束后判断权限,此注解很少被使用到。
第一步要做的也是开启注解。
第二步添加注解。
在控制器方法上添加@PreAuthorize,参数可以是任何 access()支持的表达式。
如果不满足条件,会报以下错误: