03 SS之返回JSON+UserDetail接口+基于数据库实现RBAC

news2024/11/29 10:40:33

1. 返回JSON

为什么要返回JSON

前后端分离成为企业应用开发中的主流,前后端分离通过json进行交互,登录成功和失败后不用页面跳转,而是给前端返回一段JSON提示, 前端根据JSON提示构建页面.

需求: 对于登录的各种状态 , 给前端返回JSON数据

1.1 在vo包下创建一个HttpResult对象, 存储返回的信息

vo即 value object值对象, 所有不存储在数据库中的对象就放在vo包下

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class HttpResult {
    private Integer code;
    private String msg;
    private Object data;
    public HttpResult(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

1.2 创建认证(登录)成功处理器AuthenticationSuccessHandler

当且仅当认证(登录)成功后, 该处理器开始工作, 给前端返回一个JSON

@Component
public class MyAuthenticationSuccessHandle implements AuthenticationSuccessHandler {

    //注入一个序列化器, 可以将JSON序列化, 反序列化
    @Resource
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=utf-8");
        //借助Lombok实现建造者模式, 并通过建造者模式创建对象

        HttpResult httpResult = HttpResult.builder()
                .code(1)
                .msg("登陆成功")
                .build();
               /*与普通new方法一样
                HttpResult httpResult = new HttpResult(200, "登录成功");
                *HttpResult中自定义的有参构造方法只写了code和msg,因此data可以选择性传
                HttpResult httpResult = new HttpResult(200, "登录成功",authentication);
                */
        //将对象转化为JSON
        String responseJson = objectMapper.writeValueAsString(httpResult);

        PrintWriter writer = response.getWriter();
        writer.println(responseJson);
    }
}

在安全配置类中注入登录成功处理器

@Configuration
public class MySSWebSecurityConfig extends WebSecurityConfigurerAdapter {

    // 注入登陆成功的处理器
    @Autowired
    private MyAuthenticationSuccessHandle successHandler;
    
    
        //放开登录页面权限,任何人都能尝试登录,否则登录界面都见不到
        http.formLogin().permitAll();
    }

1.3 登录失败处理器 , 无权限处理器都如法炮制

 @Resource
    private ObjectMapper objectMapper;


    /**
     * @param request 当前的请求对象
     * @param response 当前的响应对象
     * @param exception 失败的原因的异常
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        System.err.println("登陆失败");
        //设置响应编码
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=utf-8");
        //返回JSON出去
        HttpResult result=new HttpResult(-1,"登陆失败");
 
        //把result序列化为JSON字符串
        String responseJson = objectMapper.writeValueAsString(result);
        //响应出去
        PrintWriter out = response.getWriter();
        out.write(responseJson);
        out.flush();
    }
}

/**
 * 无权限的处理器
 */
@Component
public class AppAccessDeniedHandler implements AccessDeniedHandler {

    //声明一个把对象转成JSON的对象
@Resource
    private ObjectMapper objectMapper;

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        //设置响应编码
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=utf-8");
        //返回JSON出去
        HttpResult result=new HttpResult(-1,"您没有权限访问");
        //把result转成JSON
        String json = objectMapper.writeValueAsString(result);
        //响应出去
        PrintWriter out = response.getWriter();
        out.write(json);
        out.flush();
    }
}
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    // 注入登陆成功的处理器
    @Autowired
    private AutheticationSuccessHandle successHandler;

    // 注入登陆失败的处理器
    @Autowired
    private AppAuthenticationFailureHandler failureHandler;

    // 注入没有权限的处理器
    @Autowired
    private AppAccessDeniedHandler accessDeniedHandler;

    //  注入退出成功的处理器
    @Autowired
    private AppLogoutSuccessHandler logoutSuccessHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
        http.formLogin().successHandler(successHandler).failureHandler(failureHandler).permitAll();
        http.logout().logoutSuccessHandler(logoutSuccessHandler);
        http.authorizeRequests().mvcMatchers("/teacher/**").hasRole("teacher").anyRequest().authenticated();
    }
}

2. UserDetail接口

UserDetails接口是Spring Security进行身份验证和授权的核心接口之一。

通过实现UserDetails接口,可以定义如何存储和操作用户的详情信息,比如用户名、密码、权限等。

下面是几个编写一个实现了UserDetails接口的VO(Value Object)类的原因:

