【Spring Security】打造安全无忧的Web应用--使用篇

news2025/1/16 13:48:46

🥳🥳Welcome Huihui's Code World ! !🥳🥳

接下来看看由辉辉所写的关于Spring Security的相关操作吧 

目录

🥳🥳Welcome Huihui's Code World ! !🥳🥳

一.Spring Security中的授权是什么

二. 基于单体项目的授权

1.修改User配置角色和权限

2.修改SpringSecurity配置类

3.测试

三.基于前后端分离项目的授权 

JsonResponseBody

JsonResponseStatus

修改SpringSecurity配置类


在正式讲解今天的内容之前,我们还需要了解一个东西--RBAC数据模型,RBAC(Role-Based Access Control,基于角色的访问控制)数据模型是一种访问控制模型,它使用角色作为访问控制的基本单元。在这个模型中,用户被分配到不同的角色,每个角色代表着一组操作或任务。系统管理员将访问权限授予角色,而不是直接授予给用户。当用户需要执行某个操作时,系统可以检查该用户是否拥有执行此操作所需的角色,并根据角色的权限来授权或拒绝访问。RBAC 数据模型可以有效地管理复杂的访问控制策略,提高系统安全性和管理效率。

遇到复杂的权限管理,都是配置在数据库中的,一般由五张表组成,即RBAC。

  1)RBAC数据模型的五张表

          用户表,存储用户信息,由业务人员维护;

          角色表,存储角色信息,由业务人员维护 ;

          资源表,存储资源信息(菜单、按钮及其URL),由开发人员维护;

          用户-角色关系表,存储用户和角色的对应关系,多对多,由业务人员维护;

          角色-资源关系表,存储角色和资源的对应关系 ,多对多,由业务人员维护;

一.Spring Security中的授权是什么

        在Spring Security中,授权是指系统根据用户的角色和权限决定用户是否有权访问特定的资源或执行特定的操作。

        Spring Security提供了多种授权机制,包括基于角色的授权和基于权限的授权

                基于角色的授权是指系统将用户分配到不同的角色,每个角色具有不同的权限,然后通过判断用户是否具有特定的角色来进行授权。例如,系统可以定义"ADMIN"角色和"USER"角色,管理员拥有更高的权限,可以访问和操作更多的资源,而普通用户只能访问受限资源。

                基于权限的授权是指系统将具体的权限授予用户,用户可以直接拥有某个权限,而不需要通过角色间接获得。例如,系统可以定义"READ"、"WRITE"、"DELETE"等权限,根据用户所拥有的权限判断其是否有权对资源进行读取、写入或删除操作。

        Spring Security还支持细粒度的授权控制,可以通过注解、表达式或配置文件等方式来定义授权规则。可以使用@PreAuthorize@PostAuthorize@Secured等注解来标注方法或类,以声明访问资源的权限要求。

        通过授权机制,Spring Security可以保护应用程序免受未经授权的访问,确保只有具备合适权限的用户才能访问敏感资源,提高应用程序的安全性

二. 基于单体项目的授权

1.修改User配置角色和权限

这里也可以使用多表联查的方式去做,不过考虑到在数据量过于庞大的情况下,我们常常会进行分库分表,所以我们这里采用的是流的形式来编写的

package com.wh.security.config;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wh.security.model.*;
import com.wh.security.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @author是辉辉啦
 * @create 2023-12-23-14:21
 */
