目录
身份认证:
1、创建一个spring boot项目,并导入一些初始依赖:
2、由于我们加入了spring-boot-starter-security的依赖,所以security就会自动生效了。这时直接编写一个controller控制器,并编写一个接口进行测试:
3、自定义用户的登录认证:
4、使用(SecurityFilterChain)过滤器, 配置用户登录的接口可以暴露出来,被所有人都正常的访问(还应在暴露一个注册接口,但我这里就先不写了)
5、将项目运行起来(我同时还写了一个普通的test方法,类型是get,没有放行,用于测试能不能拦截到):
6、自定义一个登录页面:
7、退出接口
权限校验:
1、基于请求:
2、基于方法:
目前市面上常用的安全框架有:
Spring Security、Shiro,还有一个国人开发的框架目前也备受好评:SaToken
但是与spring boot项目融合度最高的还是Spring Security,所以目前我们讲解一下基于spring boot项目来整合spring security来实现常用的登录校验与权限认证;
Spring Security(安全框架)
1、介绍
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。
如果项目中需要进行权限管理,具有多个角色和多种权限,我们可以使用Spring Security。
采用的是责任链的设计模式,是一堆过滤器链的组合,它有一条很长的过滤器链。
2、功能
Authentication (认证),就是用户登录
Authorization (授权),判断用户拥有什么权限,可以访问什么资源
安全防护,跨站脚本攻击,session攻击等
非常容易结合Springboot项目进行使用,本次就着重与实现认证和授权这两个功能
版本spring boot3.1.16、spring security6.x
身份认证:
1、创建一个spring boot项目,并导入一些初始依赖:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.21</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-test</artifactId><scope>test</scope></dependency> </dependencies>
2、由于我们加入了spring-boot-starter-security的依赖,所以security就会自动生效了。这时直接编写一个controller控制器,并编写一个接口进行测试:
可以看到我们在访问这个接口时出现了拦截,必须要我们进行登录之后才能访问;
那么接下来我们就来实现第一个功能:用户登录认证;
3、自定义用户的登录认证:
Spring Security 6.x 的认证实现流程如下:
- 用户提交登录请求
- Spring Security 将请求交给 UsernamePasswordAuthenticationFilter 过滤器处理。
- UsernamePasswordAuthenticationFilter 获取请求中的用户名和密码,并生成一个 AuthenticationToken 对象,将其交给 AuthenticationManager 进行认证。
- AuthenticationManager 通过 UserDetailsService 获取用户信息,然后使用 PasswordEncoder 对用户密码进行校验。
- 如果密码正确,AuthenticationManager 会生成一个认证通过的 Authentication 对象,并返回给 UsernamePasswordAuthenticationFilter 过滤器。如果密码不正确,则 AuthenticationManager 抛出一个 AuthenticationException 异常。
- UsernamePasswordAuthenticationFilter 将 Authentication 对象交给 SecurityContextHolder 进行管理,并调用 AuthenticationSuccessHandler 处理认证成功的情况。
- 如果认证失败,UsernamePasswordAuthenticationFilter 会调用 AuthenticationFailureHandler 处理认证失败的情况。
看起来有点复杂,其实写起来很简单的。spring security的底层就是一堆的过滤器来是实现的,而我们只需要编写一些重要的过滤器即可,其他的就用spring security默认的实现,只要不影响我们正常的登录功能即可。
(创建一个用户表用来进行登录实现,注意这个表中的用户名不能重复,我们将用户名作为每一个用户的唯一凭证,就如同人身份证号一样)表的结构非常简单,一些配置我这里就不在描述了(实体类、mapper、service、controller等)
认证的实现流程:
1、创建一个UserDetailsService实现SpringSecurity的UserDetailsService接口(这里写的是查询用户的逻辑)
UserDetailsService:此接口中定义了登录服务方法,用来实现登录逻辑。方法的返回值是UserDetails,也是spring security框架定义中的一个接口,用来存储用户信息,我们可以自定义一个类用来实现这个接口,将来返回的时候就返回我们自定义的用户实体类。
实现UserDetailsService接口
@Component public class MyUserDetailsService implements UserDetailsService {/** UserDetailsService:提供查询用户功能,如根据用户名查询用户,并返回UserDetails*UserDetails,SpringSecurity定义的类, 记录用户信息,如用户名、密码、权限等* */@Autowiredprivate SysUserMapper sysUserMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //根据用户名从数据库中查询用户SysUser sysUser = sysUserMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(username != null, SysUser::getUsername, username)); if (sysUser==null){throw new UsernameNotFoundException("用户不存在"); }MySysUserDetails mySysUserDetails=new MySysUserDetails(sysUser);return mySysUserDetails;} }
(在原有数据库表的基础上)实现UserDetails接口:
@Data @AllArgsConstructor @NoArgsConstructor public class MySysUserDetails implements UserDetails {private Integer id;private String username;private String password;// 用户拥有的权限集合,我这里先设置为null,将来会再更改的@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}public MySysUserDetails(SysUser sysUser) {this.id = sysUser.getId();this.username = sysUser.getUsername();this.password = sysUser.getPassword();}// 后面四个方法都是用户是否可用、是否过期之类的。我都设置为true@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;} }
2、通过配置类对AuthenticationManager与自定义的UserDetails和PasswordEncoder进行关联
Spring Security是通过AuthenticationManager实现的认证,会借此来判断用户名和密码的正确性
密码解析器spring security框架定义的接口:PasswordEncoder
spring security框架强制要求,必须在spring容器中存在PasswordEncoder类型对象,且对象唯一
@Configuration @EnableWebSecurity //开启webSecurity服务 public class SecurityConfig {@Autowiredprivate MyUserDetailsService myUserDetailsService;@Bean public AuthenticationManager authenticationManager(PasswordEncoder passwordEncoder){DaoAuthenticationProvider provider=new DaoAuthenticationProvider(); //将编写的UserDetailsService注入进来provider.setUserDetailsService(myUserDetailsService); //将使用的密码编译器加入进来provider.setPasswordEncoder(passwordEncoder); //将provider放置到AuthenticationManager 中ProviderManager providerManager=new ProviderManager(provider);return providerManager; }/** 在security安全框架中,提供了若干密码解析器实现类型。* 其中BCryptPasswordEncoder 叫强散列加密。可以保证相同的明文,多次加密后,* 密码有相同的散列数据,而不是相同的结果。* 匹配时,是基于相同的散列数据做的匹配。* Spring Security 推荐使用 BCryptPasswordEncoder 作为密码加密和解析器。* */ @Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder(); }
3、在登录方法所在的类中注入AuthenticationManager,调用authenticate实现认证逻辑,并且在认证之后返回认证过的用户信息:
controller层:
// 用户登录@PostMapping("/login")public String login(@RequestBody LoginDto loginDto){String token= sysUserService.login(loginDto);return token;}
对应的service层的方法:
在这之前,介绍一个非常重要的类:UsernamePasswordAuthenticationToken
(
UsernamePasswordAuthenticationToken
是Spring Security中用于表示基于用户名和密码的身份验证令牌的类。它主要有以下两个构造方法:
-
UsernamePasswordAuthenticationToken(Object principal, Object credentials)
principal
参数表示认证主体,通常是用户名或用户对象。在身份验证过程中,这通常是用来标识用户的信息,可以是用户名、邮箱等。credentials
参数表示凭据,通常是用户的密码或其他凭证信息。在身份验证过程中,这用于验证用户的身份。
-
UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities)
- 除了上述两个参数外,这个构造方法还接受一个授权权限集合(
authorities
参数)。这个集合表示用户所拥有的权限,通常是一个包含用户权限信息的集合。 GrantedAuthority
接口代表了用户的权限信息,可以通过该接口的实现类来表示用户具体的权限。
- 除了上述两个参数外,这个构造方法还接受一个授权权限集合(
这两个构造方法的作用是创建一个包含用户身份信息、凭据信息和权限信息的身份验证令牌,以便在Spring Security中进行身份验证和授权操作。通过这些构造方法,可以将用户的相关信息封装成一个完整的身份验证对象,方便在安全框架中进行处理和验证。
总之,UsernamePasswordAuthenticationToken
是在Spring Security中用于表示用户名密码身份验证信息的重要类,通过不同的构造方法可以满足不同场景下的需求。
)
@Autowiredprivate AuthenticationManager authenticationManager;// 登录接口的具体实现@Overridepublic String login(LoginDto loginDto) { // 传入用户名和密码UsernamePasswordAuthenticationToken usernamePassword =new UsernamePasswordAuthenticationToken(loginDto.getUsername(),loginDto.getPassword()); //是实现登录逻辑,此时就回去调用LoadUserByUsername方法Authentication authenticate = authenticationManager.authenticate(usernamePassword); // 获取返回的用户信息Object principal = authenticate.getPrincipal();//强转为MySysUserDetails类型 MySysUserDetails mySysUserDetails = (MySysUserDetails) principal; // 输出用户信息System.err.println(mySysUserDetails); //返回tokenString token= UUID.randomUUID().toString();return token;}
我在test类中设置一些用户数据,并进行测试;
@Autowired private SysUserMapper sysUserMapper;
@Autowired private PasswordEncoder passwordEncoder;
@Test void contextLoads() { //导入了一个用户
SysUser sysUser=new SysUser();
sysUser.setUsername("zhangsan"); sysUser.setPassword(passwordEncoder.encode("123456")); sysUserMapper.insert(sysUser);
}
这里我们已经写好了自定义的登录流程,将项目运行起来(我同时还写了一个普通的test方法,类型是get,用来一起测试)
访问http://localhost:8080/test
这是我们写的一个普通的get方法,我们明明访问的是http://localhost:8080/test这个路径,但是却自动跳转到了Spring Security提供的默认的登录页面;这是因为Spring Security默认所有的请求都要先登录才行,我们在这里登录之后就可以继续访问test页面了;
(由于我们已经实现了UserDetailsService接口,并且在用户表中导入了一条用户数据,那么,这里的用户名和密码就是我们在数据库中存储的用户名和密码)
登录成功之后,我们就可以访问到test的信息了:
既然这个test请求要先进行拦截认证才能访问,那么,我们刚才编写的登录接口sys-user/login岂不是也要先进行拦截认证才能访问,这就与我们编写登录接口的初衷违背了,我们这个接口就是用来登陆的,现在还要先登录认证,之后再访问这个登录接口。那么有没有一种方法,不使用SpringSecurity默认的登录页面呢,使我们编写的登录接口所有人都可以直接访问呢?
4、使用(SecurityFilterChain)过滤器, 配置用户登录的接口可以暴露出来,被所有人都正常的访问(还应在暴露一个注册接口,但我这里就先不写了)
还是在第二步设置的SecurityConfig类中设置过滤器:
在spring security6.x版本之后,原先经常用的and()方法被废除了,现在spring官方推荐使用Lambda表达式的写法。
(因为我们接下来要进行测试,所以禁用CSRF保护,CSRF(Cross-Site Request Forgery)是一种攻击方式,攻击者通过伪造用户的请求来执行恶意操作。)
/** 配置权限相关的配置* 安全框架本质上是一堆的过滤器,称之为过滤器链,每一个过滤器链的功能都不同* 设置一些链接不要拦截* */@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { // 关闭csrfhttpSecurity.csrf(it->it.disable()); httpSecurity.authorizeHttpRequests(it->it.requestMatchers("/sys-user/login").permitAll() //设置登录路径所有人都可以访问.anyRequest().authenticated() //其他路径都要进行拦截);return httpSecurity.build();}
5、将项目运行起来(我同时还写了一个普通的test方法,类型是get,没有放行,用于测试能不能拦截到):
访问test请求:遇到拦截,说明我们的配置生效了
访问登录页面:能正常访问,且密码正确,返回了一个我们自己生成的一个token。
6、自定义一个登录页面:
SpringSecurity虽然默认有一个登录页面,但是我们一般情况下还是用我们自己写的登录页面,这样可操作性就大了很多;
引入thymeleaf依赖,我们直接在idea项目中建立一个登录页面;
编写一个登录页面,主要是完成用户的登录,同时我们也不再需要频繁的使用postman进行测试了:
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org"><title>自定义的登录页面</title> </head> <body><form action="/sys-user/login" method="post">用户名: <input type="text" name="username" ><br>密码: <input type="password" name="password"><br> <input type="submit" value="登录"> </form></body> </html>
这是一个简单的登录页面,就指定了用户名和密码。
并且指定from表单的提交路径为我们自定义的登录接口;将这个页面放在resource/templates目录下,方便我们将来的调用;
HTML中的form表单默认情况下会将数据格式化为key-value形式,而不是JSON格式。
也就是说我们刚刚写的自定义登录接口时是用@RequestBody接受收json类型的数据,这肯定是接受不到的,有两种方法实现:
1、直接用@RequestParam("username") ,@RequestParam("password")接收这两个参数
2、@ModelAttribute
注解:@ModelAttribute("formData") User user //在@ModelAttribute注解内写表单的id,还能使用对象进行接收
我们也可以在前端将from表单的数据转化为json之后,在进行发送,但那样需要写js,我就直接在后端改一下了。
还是使用使用(SecurityFilterChain)过滤器,指定我们自定义的登录表单路径,(解释一下fromLogin方法):
formLogin 方法是 Spring Security 中用于配置基于表单的登录认证的一种方式。它通常用于传统的 Web 应用程序,其中前端页面由后端动态生成,并且用户在页面中输入用户名和密码来进行登录。在这种情况下,Spring Security 负责处理登录请求、验证用户身份、生成会话等操作。
/** 配置权限相关的配置* 安全框架本质上是一堆的过滤器,称之为过滤器链,每一个过滤器链的功能都不同* 设置一些链接不要拦截* */@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { // 关闭csrfhttpSecurity.csrf(it->it.disable()); // 配置路径相关 httpSecurity.authorizeHttpRequests(it->it.requestMatchers("/login","sys-user/login").permitAll() //设置登录路径所有人都可以访问.anyRequest().authenticated() //其他路径都要进行拦截); //表单 httpSecurity.formLogin(from->from.loginPage("/login") //跳转到自定义的登录页面 .loginProcessingUrl("/sys-user/login") //处理前端的请求,与from表单的action一致即可.defaultSuccessUrl("/index") //默认的请求成功之后的跳转页面,直接访问登录页面);return httpSecurity.build();}
注意,这里还需要将/login这个接口进行放行。
我们知道,不能直接访问login.html这个自定义的登录页面,但是我们可以使用路径映射。先写一个login的get请求,并将这个请求映射到login.html页面。
.defaultSuccessUrl("/index"):这个方法是我们默认的登录成功之后跳转的请求地址。
如果你之前有请求的地址,但是这个地址没有放行或者你没有登录,那么会自动跳转到我们自定义的登录页面,完成登录之后,会跳转到你最先访问的地址;如果你直接访问的就是/login登录地址,那么默认的登录成功之后跳转到我们指定的地址:/index
@Controller public class Login {@GetMapping("/login") public String login(){System.out.println("用户进入登录页面");return "login"; //没使用json返回,直接映射到自定义登录的页面 }@GetMapping("/index") @ResponseBodypublic String index(){return "用户登录成功"; } }
现在我们已经自定义了一个登录页面,将项目启动起来进行测试:
我访问/test地址,这个地址没有放行,而且我们这是没有登录,那么会自动跳转到我们自定义的登录页面:
我们进行登录之后,会跳转到/test请求地址:
可以看到我们的结果与我们设想的一样:
现在我们直接访问/login登录页面:可以看到返回了/index页面的内容(这个是我们设置的默认登录成功之后返回的页面)
7、退出接口
需要注意的是在Spring Security中,没有专门用于处理退出失败的接口。退出(注销)操作通常是由浏览器发起的,Spring Security会拦截注销请求并执行相应的注销逻辑。
退出操作通常是通过调用SecurityContextLogoutHandler
来完成的,它会清除用户的安全上下文,包括认证信息和会话信息。
在security框架中,默认提供了退出登陆的功能。请求地址是 /lohout 此为默认值,可以通过配置进行修该。直接请求 /logout ,会实现自动退出登录逻辑(默认的/logout接收get、和post请求)
退出登陆时,会清楚内存中的登录用户主体信息,销毁会话对象等等。
自定义退出接口:
httpSecurity.logout(logout->{logout.logoutUrl("/user/login") //自定义退出接口.logoutSuccessHandler(logoutSuccess); //退出成功之后的逻辑});
编写退出成功之后的逻辑,我们可以在这里删除掉redis中的数据,设置返回的信息等等....
@Component
public class LogoutSuccess implements LogoutSuccessHandler {@Resourceprivate RedisTemplate<String,String> redisTemplate;/*
* 登录成功之后的逻辑
* */@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {String token = request.getHeader("token");// 删除redis中的数据redisTemplate.delete(token);Map<String,Object> map=new HashMap<>();map.put("msg","退出成功");map.put("code",200);response.getWriter().write(JSON.toJSONString(map));response.setContentType("application/json;charset=utf-8");}}
权限校验:
我们费了很多功夫完成了身份认证,权限校验相对来说是比较简单的:
首先,我先解释一下角色与权限在SpringSecurity中的作用:
-
角色(Role):角色是一组权限的集合,通常代表着用户的身份或职责。在Spring Security中,可以通过配置将角色分配给用户或者用户组,以此来控制用户对系统资源的访问。例如,管理员拥有添加、删除和修改用户的权限,而普通用户只能查看自己的信息。
-
权限(Permission):权限是指对某一特定资源的访问控制,例如读写文件、访问数据库等。在Spring Security中,通常使用“资源-操作”命名方式来定义权限,例如“/admin/* - GET”表示允许访问以/admin/开头的所有URL的GET请求。可以将权限分配给角色,也可以将其分配给单独的用户。
角色与权限之间的关系是多对多的;
建立两张简单的表;一张用来存放角色、一张用来存放权限
角色表:
权限表:
这里建立的两张表只是用来进行测试,正常的数据不可能这么少的。建立相应的实体类;
SpringSecurity要求将身份认证信息存到GrantedAuthority对象列表中。代表了当前用户的权限。 GrantedAuthority对象由AuthenticationManager插入到Authentication对象中,然后在做出授权决策 时由AccessDecisionManager实例读取。 GrantedAuthority 接口只有一个方法
AuthorizationManager实例通过该方法来获得GrantedAuthority。通过字符串的形式表示, GrantedAuthority可以很容易地被大多数AuthorizationManager实现读取。如果GrantedAuthority不 能精确地表示为String,则GrantedAuthorization被认为是复杂的,getAuthority()必须返回null
告知权限的流程:
直接在登录时查询用户的权限,并放在我们自定义的实现了UserDetail的接口类中,用来表示登录用户的全部信息;
在MySysUserDetails类中加入两个属性,记录从数据库中查处的角色和权限信息
我这里就简单一点,不在做多表关联查询了。直接把zhangsan用户设置为超级管理员,拥有所有权限;lisi用户设置为普通管理员,拥有基本权限。
在MyUserDetailsService中实现用户权限的赋值:
@Component public class MyUserDetailsService implements UserDetailsService {/** UserDetailsService:提供查询用户功能,如根据用户名查询用户,并返回UserDetails*UserDetails,SpringSecurity定义的类, 记录用户信息,如用户名、密码、权限等* */@Autowiredprivate SysUserMapper sysUserMapper;@Autowired private SysRoleMapper sysRoleMapper; @Autowired private SysPermissionsMapper sysPermissionsMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //根据用户名从数据库中查询用户SysUser sysUser = sysUserMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(username != null, SysUser::getUsername, username)); if (sysUser==null){throw new UsernameNotFoundException("用户不存在"); }MySysUserDetails mySysUserDetails=new MySysUserDetails(sysUser); if ("zhangsan".equals(username)){ //zhangsan用户是超级管理员,拥有一切权限SysRole sysRole = sysRoleMapper.selectOne(new LambdaQueryWrapper<SysRole>().eq(SysRole::getRoleName, "超级管理员"));Set<SysRole> roles=new HashSet<>();roles.add(sysRole);mySysUserDetails.setRoles(roles);SysPermissions sysPermissions = sysPermissionsMapper.selectById(1);Set<String> permissions=new HashSet<>();permissions.add(sysPermissions.getPermissionsName());mySysUserDetails.setPermissions(permissions); }if ("lisi".equals(username)){ //lisi用户是普通管理员,拥有基本权限SysRole sysRole = sysRoleMapper.selectOne(new LambdaQueryWrapper<SysRole>().eq(SysRole::getRoleName, "普通管理员"));Set<SysRole> roles=new HashSet<>();roles.add(sysRole);mySysUserDetails.setRoles(roles);SysPermissions sysPermissions = sysPermissionsMapper.selectById(2);Set<String> permissions=new HashSet<>();permissions.add(sysPermissions.getPermissionsName());mySysUserDetails.setPermissions(permissions);}return mySysUserDetails;} }
在实现了UserDetailes接口的用户信息类MySysUserDetails中完成角色和权限的赋值:
// 角色信息private Set<SysRole> roles; // 权限信息private Set<String> permissions;// 用户拥有的权限集合,我这里先设置为null,将来会再更改的@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {System.err.println("进入权限的获取方法");List<GrantedAuthority> authorities = new ArrayList<>(); // 授权信息列表 // 将角色名称添加到授权信息列表中roles.forEach(role->authorities.add(new SimpleGrantedAuthority(role.getRoleName())));// 将权限名称添加到授权信息列表中permissions.forEach(permission->authorities.add(new SimpleGrantedAuthority(permission)));return authorities; // 返回授权信息列表}
用户认证之后,会去存储用户对应的权限,并且给资源设置对应的权限,SpringSecurity支持两种粒度 的权限:
1、基于请求的:在配置文件中配置路径,可以使用**的通配符
2、基于方法的:在方法上使用注解实现
角色配置:在UserDetails接口中存在相关的权限和角色管理,只不过我们在实现这个接口的时候,将这些都设置为了null。现在我们只需要将这些信息实现即可:
1、基于请求:
还是在SecurityFilter过滤器中实现请求地址的权限校验
httpSecurity.authorizeHttpRequests(it-> //hello地址只有超级管理员角色才能访问 it.requestMatchers("/hello").hasRole("超级管理员") //hello2地址只有"拥有所有权限"的权限才能访问 .requestMatchers("hello2").hasAuthority("拥有所有权限").requestMatchers("/login","sys-user/login").permitAll() //设置登录路径所有人都可以访问.anyRequest().authenticated() //其他路径都要进行拦截);
使用sili进行登录时,访问hello2接口显示权限不够:
使用zhangsan进行登录时,访问hello2接口可以访问到:
2、基于方法:
基于方法的权限认证要在SecurityConfig类上加上@EnableMethodSecurity注解,表示开启了方法权限的使用;
常用的有四个注解:
@PreAuthorize
@PostAuthorize
@PreFilter
@PostFilter
/*测试@PreAuthorize注解 * 作用:使用在类或方法上,拥有指定的权限才能访问(在方法运行前进行校验) * String类型的参数:语法是Spring的EL表达式 * 有权限:test3权限 * hasRole:会去匹配authorities,但是会在hasRole的参数前加上一个ROLE_前缀, * 所以在定义权限的时候需要加上ROLE_前缀 * role和authorities的关系是:role是一种复杂的写法,有ROLE_前缀,authorities是role的简化写法 * 如果使用 * hasAnyRole:则匹配的权限是在authorities加上前缀ROLE_ * 推荐使用 * hasAnyAuthority:匹配authorities,但是不用在authorities的参数前加上ROLE_前缀 * */ @PreAuthorize("hasAnyAuthority('拥有所有权限')") @ResponseBody @GetMapping("/test3") public String test3(){System.out.println("一个请求");return "一个test3请求"; }
/*@PostAuthorize:在方法返回时进行校验。可以还是校验权限、或者校验一些其他的东西(接下来我们校验返回值的长度) *返回结果的长度大于3、则认为是合法的 returnObject:固定写法,代指返回对象 * */ @ResponseBody @PostAuthorize("returnObject.length()>4") @GetMapping("/test4") public String test4(){System.out.println("一个test4请求");return "小张自傲张最终"; }
/* * @PreFilter:过滤符合条件的数据进入到接口 * */@PostFilter("filterObject.length()>3")@ResponseBody@GetMapping("/test5")public String test5(){System.out.println("一个test4请求"); List<String> list = new ArrayList<>(); list.add("张三"); list.add("王麻子"); list.add("狗叫什么");return "一个test5请求";}
/* * @PreFilter:过滤符合条件的数据返回,数据必须是Collection、map、Array【数组】 * */ @PreFilter("filterObject.length()>5") @ResponseBody @PostMapping("/test6") public List<String> test6(@RequestBody List<String> list){return list; }
这四个常用的权限校验方法我都写出来了,运行结果我就不在一一截图了。
需要注意的是这些方法不仅仅局限在权限的校验,还能对返回的结果做一定的操作;
最需要注意的就是@PreFilter注解,它要求前端传递的参数一定是数组或集合;
还有在SpringSecurity框架中:
role和authorities的关系是:role是一种复杂的写法,有ROLE_前缀,authorities是role的简化写法
基于方法鉴权 在SpringSecurity6版本中@EnableGlobalMethodSecurity被弃用,取而代之的是 @EnableMethodSecurity。默认情况下,会激活pre-post注解,并在内部使用 AuthorizationManager。
新老API区别 此@EnableMethodSecurity替代了@EnableGlobalMethodSecurity。提供了以下改进: 1. 使用简化的AuthorizationManager。 2. 支持直接基于bean的配置,而不需要扩展GlobalMethodSecurityConfiguration 3. 使用Spring AOP构建,删除抽象并允许您使用Spring AOP构建块进行自定义 4. 检查是否存在冲突的注释,以确保明确的安全配置 5. 符合JSR-250 6. 默认情况下启用@PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter
主要的权衡似乎是您希望您的授权规则位于何处。重要的是要记住,当您使用基于注释的方法安全性 时,未注释的方法是不安全的。为了防止这种情况,请在HttpSecurity实例中声明一个兜底授权规则。 如果方法上也定义了权限,则会覆盖类上的权限
注意:使用注解的方式实现,如果接口的权限发生变化,需要修改代码了。
总结:
-
登录校验(Authentication):
- 用户提交用户名和密码进行登录。
- Spring Security会拦截登录请求,并将用户名和密码与存储在系统中的凭据(如数据库或LDAP)进行比对。
- 如果用户名和密码匹配,则认为用户通过了身份验证,可以继续访问受限资源。
- 认证成功后,Spring Security会创建一个包含用户信息和权限的安全上下文(Security Context)。
-
权限认证(Authorization):
- 一旦用户通过了身份验证,Spring Security就会开始进行权限认证。
- 针对每个受限资源或操作,可以配置相应的权限要求,例如需要哪些角色或权限才能访问。
- Spring Security会根据配置的权限要求,检查当前用户所拥有的角色和权限,判断是否满足访问条件。
- 如果用户拥有足够的角色或权限,就被允许访问资源;否则将被拒绝访问,并可能重定向到登录页面或返回相应的错误信息。
Spring Security通过身份验证(Authentication)来确认用户的身份,并通过授权(Authorization)来控制用户对受保护资源的访问。这种分离的设计使得安全配置更加灵活,并且可以轻松地对不同的用户和角色进行管理和控制。
这都是一些基础的理论,下面我将实战中演示使用security的案例:
前后端分离,使用vue3整合SpringSecurity加JWT实现登录认证_springsecurity整合vue3-CSDN博客