定制用户信息:Spring Security需要从应用程序获取用户的认证信息(如用户名、密码和权限)。大多数应用程序会将这些信息存储在数据库中,并且每个应用程序的用户模型都可能不同。通过实现UserDetails接口,可以根据应用程序的用户模型定制用户信息,使其包含Spring Security所需的任何必要信息。

权限和角色管理:UserDetails提供了获取用户权限(GrantedAuthority)的方法,这对于角色基于的访问控制至关重要。通过实现这个接口,可以在用户实体中很方便地管理用户的角色和权限,并且可以灵活地控制用户的访问权限。

灵活性和可扩展性:通过实现UserDetails接口,可以根据需要添加额外的属性和方法,从而为应用程序提供更大的灵活性和可扩展性。例如,可以添加手机号码、电子邮件地址或其他自定义的用户属性。

集成Spring Security的认证和授权机制:实现UserDetails接口是将应用程序的用户模型与Spring Security框架集成的一种方式。这样做可以利用Spring Security提供的强大的安全特性,如密码加密、会话管理、CSRF保护等,而无需从头开始实现这些安全特性。

提高代码的可读性和维护性:通过创建一个明确的VO类来实现UserDetails接口,可以提高代码的组织性、可读性和维护性。这样做有助于将安全框架的实现细节与应用程序的业务逻辑分离,使得代码更加清晰和易于维护。

3.基于数据库的认证

前面的自定义用户过程中, 代码是写死的, 这在实际使用中显然不现实.

基于数据库认证的核心思想是 : 定义一个VO类(这里起名SecurtiyUser),该类实现UserDeatails接口, 今后前端–后端–数据库交互用户信息就通过这个类

3.1 项目设计

3.1.1 导入数据库文件

最终包含5张表, 即第一章中提到的实现RBAC最少包括五张表 (用户表、角色表、用户角色表、权限表、角色权限表)
在这里插入图片描述

最基本的三张表是:

用户表 (Users):存储用户信息,每个用户都可以被分配一个或多个角色。至少包含用户ID和用户名等基本信息。

角色表 (Roles):存储角色信息,角色代表了一组权限的集合,用于控制访问权限。至少包含角色ID和角色名等基信息。

权限表 (Permissions):存储权限信息,权限定义了对系统资源的访问能力,比如读取、写入、修改等操作。至少包含权限ID和权限描述等基本信息。

然而,仅有上述三张表是不足以实现完整的RBAC功能的,因为它们没有建立用户、角色和权限之间的关联。因此,通常还需要额外的关联表来建立这些实体之间的多对多关系:

用户-角色关联表 (User_Roles):建立用户和角色之间的关系。这张表至少包含用户ID和角色ID,表示哪些用户属于哪些角色。

角色-权限关联表 (Role_Permissions):建立角色和权限之间的关系。这张表至少包含角色ID和权限ID,表示哪些角色拥有哪些权限。

3.1.2 中间件选择

存取数据需要一个中间件 , 这里选择MB

3.2 需求

根据用户名获取用户信息, 能获取到框架再自动比对密码

3.3 实现

3.3.1 根据用户名获取用户信息, 能获取到框架再自动比对密码

本次开发采用由下到上开发, 并逐层单元测试的方法进行

3.3.1.1 开发并测试dao(mapper接口层)

在这里插入图片描述

package com.sunsplanter.entity;

@Data
//实现Serializable接口, 方便对象序列化和反序列化
public class SysUser implements Serializable {
    private Integer userId;
    private String username;
    private String password;
    private String sex;
    private String address;
    private Integer enabled;
    private Integer accountNoExpired;
    private Integer credentialsNoExpired;
    private Integer accountNoLocked;
}
//实际就是之前的mapper接口SysUser
//要么在每个mapper中使用@Mapper声明, 要么在启动类中用@MapperScan指定扫描位置
@Mapper
public interface SysUserDao {
    /**
     * 根据用户名获取用户信息
     * 建议每个参数都使用@Param绑定,确保准确传递到xml文件中
     */
    SysUser getUserByUserName(@Param("username") String username);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.sunsplanter.dao.SysUserDao">
    <select id="getUserByUserName" resultType="sysUser">
        select user_id,username,password,sex,address,enabled,account_no_expired,credentials_no_expired,account_no_locked
        from sys_user where username=#{username}
    </select>

</mapper>

单元测试