@Component
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private IUserService userService;
    @Autowired
    private IUserRoleService userRoleService;
    @Autowired
    private IRoleService roleService;
    @Autowired
    private IRoleModuleService roleModuleService;
    @Autowired
    private IModuleService moduleService;
    /**
     * 实现Spring Security内置的UserDetailService接口,重写loadUserByUsername方法实现数据库的身份校验
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户名查询数据库中用户信息
        User user = userService.getOne(new QueryWrapper<User>().eq("username", username));
        //判断用户是否存在
        if(Objects.isNull(user)) {
            throw new UsernameNotFoundException("该用户不存在!!!");
        }
        //先拿到用户的身份id
        List<Integer> roleIds = userRoleService.list(new QueryWrapper<UserRole>().eq("user_id", user.getId()))
                .stream().map(UserRole::getRoleId)
                .collect(Collectors.toList());
        //根据身份id再拿到身份所对应的角色名称roleName
        List<String> roleName = roleService.list(new QueryWrapper<Role>().in("role_id", roleIds))
                .stream().map(Role::getRoleName)
                .collect(Collectors.toList());
        //然后通过身份id再拿到身份和权限的中间表的id
        List<Integer> moduleIds = roleModuleService.list(new QueryWrapper<RoleModule>().in("role_id", roleIds))
                .stream().map(RoleModule::getModuleId)
                .collect(Collectors.toList());
        //再通过这个中间表的id拿到对应的可访问的模块的url
        List<String> moduleUrls = moduleService.list(new QueryWrapper<Module>().in("id", moduleIds))
                .stream().map(Module::getUrl)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        //把roleName和moduleUrls整合在一起
        roleName.addAll(moduleUrls);
        //把拿到的角色的名称以及角色可访问到的url赋值给user中的哪个管理角色权限的字段
        // roles [管理员,普通用户,book:manager:add,book:manager:list]
        List<SimpleGrantedAuthority> authorities = roleName.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        user.setAuthorities(authorities);
        return user;
    }
}

2.修改SpringSecurity配置类

加一个注解

@EnableGlobalMethodSecurity(prePostEnabled = true)

@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标准的注解之一,用于在方法执行之前对访问进行权限验证

3.测试

使用管理员角色登录

使用普通角色登录

如果说没有权限或者是退出登录等其他操作,都会是跳到指定的页面

三.基于前后端分离项目的授权 

关于前面的配置都是和上面所说的一样,这里就不过多阐述了,但是前后端分离的项目就是不能够直接跳转页面了,而是要返回响应的内容。所以我们需要写一个专门的响应类

JsonResponseBody

package com.wh.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);
    }

}

JsonResponseStatus

package com.wh.security.resp;

import lombok.Getter;

@Getter
public enum JsonResponseStatus {

    OK(200, "OK"),
    UN_KNOWN(500, "未知错误"),
    RESULT_EMPTY(1000, "查询结果为空"),
    NO_ACCESS(3001, "没有权限"),
    NO_LOGIN(4001, "没有登录"),
    LOGIN_FAILURE(5001, "登录失败"),
    ;

    private final Integer code;
    private final String msg;

    JsonResponseStatus(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

}

并且需要在配置类中将登录成功或者权限认证失败等这些需要跳转页面的操作全部都修改掉

修改SpringSecurity配置类

package com.wh.security.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wh.security.resp.JsonResponseBody;
import com.wh.security.resp.JsonResponseStatus;
import com.wh.security.service.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.annotation.Resource;
import javax.sql.DataSource;

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

    // 自动装配自定义的用户服务实现类
    @Autowired
    private MyUserDetailsService myUserDetailsService;
    // 自动装配数据源
    @Resource
    public DataSource dataSource;
    // 自动装配对象映射器
    @Autowired
    private ObjectMapper objectMapper;
    // 自动装配自定义的登录失败处理器
    @Autowired
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;

    // 创建密码编码器实例
    @Bean
    public PasswordEncoder bcryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // 创建模拟用户数据的服务实现类
    // @Autowired
    // private UserDetailsService userDetailsService;

    // 获取认证管理器(认证管理器),登录时认证使用(基于数据库方式)
    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        // 创建DaoAuthenticationProvider实例
        DaoAuthenticationProvider provider=new DaoAuthenticationProvider();
        // 设置userDetailsService,基于数据库方式进行身份认证
        provider.setUserDetailsService(myUserDetailsService);
        // 配置密码编码器
        provider.setPasswordEncoder(bcryptPasswordEncoder());
        return new ProviderManager(provider);
    }

    // 配置持久化Token方式,注意tokenRepository.setCreateTableOnStartup()配置
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        // 设置为true要保障数据库该表不存在,不然会报异常哦
        // 所以第二次打开服务器应用程序的时候得把它设为false
        tokenRepository.setCreateTableOnStartup(false);
        return tokenRepository;
    }

    // 配置安全过滤器链(SecurityFilterChain)
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/", "/noAccess", "/toLogin").permitAll()
                //所有请求全部需要登录
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/toLogin")
                //设置处理登录请求的接口
                .loginProcessingUrl("/userLogin")
                .usernameParameter("username")
                .passwordParameter("password")
                //登录成功
                .successHandler((req, resp, auth) -> {
                    Object user = auth.getPrincipal();
                    objectMapper
                            .writeValue(resp.getOutputStream(), JsonResponseBody.success(user));
                })
                //登录失败
                .failureHandler(myAuthenticationFailureHandler)
                .and()
                .exceptionHandling()
                //权限不足
                .accessDeniedHandler((req, resp, ex) -> {
                    objectMapper
                            .writeValue(resp.getOutputStream(), JsonResponseBody.other(JsonResponseStatus.NO_ACCESS));
                })
                //没有认证
                .authenticationEntryPoint((req, resp, ex) -> {
                    objectMapper
                            .writeValue(resp.getOutputStream(), JsonResponseBody.other(JsonResponseStatus.NO_LOGIN));
                })
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/");
        http.csrf().disable(); //禁用CSRF保护
        return http.build();
    }
}

这样就不会跳页面了,而是返回出对应的数据

好啦,今天的分享就到这了,希望能够帮到你呢!😊😊   

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

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

相关文章

阿贝云云服务器

最近&#xff0c;我有幸获得了阿贝云提供的免费云服务器&#xff0c;阿贝云_免费云服务器、高防服务器、虚拟主机、免费空间、免费vps主机服务商!并在使用过程中有了一些深刻的体验和感受。在这篇博客中&#xff0c;我将分享我对阿贝云免费云服务器的使用感受和评价。 首先&am…

【iOS】UICollectionView

文章目录 前言一、实现简单九宫格布局二、UICollectionView中的常用方法和属性1.UICollectionViewFlowLayout相关属性2.UICollectionView相关属性 三、协议和代理方法&#xff1a;四、九宫格式的布局进行升级五、实现瀑布流布局实现思路实现原理代码调用顺序实现步骤实现效果 总…

需求分析工程师岗位的职责描述(合集)

需求分析工程师岗位的职责描述1 职责&#xff1a; 1&#xff0c;负责需求调研&#xff0c;对需求进行分析&#xff0c;编写解决方案、需求规格说明书等 2&#xff0c;根据需求制作原型&#xff0c;并负责原型展示以及客户沟通等工作 3&#xff0c;负责向技术团队精确地传达业务…

排序算法——桶排序

把数据放进若干个桶&#xff0c;然后在桶里用其他排序&#xff0c;近乎分治思想。从数值的低位到高位依次排序&#xff0c;有几位就排序几次。例如二位数就排两次&#xff0c;三位数就排三次&#xff0c;依次按照个十百...的顺序来排序。 第一次排序&#xff1a;50 12 …

Unity手机移动设备重力感应

Unity手机移动设备重力感应 一、引入二、介绍三、测试成果X Y轴Z轴横屏的手机&#xff0c;如下图竖屏的手机&#xff0c;如下图 一、引入 大家对重力感应应该都不陌生&#xff0c;之前玩过的王者荣耀的资源更新界面就是使用了重力感应的概念&#xff0c;根据手机的晃动来给实体…

EPROM 作为存储器的 8 位单片机

一、基本概述 TX-P01I83 是以 EPROM 作为存储器的 8 位单片机&#xff0c;专为多 IO 产品的应用而设计&#xff0c;例如遥控器、风扇/灯光控制或是 玩具周边等等。采用 CMOS 制程并同时提供客户低成本、高性能等显着优势。TX-P01I83 核心建立在 RISC 精简指 令集架构可以很容易…

SQL优化-深入了解SQL处理流程原理(Server层与存储引擎交互、数据管理结构)

做SQL优化的前提就必须要清楚当一个SQL被发送到Mysql时&#xff0c;它的处理流程。下面通过一个SQL优化分析过程来详细了解Mysql对SQL执行流程原理。 1、Mysql架构 在上篇文章中已经做了简单架构介绍&#xff0c;Mysql架构分为两个大的组件&#xff1a;Server层、存储层 Mysq…

vue3项目 - Eslint 配置代码风格

Eslint 自定义配置 总结&#xff1a; Prettier &#xff08;代码规范的插件&#xff0c;格式化 &#xff09;---> 美观 Eslint &#xff08;规范、纠错、检验错误 &#xff09;-----> 纠错 首先&#xff0c;禁用 Prettier 插件&#xff0c;安装 ESLint 插件&#x…

【量化金融】证券投资学

韭菜的自我修养 第一章&#xff1a; 基本框架和概念1.1 大盘底部形成的技术条件1.2 牛市与熊市1.3 交易系统1.3.1 树懒型交易系统1.3.2 止损止损的4个技术 第二章&#xff1a;证券家族4兄弟2.1 债券&#xff08;1&#xff09;债券&#xff0c;是伟大的创新&#xff08;2&#x…

案例147:基于微信小程序的酒店管理系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

重构云计算,打造 AI 原生时代的云计算产品与技术体系,实现 AI 零距离

概述 自 ChatGPT 大模型横空出世以来&#xff0c;文心一言、通义千问等诸多大模型接踵而来&#xff0c;感觉这个世界每天都在发生着翻天覆地的变化。 今年很有幸&#xff0c;参与了云栖的盛宴&#xff0c;当时被震惊到瞠目结舌&#xff0c;12 月 20 日百度云智能云智算大会&a…

使用Python自己写了一个Renpy 汉化插件

之前看了很多教程都是Renpy怎么解包、怎么创建翻译文件&#xff0c;对翻译过程没有过多讲解&#xff0c;就根据翻译文件写了一个小程序&#xff0c;如果需要的可以自行下载使用。 使用方法&#xff1a; 1.按照正常unrpa的解包方式renpy进行解包&#xff1b; 2.使用renpy-sdk…

Bloom过滤器

Bloom过滤器 一、概述二、原理三、优缺点1. 优点2.缺点 四、Bloom过滤器在比特币中的应用五、项目应用步骤1. pom.xml引入依赖2. 样例代码 六、Java版简易实现 一、概述 Bloom过滤器是一个允许用户描述特定的关键词组合而不必精确表述的基于概率的过滤方法。它能让用户在有效搜…

新型智慧城市解决方案:PPT全文56页,附下载

关键词&#xff1a;智慧城市解决方案&#xff0c;智慧城市管理技术&#xff0c;智慧城市建设&#xff0c;数字城市建设 一、智慧城市宏观形势 1、政策支持&#xff1a;出台了一系列政策&#xff0c;鼓励和支持智慧城市的发展。这些政策为智慧城市的建设提供了政策保障和资金支…

Confluent 与阿里云将携手拓展亚太市场,提供消息流平台服务

10 月 31 日&#xff0c;杭州云栖大会上&#xff0c;阿里云云原生应用平台负责人丁宇宣布&#xff0c;Confluent 成为阿里云技术合作伙伴&#xff0c;合作全新升级&#xff0c;一起拓展和服务亚太市场。 本次合作伙伴签约&#xff0c;阿里云与消息流开创领导者 Confluent 将进一…

Zabbix6 使用Agent2实现证书监控的详细步骤

目标 我们的目标是通过获取网站的证书信息来实现网站证书监控。 使用agent2的key 只需使用其中的key&#xff0c;就能实现我们的目标功能。然而&#xff0c;由于它返回的是json格式的数据&#xff0c;我们需要根据数据来配置监控项目&#xff08;item&#xff09;和触发器&am…

Jenkins自动化部署之后端

准备工作参考本人另外几篇Jenkins相关的文章 新建任务 添加参数配置 字符串参数&#xff1a;分支名称 多选框&#xff1a;项目名称&#xff08;Extended Choice Parameter插件必备&#xff0c;插件安装参考我另外的文章&#xff09; 这个分割规则自定义。只要根据Jenkins…

Linux Centos-7.5_64bit 系统等保测评内容

一、身份鉴别 a) 应对登录的用户进行身份标识和鉴别&#xff0c;身份标识具有唯一性&#xff0c;身份鉴别信息具有复杂度要求并定期更换 指引&#xff1a;1.观察管理员登录方式。2.查看系统配置&#xff0c;记录配置参数。&#xff08;more /etc/pam.d/system-auth、more /et…

Conda命令、Miniconda3基础安装使用教程

Conda命令、Miniconda3基础安装使用教程 Minicodna3下载地址&#xff1a;https://docs.conda.io/projects/miniconda/en/latest/index.html 正常安装完毕后&#xff0c;出现conda的控制台&#xff1a; 默认的源下载太慢&#xff0c;换清华源&#xff0c;依次执行以下命令&…

Python内置类属性__class__属性的使用教程

概要 Python作为一种高级编程语言&#xff0c;提供了丰富的功能和灵活性&#xff0c;使得开发人员能够更加方便地处理各种任务。其中一个强大的功能是内置类属性__class__属性。本文将详细介绍__class__属性的用法&#xff0c;帮助读者更好地理解和利用这一功能。 第一部分&am…