【Spring Security】分布式鉴权的使用

news/2024/12/13 16:40:29/

🎉🎉欢迎来到我的CSDN主页!🎉🎉

🏅我是Java方文山,一个在CSDN分享笔记的博主。📚📚

🌟推荐给大家我的专栏《Spring Security》。🎯🎯

👉点击这里,就可以查看我的主页啦!👇👇

Java方文山的个人主页

🎁如果感觉还不错的话请给我点赞吧!🎁🎁

💖期待你的加入,一起学习,一起进步!💖💖

请添加图片描述

前言

我们都知道Spring Security是做认证鉴权的框架,为了一些不必要的麻烦,登录功能都是他们自己做的,那我们就需要将登录的功能交给Spring Security管理,但是他们做的东西肯定也是不满足于我们的需求,所以我们要在根据他们提供的代码上自定义,可以将自定义的用户信息获取逻辑集成到Spring Security中,从而实现基于数据库的用户身份认证。

再上一篇中我是通过实现UserDetailsService接口并重写loadUserByUsername方法完成的,但是后面我想了想不应该把代码放在UserServiceImpl层,所以我们需要创建一个类来继承UserServiceImpl将自定义用户身份认证的代码写入。

MyUserDetailsService 

package com.csdn.security.config;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.csdn.security.pojo.User;
import com.csdn.security.service.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;import java.util.Objects;/*** @author Java方文山* @compay csdn_Java方文山* @create 2023-12-22-12:10*/
@Component
public class MyUserDetailsService implements UserDetailsService {@Autowiredprivate UserServiceImpl userService;/*** 实现Spring Security内置的UserDetailService接口,重写loadUserByUsername方法实现数据库的身份校验* @param username* @return* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//根据用户名查询数据库中用户信息User user = userService.getOne(new QueryWrapper<User>().eq("username", username));//判断用户是否存在if(Objects.isNull(user))throw new UsernameNotFoundException("用户不存在");//权限校验TODO,后续讲解return user;}
}

记得我们也要把我们做身份验证这里也改掉,替换成MyUserDetailsService 

 一、前期准备

我们将数据相应到前端,肯定需要特定的格式,方便我们前端做数据显示,这里会用到两个类

JsonResponseStatus 

package com.csdn.security.resp;import lombok.Getter;@Getter
public enum JsonResponseStatus {OK(200, "OK"),UN_KNOWN(500, "未知错误"),RESULT_EMPTY(1000, "查询结果为空!"),;private final Integer code;private final String msg;JsonResponseStatus(Integer code, String msg) {this.code = code;this.msg = msg;}}

这个类是一个枚举类,用于定义接口返回的 JSON 响应状态。它包含了一些常见的响应状态,如 OK(200, "OK") 表示请求成功,UN_KNOWN(500, "未知错误") 表示未知错误,RESULT_EMPTY(1000, "查询结果为空!") 表示查询结果为空。每个响应状态都有一个对应的状态码和消息。在接口的返回值中,可以使用这个枚举类来表示具体的响应状态。 

JsonResponseBody

package com.csdn.security.resp;import lombok.Data;@Data
public class JsonResponseBody<T> {private Integer code;private String msg;private T data;private Long total;private JsonResponseBody(JsonResponseStatus jsonResponseStatus, T data) {this.code = jsonResponseStatus.getCode();this.msg = jsonResponseStatus.getMsg();this.data = data;}private JsonResponseBody(JsonResponseStatus jsonResponseStatus, T data, Long total) {this.code = jsonResponseStatus.getCode();this.msg = jsonResponseStatus.getMsg();this.data = data;this.total = total;}public static <T> JsonResponseBody<T> success() {return new JsonResponseBody<T>(JsonResponseStatus.OK, null);}public static <T> JsonResponseBody<T> success(T data) {return new JsonResponseBody<T>(JsonResponseStatus.OK, data);}public static <T> JsonResponseBody<T> success(T data, Long total) {return new JsonResponseBody<T>(JsonResponseStatus.OK, data, total);}public static <T> JsonResponseBody<T> unknown() {return new JsonResponseBody<T>(JsonResponseStatus.UN_KNOWN, null);}public static <T> JsonResponseBody<T> other(JsonResponseStatus jsonResponseStatus) {return new JsonResponseBody<T>(jsonResponseStatus, null);}}

 这个类是一个通用的 JSON 响应体类

  • code:表示响应状态码。
  • msg:表示响应消息。
  • data:表示响应数据的泛型对象。
  • total:表示响应数据的总数。

修改我们登陆成功后的操作,加入一个JSON字符串转换类并将获取到的对象输出到前端。

    //JSON格式转换@Autowiredprivate ObjectMapper objectMapper;
 //设置登录成功后重定向到那个页面.successHandler((req, resp, auth) -> {objectMapper.writeValue(resp.getOutputStream(),auth.getPrincipal());})

 重启服务器登录进行测试

 可以看到我们的数据都响应到前端了,前端人员就可以拿着数据进行操作了

二、分布式鉴权

1.授权介绍

Spring Security 中的授权分为两种类型:

  • 基于角色的授权:以用户所属角色为基础进行授权,如管理员、普通用户等,通过为用户分配角色来控制其对资源的访问权限。

  • 基于资源的授权:以资源为基础进行授权,如 URL、方法等,通过定义资源所需的权限,来控制对该资源的访问权限。

Spring Security 提供了多种实现授权的机制,最常用的是使用基于注解的方式,建立起访问资源和权限之间的映射关系。

其中最常用的两个注解是 @Secured@PreAuthorize@Secured 注解是更早的注解,基于角色的授权比较适用,@PreAuthorize 基于 SpEL 表达式的方式,可灵活定义所需的权限,通常用于基于资源的授权。

2.表设计

我们会用到以下五张表sys_user、sys_user_role、sys_role、sys_role_module、sys_module

sys_user(用户表)

sys_user_role(用户角色表)

sys_role(角色表)

 sys_role_module(角色权限表)

 sys_module(模块表)

 

3.获取用户权限

我们在数据表都在一个服务器的情况下,可以直接进行联表查询

select * from sys_user a,sys_user_role b,sys_role_module c,sys_module d
where a.id=b.user_id andb.role_id=c.role_id andc.module_id=d.id

如果我们不是源于一个数据库或者一个服务器,那么就要将数据在后端做处理,先在我们自定义身份验证类也就是MyUserDetailsService类,随后将五个表的service实现类引入

 认证身份的同时把权限查询出来。

    @Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//根据用户名查询数据库中用户信息User user = userService.getOne(new QueryWrapper<User>().eq("username", username));//判断用户是否存在if(Objects.isNull(user))throw new UsernameNotFoundException("用户不存在");//权限校验TODO//.stream().map()遍历所有对象将新的数据放入流中// .collect将流中的数据变成一个集合//1.先查询出用户拥有的角色List<Integer> userId = userRoleService.list(new QueryWrapper<UserRole>().eq("user_id", user.getId())).stream().map(UserRole::getRoleId).collect(Collectors.toList());//2.根据角色查询角色名称List<String> roleId = roleService.list(new QueryWrapper<Role>().in("role_id", userId)).stream().map(Role::getRoleName).collect(Collectors.toList());//3.根据角色查询角色所拥有模块IDList<Integer> ModuleId = roleModuleService.list(new QueryWrapper<RoleModule>().in("role_id", userId)).stream().map(RoleModule::getModuleId).collect(Collectors.toList());//4.根据模块ID查询模块URLList<String> urls = moduleService.list(new QueryWrapper<Module>().in("id", ModuleId)).stream().map(Module::getUrl).collect(Collectors.toList());//将用户角色名称与模块url放入一个集合roleId.addAll(urls);//认证集合对象List<SimpleGrantedAuthority> collect = roleId.stream().map(e -> {return new SimpleGrantedAuthority(e);}).collect(Collectors.toList());//将认证集合对象赋值给用户实体user.setAuthorities(collect);return user;}

 这里还有一个问题需要我们解决,我们写完以上代码进行测试后发现有错误

该错误很简单出现在对模块URL遍历这里,因为我们的数据库表中有两个值是空的

第一个方法是加上url的值

第二个方法就是过滤

4.修改SpringSecurity配置类

当我们想要开启spring方法级安全时,只需要在任何 @Configuration实例上使用@EnableGlobalMethodSecurity 注解就能达到此目的。同时这个注解为我们提供了prePostEnabled securedEnabled jsr250Enabled 三种不同的机制来实现同一种功能。

修改WebSecurityConfig配置类,开启基于方法的安全认证机制,也就是说在web层的controller启用注解机制的安全确认。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {...
}

@EnableGlobalMethodSecurity是Spring Security提供的一个注解,用于启用方法级别的安全性。它可以在任何@Configuration类上使用,以启用Spring Security的方法级别的安全性功能。它接受一个或多个参数,用于指定要使用的安全注解类型和其他选项。以下是一些常用的参数:

  • prePostEnabled:如果设置为true,则启用@PreAuthorize@PostAuthorize注解。默认值为false

  • securedEnabled:如果设置为true,则启用@Secured注解。默认值为false

  • jsr250Enabled:如果设置为true,则启用@RolesAllowed注解。默认值为false

  • proxyTargetClass:如果设置为true,则使用CGLIB代理而不是标准的JDK动态代理。默认值为false

使用@EnableGlobalMethodSecurity注解后,可以在应用程序中使用Spring Security提供的各种注解来保护方法,例如@Secured@PreAuthorize@PostAuthorize@RolesAllowed。这些注解允许您在方法级别上定义安全规则,以控制哪些用户可以访问哪些方法。

注解介绍:

注解说明
@PreAuthorize用于在方法执行之前对访问进行权限验证
@PostAuthorize用于在方法执行之后对返回结果进行权限验证
@Secured用于在方法执行之前对访问进行权限验证
@RolesAllowed是Java标准的注解之一,用于在方法执行之前对访问进行权限验证

5.控制Controller层接口权限

@PreAuthorize("hasAuthority('order:manager:list')")@RequestMapping("/queryRoles")public String  queryRoles(){return "管理员权限";}@PreAuthorize("hasAnyAuthority('book:manager:list','order:manager:list')")@RequestMapping("/queryModules")public String queryModules(){return "管理员权限/用户权限";}

至此我们的鉴权就全部完成了

请添加图片描述

到这里我的分享就结束了,欢迎到评论区探讨交流!!

💖如果觉得有用的话还请点个赞吧 💖


http://www.ppmy.cn/news/1279442.html

相关文章

【Linux笔记】系统信息

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Linux学习 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 命令 1. uname - 显示系统信息 2. hostname - 显示或设置系统主机名 3. top - 显示系统资源使用情况 4. df - 显示磁盘空间使用情…

2023年Top5搭建帮助中心工具集锦

随着企业知识管理的不断深化&#xff0c;帮助中心成为了一个越来越重要的组成部分。帮助中心是一个集成了企业知识、FAQ、常见问题解答、教程、使用指南等内容的在线平台&#xff0c;旨在为用户提供快速、准确的问题解答和自助服务。那么在这一年&#xff0c;有哪些搭建帮助中心…

ARM 点灯

.text .global _start _start: led1设置GPIOE时钟使能 RCC_MP_AHB4ENSETR[4]->1 0X50000A28LDR R0,0X50000A28 指定寄存器地址LDR R1,[R0] 将寄存器数值取出来放在R1中ORR R1,R1,#(0x1<<4) 将第4位设置为1STR R1,[R0] 将修改后的值写回去设置PE10为输出 GPIOE…

1161转进制(C语言)

一&#xff1a;题目 二&#xff1a;思路分析 1.首先该题目让我们使用递归求十进制转其他进制 2.其次&#xff0c;我们要知道十进制转换为其他进制怎么转换&#xff0c;以例题所给的数据为例 由此图可以看出&#xff0c;十进制转换为其他进制&#xff0c;是辗转相除法&#xf…

Android将自定义的SurfaceView保存为bitmap

正常将View保存为Bitmap的方法&#xff1a; private Bitmap getViewToBitmap(View view) { // layoutView(view);//创建Bitmap,最后一个参数代表图片的质量.Bitmap bitmap Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);if (bitma…

flutter 路由配置

get用法 进入新页面 Get.to(NextScreen());back回退操作 使用场景&#xff1a; 关闭Dialogs、SnackBars或者退出当前页面 Get.back(); off类似于replace操作 它会替拿当新页面换掉当前页面&#xff0c;并且新页面左上角没有返回按钮&#xff0c; Get.off(NextScreen()); off…

C++函数模板详解,轻松实现通用函数

C函数模板详解,轻松实现通用函数 函数模板 编写通用函数 您也可以为独立的函数编写模板。其语法与类模板类似。例如&#xff0c;您可以编写以下通用函数来在数组中查找一个值并返回其索引&#xff1a; static const size_t NOT_FOUND { static_cast<size_t>(-1) };te…

Win7如何修改MAC地址

MAC地址&#xff0c;又叫做物理地址、硬件地址&#xff0c;是用来定义网络设备的位置&#xff0c;一般情况下&#xff0c;MAC地址在网卡中是固定的&#xff0c;但不排除有人手动去修改自己的MAC地址。win7如何修改MAC地址?其实修改MAC地址的方法很简单&#xff0c;可以通过硬件…