	@Resource
	private SysUserDao sysUserDao;

	@Test
	void getUserByUserName() {
		SysUser sysUser = sysUserDao.getUserByUserName("obama");
		assertNotNull(sysUser);
	}

启动后左侧提示通过测试, 控制台输出信息SQL语句与SQL执行的结果
在这里插入图片描述

3.3.1.2 开发并测试service
public interface SysUserService{
	//根据用户名获取用户信息,以便确认存在此用户
	//此处不用@Param注解,因为MB注解只在DAO层
	SysUser.getUserByUserName(String userName)
}
@Service
@Slf4j
public class SysUserServiceImpl implements SysUserService {

    @Resource
    private SysUserDao sysUserDao;

    @Override
    public SysUser geUsertUserByName(String userName){
        return sysUserDao.getUserByUserName(userName);
    }
}

单元测试:

 @Resource
    private SysUserService sysUserService;
    
    @Test
    void getUserByUsername() {
        SysUser sysUser = SysUserService.getUserByname("obama");
        assertNotNull(sysUser);
    }

在这里插入图片描述

3.3.1.3 整合SS实现Service

本步骤最终效果最终与3.3.1.2一致, 只是本步骤额外整合了SS

整合SS验证用户是否存在的核心两步是:

  1. 定义一个VO类作为中介, 存在于后端三层结构与数据库之间
  2. 定义一个类实现UserDetailsService接口, 用来判断是否存在用户

在这里插入图片描述

定义一个VO类(这里起名SecurtiyUser),该类实现UserDeatails接口, 今后前端–后端–数据库交互用户信息就通过这个类

public class SecurityUser implements UserDetails {

    //这个类作为数据库与后端代码交互用户信息的中转站,自然要获取一个实体对象才能操作
    private  final SysUser sysUser;

    public SecurityUser(SysUser sysUser) {
        this.sysUser=sysUser;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        String userPassword=this.sysUser.getPassword();
        //注意清除密码
        this.sysUser.setPassword(null);
        return userPassword;

    }

    @Override
    public String getUsername() {
        return sysUser.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return sysUser.getAccountNoExpired().equals(1);
    }

    @Override
    public boolean isAccountNonLocked() {
        return sysUser.getAccountNoLocked().equals(1);
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return sysUser.getCredentialsNoExpired().equals(1);
    }

    @Override
    public boolean isEnabled() {
        return sysUser.getEnabled().equals(1);
    }
}

新建UserServiceImpl 实现UserDetailService接口, 判断是否存在该用户

@Service
//UserDetailsService接口只有一个方法,这个接口的唯一作用就是判断用户存不存在
public class UserServiceImpl implements UserDetailsService {
    @Resource
    private SysUserDao sysUserDao;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser sysUser = sysUserDao.getByUserName(username);
        if(null==sysUser){
            throw new UsernameNotFoundException("账号不存在");
        }

        //若通过username一步一步向下调用到数据库最终查到该用户, 
        //则利用中介SecurityUser将其返回
        return new SecurityUser(sysUser);
    }
}

单元测试:

	@Resource
	private UserServiceImpl userService;

	@Test
	void loadUserByUsername() {
		UserDetails userDetails = userService.loadUserByUsername("obama");
		assertNotNull(userDetails);
	}
3.3.1.4 controller
@RestController
@Slf4j
@RequestMapping("/student")
public class StudentController {
    @GetMapping("/query")
    public String queryInfo(){
        return "query student";
    }
    
    @GetMapping("/add")
    public String addInfo(){
        return "add  student!";
    }


@RestController
@Slf4j
@RequestMapping("/teacher")
public class TeacherController {
    @GetMapping("/query")
    @PreAuthorize("hasAuthority('teacher:query')")
    public String queryInfo(){
        return "I am a teacher!";
    }
}

最终符合权限的人可以正常查询, 即之前写入数据库中三张表确定权限 , tomas是教师, eric是学生
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
项目结构如图:
在这里插入图片描述
相比与之前 , 无非是多了两个东西:
SecurityUser类实现UserDetails, 实现数据的中转交互
UserServiceImpl实现UserDetailsService, 用于登录前前判断用户是否存在

3.4 查询数据库得到权限

3.4.1 存在的问题

根据表内容:
sys_role_permission和sy_permission
在这里插入图片描述

1号角色(管理员)拥有1,3,4,5,9,10,17号功能的权限
2号角色(教师)拥有2,3,4,5,9号功能的权限

表中我们预设的内容 , eric作为学生, 其应具有1,2,6,9号功能的权限, 即 学生管理/查询, 导出学生信息, 教师查询功能的权限

在上例中再拷贝一个之前写过的查询用户信息的类:

@RestController
@Slf4j
public class CurrentLoginUserInfoController {

