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

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.mfbz.cn/a/391864.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

《Go 简易速速上手小册》第9章:数据库交互(2024 最新版)

文章目录 9.1 连接数据库 - Go 语言的海底宝藏之门9.1.1 基础知识讲解安装数据库驱动数据库连接 9.1.2 重点案例&#xff1a;用户信息管理系统准备数据库Go 代码实现连接数据库添加新用户查询用户信息用户登录验证主函数 9.1.3 拓展案例 1&#xff1a;批量添加用户准备数据库Go…

SCI文章复现 | GEO文章套路,数据下载和批次效应处理

原文链接&#xff1a; SCI文章复现 | GEO文章套路&#xff0c;数据下载和批次效应处理https://mp.weixin.qq.com/s/KBA67EJ7cCK5NDTUzrwJ2Q 一、前言 这是2024年春节后的第一个推送教程&#xff0c;我们也给大家赠送一个福利。将前期的付费教程免费推送给大家。其实&#xff…

第13章 网络 Page741~744 asio核心类 ip::tcp::socket

1. ip::tcp::socket liburl库使用"curl*" 代表socket 句柄 asio库使用ip::tcp::socket类代表TCP协议下的socket对象。 将“句柄”换成“对象”,因为asio库是不打折扣的C库 ip::tcp::socket提供一下常用异步操作都以async开头 表13-3 tcp::socket提供的异步操作 …

乡政府|乡政府管理系统|基于Springboot的乡政府管理系统设计与实现(源码+数据库+文档)

乡政府管理系统目录 目录 基于Springboot的乡政府管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户信息管理 2、活动信息管理 3、新闻类型管理 4、新闻动态管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选题推…

BeginCTF2024 RE WP 剩下的复现

12. goforfun&#xff08;寄&#xff09; 前面是一些无关紧要的初始化 下面看到疑似rc4 虽然函数支离破碎&#xff0c;但可以看到rc4的结构&#xff0c;异或的部分做了魔改 后面似乎是base64换表&#xff0c;但脚本跑不出来&#xff0c;这里的算法没搞懂&#xff0c;只能贴一下…

layui表格中使用cascader后导致表格滚动条消失

修改前&#xff0c;受影响页面 修改后最终想要的效果 修改方法

智慧校园规划建设方案

校园信息化建设呈现智能化、应用多样化发展趋势&#xff0c;多种技术和应用交叉渗透至校园生活的各个方面&#xff0c;全面的智慧校园时代已经到来。 对智慧校园的四大应用领域分析 智慧的教学 信息共享交互&#xff1a;建立信息发布、共享、传播与交互的公共平台 教学流程…

torch.utils.data

整体架构 平时使用 pytorch 加载数据时大概是这样的&#xff1a; import numpy as np from torch.utils.data import Dataset, DataLoaderclass ExampleDataset(Dataset):def __init__(self):self.data [1, 2, 3, 4, 5]def __getitem__(self, idx):return self.data[idx]def…

Linux: GDB 调试工具

目录 概念&#xff1a; Linux 下 debug 和 release 的区别&#xff1a; GDB 的使用 &#xff1a; 激活和进入工作模式&#xff1a; 查看文件的内容&#xff1a; 运行调试的文件&#xff1a; 打断点&#xff1a; 查看断点&#xff1a; 删除断点&#xff1a; 禁用断点…

17.3.1.3 灰度

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 灰度的算法主要有以下三种&#xff1a; 1、最大值法: 原图像&#xff1a;颜色值color&#xff08;R&#xff0c;G&#xff0c;B&a…

wayland(xdg_wm_base) client 使用 dmabuf 最简实例

文章目录 前言一、zwp_linux_dmabuf_v1 协议二、wayland client 使用 zwp_linux_dmabuf_v1 协议传递dma-buf代码实例1. wayland_dmabuf.c 代码实例2. xdg-shell-protocol.c 和 xdg-shell-client-protocol.h3. linux-dmabuf-unstable-v1-client-protocol.h 和 linux-dmabuf-unst…

如何在JavaScript中使用大于和小于运算符

在你的 JavaScript 程序中&#xff0c;你经常需要比较两个值&#xff0c;以确定一个是否大于另一个或小于另一个。这就是大于和小于运算符派上用场的地方。 在本文中&#xff0c;我们将通过代码示例更详细地介绍如何使用这些运算符。 &#xff08;本文内容参考&#xff1a;ja…

Stable Diffusion 模型下载:Beautiful Realistic Asians(美丽真实的亚洲人)

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十 下载地址 模型介绍 Beautiful Realistic Asians&#xff08;BRA&#xff09;模型是由作者自己训练…

阿里云服务器租用价格 2024年新版活动报价及租用收费标准

2024年最新阿里云服务器租用费用优惠价格表&#xff0c;轻量2核2G3M带宽轻量服务器一年61元&#xff0c;折合5元1个月&#xff0c;新老用户同享99元一年服务器&#xff0c;2核4G5M服务器ECS优惠价199元一年&#xff0c;2核4G4M轻量服务器165元一年&#xff0c;2核4G服务器30元3…

NumPyML 源码解析(四)

numpy-ml\numpy_ml\neural_nets\utils\__init__.py """ 神经网络特定的常见辅助函数。neural_nets.utils 模块包含神经网络特定的辅助函数&#xff0c;主要用于处理 CNNs。 """# 从当前目录下的 utils 模块中导入所有内容 from .utils import *…

天锐绿盾|防泄密系统|计算机文件数据\资料安全管理软件

“天锐绿盾”似乎是一款专注于防泄密和计算机文件数据/资料安全管理的软件。在信息安全日益受到重视的今天&#xff0c;这样的软件对于保护企业的核心数据资产和防止敏感信息泄露至关重要。 通用地址&#xff1a;www.drhchina.com 防泄密系统的主要功能通常包括&#xff1a; 文…

组合数的计算

1.由定义式直接算&#xff1a;n!/m!*(n-m)! #include <iostream> using namespace std; long long combine(long long m,long long n ){long long result1;for(int i1;i<n1;i){//n!result*i;}for(int i1;i<m1;i){//n!/m!result/i;}for(int i1;i<n-m1;i){//n!/(…

红蓝对抗:网络安全领域的模拟实战演练

引言&#xff1a; 随着信息技术的快速发展&#xff0c;网络安全问题日益突出。为了应对这一挑战&#xff0c;企业和组织需要不断提升自身的安全防护能力。红蓝对抗作为一种模拟实战演练方法&#xff0c;在网络安全领域得到了广泛应用。本文将介绍红蓝对抗的概念、目的、过程和…

问卷设计初探:题目类型概览与注意事项梳理

问卷法常被人们应用于社会调查中&#xff0c;它能反馈出最真实的社会信息。所以&#xff0c;很多企业为了最大程度地了解市场&#xff0c;也经常使用问卷调查法进行研究。不过&#xff0c;想要发挥出问卷法的最大用处&#xff0c;前提是要将问卷设计规范并且可量化。 想要设计…

【漏洞复现】企语iFair协同管理系统任意文件读取漏洞

Nx01 产品简介 企语iFair协同管理系统是一款专业的协同办公软件&#xff0c;该管理系统兼容性强&#xff0c;适合多种企业类型。 Nx02 漏洞描述 企语iFair协同管理系统存在任意文件读取漏洞&#xff0c;未经身份认证的攻击者可以通过此漏洞获取服务器敏感信息。 Nx03 产品主页…
最新文章