    @GetMapping("/getLoginUserInfo")
    public Principal getLoginUserInfo(Principal principle){
        return principle;
    }

}

并且之后进入http://localhost:8080/getLoginUserInfo查询
发现eric作为学生, 并没有任何权限,
因此若我们进入teacher/quey, 会报403
在这里插入图片描述

这说明数据库中的权限并没有被我们查询出来

因此现在的目标是编写动态SQL语句, 根据传入的用户ID判断所属的角色, 以及拥有的权限,最终返回权限

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1455751.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

面向对象编程(三)

目录 1. 关键字&#xff1a;static 1.1 类属性、类方法的设计思想 1.2 static关键字 1.3 静态变量 1.3.1 语法格式 1.3.2 静态变量的特点 1.3.3 举例 1.3.4 内存解析 1.4 静态方法 1.4.1 语法格式 1.4.2 静态方法的特点 1.4.3 举例 2. 单例(Singleton)设计模式 2…

HarmonyOS router页面跳转

默认启动页面index.ets import router from ohos.router import {BusinessError} from ohos.baseEntry Component struct Index {State message: string Hello World;build() {Row() {Column() {Text(this.message).fontSize(50).fontWeight(FontWeight.Bold)//添加按钮&am…

简约火箭发射静态404错误页面源码

简约火箭发射静态404错误页面源码&#xff0c;源码由HTMLCSSJS组成,记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 蓝奏云下载&#xff1a;https://wfr.lanzout.com/iK…

烦人的鼠标唤醒电脑功能彻底禁用

1.常规操作禁用鼠标唤醒 搜索设备 电源管理取消勾选 这样操作后, 系统更新等各种原因, 又会失效, 需要反复操作, 很烦 2.关闭计算机管理的计划任务 禁用 3.查询唤醒原因 powercfg /waketimers powercfg /lastwake

多模态学习综述(MultiModal Learning)

最早开始关注到多模态机器学习是看到Jeff Dean在2019年年底NeurIPS大会上的一个采访报道&#xff0c;讲到了2020年机器学习趋势&#xff1a;多任务和多模态学习将成为突破口。 Jeff Dean 谈2020年机器学习趋势&#xff1a;多任务和多模式学习将成为突破口 站在2022年&#xff…

华为模拟器防火墙配置实验(四)

实验拓扑图 需求&#xff1a; 1&#xff0c;办公区设备可以通过电信链路和移动链路正常上网&#xff08;多对多的NAT&#xff0c;并且需要保存一个公网IP不能用来转换&#xff09; 2&#xff0c;分公司的设备可以通过总公司的移动链路和电信链路访问DMZ区域的http服务器 3&…

VSCODE上使用python_Django

接上篇 https://blog.csdn.net/weixin_44741835/article/details/136135996?csdn_share_tail%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22136135996%22%2C%22source%22%3A%22weixin_44741835%22%7D VSCODE官网&#xff1a; Editing Python …

python+django咖啡网上商城网站

全网站共设计首页、咖啡文化、咖啡商城、个人信息、联系我们5个栏目以及登录、注册界面&#xff0c;让用户能够全面的了解中国咖啡咖啡文化宣传网站以及一些咖啡知识、文化。 栏目一首页&#xff0c;主要放置咖啡的起源及发展进程的图文介绍&#xff1b;栏目二咖啡文化&#xf…

FPGA 高速接口(LVDS)

差分信号环路测试 1 概述 LVDS&#xff08;Low Voltage Differential Signalin&#xff09;是一种低振幅差分信号技术。它使用幅度非常低的信号&#xff08;约350mV&#xff09;通过一对差分PCB走线或平衡电缆传输数据。大部分高速数据传输中&#xff0c;都会用到LVDS传输。 …

多维时序 | Matlab实现基于VMD-DBO-LSTM、VMD-LSTM、LSTM的多变量时间序列预测

多维时序 | Matlab实现基于VMD-DBO-LSTM、VMD-LSTM、LSTM的多变量时间序列预测 目录 多维时序 | Matlab实现基于VMD-DBO-LSTM、VMD-LSTM、LSTM的多变量时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Matlab实现基于VMD-DBO-LSTM、VMD-LSTM、LSTM的多变量时间…

06 分频器设计

分频器简介 实现分频一般有两种方法&#xff0c;一种方法是直接使用 PLL 进行分频&#xff0c;比如在 FPGA 或者 ASIC 设计中&#xff0c;都可以直接使用 PLL 进行分频。但是这种分频有时候受限于 PLL 本身的特性&#xff0c;无法得到频率很低的时钟信号&#xff0c;比如输入 …

LeetCode 热题 100 Day01

哈希模块 哈希结构&#xff1a; 哈希结构&#xff0c;即hash table&#xff0c;哈希表|散列表结构。 图摘自《代码随想录》 哈希表本质上表示的元素和索引的一种映射关系。 若查找某个数组中第n个元素&#xff0c;有两种方法&#xff1a; 1.从头遍历&#xff0c;复杂度&#xf…

【Node-RED】安全登陆时,账号密码设置

【Node-RED】安全登陆时&#xff0c;账号密码设置 前言实现步骤密码生成setting.js 文件修改 安全权限 前言 Node-RED 在初始下载完成时&#xff0c;登录是无账号密码的。基于安全性考虑&#xff0c;本期博文介绍在安全登陆时&#xff0c;如何进行账号密码设置。当然&#xff…

spring boot自动装配及自动装配条件判断

第一步需要在pom.xml文件指定需要导入的坐标 要是没有自动提示需要检查maven有没有 实现代码 /*springboot第三方自动配置实现方法 * 什么是自动配置 自动配置就是springboot启动自动加载的类不需要在手动的控制反转自动的加入bean中 * * *//*第一种方案包扫描 不推荐因为繁琐…

CentOS 7.9如何禁止内核自动更新升级

要在 CentOS 7.9 系统中禁止内核自动更新&#xff0c;你可以通过配置 YUM&#xff08;Yellowdog Updater, Modified&#xff09;来实现。这里有几种方法可以阻止内核自动更新&#xff1a; 方法 1: 使用 exclude 选项在 YUM 配置中 编辑 YUM 的配置文件 /etc/yum.conf&#xff…

第三篇【传奇开心果系列】Python的文本和语音相互转换库技术点案例示例:pyttsx3实现语音助手经典案例

传奇开心果短博文系列 系列短博文目录Python的文本和语音相互转换库技术点案例示例系列 短博文目录一、项目背景和目标二、雏形示例代码三、扩展思路介绍四、与其他库和API集成示例代码五、自定义语音示例代码六、多语言支持示例代码七、语音控制应用程序示例代码八、文本转语音…

【C语言】Debian安装并编译内核源码

在Debian 10中安装并编译内核源码的过程如下&#xff1a; 1. 安装依赖包 首先需要确保有足够的权限来安装包。为了编译内核&#xff0c;需要有一些基础的工具和库。 sudo apt update sudo apt upgrade sudo apt install build-essential libncurses-dev bison flex libssl-d…

kettle中JavaScript使用例子

1.将输入日期减一后&#xff0c;得到对应格式的输出 输入为20240216则Alert输出20240215 日期减一。 对应函数参考&#xff1a; https://blog.csdn.net/doasmaster/article/details/112978529

激光条纹中心线提取算法FPGA实现方案

1 概述 激光条纹中心线提取是3D线激光测量领域一个较为基础且重要的算法。目前&#xff0c;激光条纹中心线提取已有多种成熟的算法&#xff0c;有很多相关的博客和论文。 激光条纹中心线提取的真实意义在于工程化和产品化的实际应用&#xff0c;而很多算法目前只能用于学术研究…

Days 34 ElfBoard 音频接口

音频接口介绍 音频模块采用了 NAU88C22 芯片&#xff0c;芯片数据信号使用 I2S 接口进行通讯&#xff0c;主要信号功能&#xff1a; SAI_MCLK&#xff1a;音频信号主时钟&#xff1b; SAI_BCLK&#xff1a;音频信号位时钟&#xff1b; SAI_SYNC&#xff1a;左右声道控制信号&am…