Spring security之授权

前言

本篇为大家带来Spring security的授权,首先要理解一些概念,有关于:权限、角色、安全上下文、访问控制表达式、方法级安全性、访问决策管理器

一.授权的基本介绍

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

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

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

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

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

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

方法一.

使用SQL语句的方式查询该角色的权限,并且可以对它进行修改

根据用户id查询出对应的角色信息

 SELECT
            *
        FROM
            sys_user a,
            sys_user_role b,
            sys_role_module c,
            sys_module d
        WHERE a.id = b.user_id and
            b.role_id=c.role_id and
            c.module_id = d.id and
            a.id=#{id}

 

根据用户ID查询出角色对应的权限信息

select
	m.url
from
	sys_user u,sys_user_role ur,sys_role r,sys_role_module rm,sys_module m
where
    u.id=ur.userid and ur.roleid=r.roleid and
    r.roleid=rm.roleid and rm.moduleid=m.id and
    u.id=#{userid} and url is not null

但是并不推荐使用这种方法,当我们在实际开发中,要考虑到不同的数据表可能来自不同的库中,使用SQL查询时就会出现链表查询不同库的表的情况,所以,更多的时候我们会使用Java利用不同的操作对表进行依次查询作为条件最终得到结果

方法二.利用Java对表单一查询然后作为查询条件,最终查询出结果

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService, UserDetailsService {

    @Autowired
    private IUserRoleService userRoleService;
    @Autowired
    private IRoleService roleService;
    @Autowired
    private IRoleModuleService roleModuleService;
    @Autowired
    private IModuleService moduleService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = getOne(new QueryWrapper<User>().eq("username", username));
        if(user==null){
            throw new UsernameNotFoundException("用户名无效");
        }
        //查询出身份
        //map遍历所有对象,返回新的数据放到新的集合中
        //filter 过滤流中的内容
        //collect将流中的元素变成一个集合
        List<Integer> role_ids = userRoleService
                .list(new QueryWrapper<UserRole>().eq("user_id", user.getId()))
                .stream().map(UserRole::getRoleId)
                .collect(Collectors.toList());
        //根据身份字段查询身份对应的名字字段
        List<String> roles = roleService
                .list(new QueryWrapper<Role>())
                .stream().map(Role::getRoleName)
                .collect(Collectors.toList());
        //根据身份id查询具备的权限id
        List<Integer> module_ids = roleModuleService
                .list(new QueryWrapper<RoleModule>().in("role_id", role_ids))
                .stream().map(RoleModule::getModuleId)
                .collect(Collectors.toList());
        //根据权限id查询对应的权限
        List<String> modules = moduleService
                .list(new QueryWrapper<Module>().in("id", module_ids))
                .stream().map(Module::getUrl)
                .collect(Collectors.toList());
        // 将权限字段加到身份中
        roles.addAll(modules);
        //将当前集合内容加到权限字段中
        List<SimpleGrantedAuthority> authorities = roles.stream()
                .map(SimpleGrantedAuthority::new)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        user.setAuthorities(authorities);
        return user;
    }
}

三.SpringSecurity配置类

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

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

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

    @Autowired
    private UserServiceImpl userDetailsService;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        //创建DaoAuthenticationProvider
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        //设置userDetailsService,基于数据库方式进行身份认证
        provider.setUserDetailsService(userDetailsService);
        //配置密码编码器
        provider.setPasswordEncoder(passwordEncoder());
        return new ProviderManager(provider);
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                //antMatchers 匹配对应的路径
                //permitAll 允许
                .antMatchers("/").permitAll()
                //anyRequest 其余所有请求
                //authenticated 登录
                .anyRequest().authenticated()
                .and()
                .formLogin()
                //loginPage 登录页面
                .loginPage("/")
                //设置处理登录请求的接口
                .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();
        return http.build();
    }

}

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

 四.管理控制Controller层的权限

@Controller
public class IndexController {

    @RequestMapping("/")
    public String toLogin() {
        return "login";
    }

    @RequestMapping("/userLogin")
    public String userLogin() {
        return "index";
    }

    @RequestMapping("/index")
    public String toIndex() {
        return "index";
    }

    @RequestMapping("/noAccess")
    public String noAccess() {
        return "accessDenied";
    }

    @ResponseBody
    @RequestMapping("/order_add")
    @PreAuthorize("hasAuthority('order:manager:add')")
    public String order_add() {
        return "订单新增";
    }

    @ResponseBody
    @PreAuthorize("hasAuthority('book:manager:add')")
    @RequestMapping("/book_add")
    public String book_add() {
        return "书本新增";
    }

}

在当前登录的用户必须拥有当前的权限字段才能进行访问,例如:book:manager:add

五.异常处理

AccessDeniedHandler是Spring Security提供的一个接口,用于处理访问被拒绝的情况。当用户尝试访问受保护资源但没有足够的权限时,Spring Security会调用AccessDeniedHandler来处理这种情况。

AccessDeniedHandler接口只有一个方法handle(),该方法接收HttpServletRequestHttpServletResponseAccessDeniedException三个参数。在handle()方法中,可以自定义响应的内容,例如返回一个自定义的错误页面或JSON响应。

创建AccessDeniedHandlerImpl类并实现AccessDeniedHandler接口,实现自定义的JSON响应。例如:

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

}

单独写一个接口进行实现,并将出现异常后的操作在里面实现

package com.yu.security.config;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yu.security.pojo.User;
import com.yu.security.resp.JsonResponseBody;
import com.yu.security.resp.JsonResponseStatus;
import com.yu.security.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Autowired
    private ObjectMapper objectMapper;

    // 在redis中定义一个键当登录失败是就对那个键的值进行加一

    //如果大于三就锁住

    @Autowired
    private IUserService userService;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        if(1==2){
            User user = userService.getOne(new QueryWrapper<User>().eq("username", request.getParameter("username")));
            user.setAccountNonLocked(false);
            userService.updateById(user);
        }
        objectMapper.writeValue(response.getOutputStream(), JsonResponseBody.other(JsonResponseStatus.LOGIN_FAILURE));
    }

}

在当前例子中:我们通过在配置类引入当前接口,并实现当前接口,在实现类中,对登录失败进行 对应的操作,在Redis中定义一个键当登录失败是就对那个键的值进行加一,如果大于三就对当前账号进行冻结


                

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

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

相关文章

2024最新苹果手机APP软件下架了,怎么安装?

如果你是一个iPhone用户&#xff0c;你可能会遇到这样的情况&#xff1a;你想下载或更新一个软件&#xff0c;但发现它已经从APP Store上消失了。这可能是因为软件违反了苹果的规则&#xff0c;或者开发者主动撤下了软件。那么&#xff0c;这种情况下&#xff0c;你还能安装或使…

OpenCV | 霍夫变换:以车道线检测为例

霍夫变换 霍夫变换只能灰度图&#xff0c;彩色图会报错 lines cv2.HoughLinesP(edge_img,1,np.pi/180,15,minLineLength40,maxLineGap20) 参数1&#xff1a;要检测的图片矩阵参数2&#xff1a;距离r的精度&#xff0c;值越大&#xff0c;考虑越多的线参数3&#xff1a;距离…

Unreal5.3 PCG 笔记

目录 ElectricDreams场景功能移动中间山体向周围随机生成倒下的树干树干上随机生成的植被 ElectricDreams场景功能 移动中间山体向周围随机生成倒下的树干 配置内容 中心山体Spline周围沟渠Spline&#xff08;土堆&#xff09;PCG规则 主要功能节点 SplineSample&#xff08;…

基于Java web的住院管理系统论文

目 录 目 录 I 摘 要 III ABSTRACT IV 1 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 研究内容 2 2 系统开发环境 3 2.1 vue技术 3 2.2 JAVA技术 3 2.3 MYSQL数据库 3 2.4 B/S结构 4 2.5 SSM框架技术 4 3 系统分析 5 3.1 可行性分析 5 3.1.1 技术可行性 5 3.1.2 操作可行性 5 3…

Linux 操作系统(用户注册、删除、权限修改等)

添加用户 格式&#xff1a;useradd 用户名 ( 添加用户 ) passwd 用户名 (给用户设置密码) Linux 用户切换原则&#xff1a; 高权限向低权限切换无需输入密码 低权限向高权限或同级用户切换需要输入密码 用户切换&#xff1a;su su – 用户名 &#xff08;用户切换&#x…

【经典LeetCode算法题目专栏分类】【第9期】深度优先搜索DFS与并查集:括号生成、岛屿问题、扫雷游戏

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能AI、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推荐--…

蓝牙技术在物联网中的应用

随着蓝牙技术的不断演进和发展&#xff0c;蓝牙已经从单一的传统蓝牙技术发展成集传统蓝牙。高速蓝牙和低耗能蓝牙于一体的综合技术&#xff0c;不同的应用标准更是超过40个越来越广的技术领域和越来越多的应用场景&#xff0c;使得目前的蓝牙技术成为包含传感器技术、识别技术…

DRF从入门到精通三(反序列化数据校验源码分析、断言Assert、DRF之请求、响应)

文章目录 一、反序列化数据校验源码分析二、断言Assert三、DRF之请求、响应Request类和Response类请求中的Request 能够解析前端传入的编码格式响应中的Response能够响应的编码格式 一、反序列化数据校验源码分析 反序列化数据校验&#xff0c;校验顺序为&#xff1a;先校验字段…

懂机器学习?先来回答这三个问题 >>

机器学习是一种数据分析技术&#xff0c;让计算机学习人类和动物与生俱来的能力&#xff1a;从经验中学习。 机器学习算法使用计算方法直接从数据中“学习”信息&#xff0c;而不依赖于预定方程作为模型。 随着可用于学习的样本数量的增加&#xff0c;算法也会相应地提高性能。…

探索栈数据结构:深入了解其实用与实现(c语言实现栈)

上次结束了链表部分的内容&#xff1a;链接未来&#xff1a;深入理解链表数据结构&#xff08;二.c语言实现带头双向循环链表&#xff09; 然而&#xff0c;当我们涉及特定问题时&#xff0c;另一个非常有用的数据结构也开始显得至关重要——栈 栈与链表有着截然不同的特性&a…

MySQL数据库基础和基本的增删改查操作

目录 前瞻 数据库的基本概念 数据库管理系统&#xff08;DBMS&#xff09; 数据库系统(DBS) 数据库类型和常用数据库 关系型数据库 SQL 非关系型数据库 NoSQL SQL语句 简介 SQL语句分类 常用的数据类型 MySQL的六大约束特性 SQL语句的使用 创建及删除数据库和表 …

H5小游戏加固方案

今年的中国游戏产业年会上&#xff0c;小游戏成了万众瞩目的行业新风口。据伽马数据统计&#xff1a;2023年小游戏市场规模可达200亿元&#xff0c;同比增长300% 。 小游戏有着分发更精准、用户转化率更高、研发成本更低、场景适用性更强等优势&#xff0c;具备巨大的市场潜力…

openGauss学习笔记-171 openGauss 数据库运维-备份与恢复-导入数据-深层复制

文章目录 openGauss学习笔记-171 openGauss 数据库运维-备份与恢复-导入数据-深层复制171.1 使用CREATE TABLE执行深层复制171.1.1 操作步骤 171.2 使用CREATE TABLE LIKE执行深层复制171.2.1 操作步骤 171.3 通过创建临时表并截断原始表来执行深层复制171.3.1 操作步骤 openGa…

服务器数据恢复-昆腾存储StorNext文件系统下raid5数据恢复案例

服务器数据恢复环境&#xff1a; 昆腾某型号存储&#xff0c;StorNext文件存储系统。 共有9个分别配置了24块磁盘的磁盘柜&#xff0c;其中8个磁盘柜存放普通数据&#xff0c;1个磁盘柜存放元数据。 存放元数据的磁盘柜中的24块磁盘组建了8组RAID1阵列和1组4盘RAID10阵列&#…

Java 虚拟机中的内存结构

1 内存结构 1.1 程序计数器 1.1.1 定义 Program Counter Register 程序计数器&#xff08;寄存器&#xff09; 作用&#xff1a;是记住下一条 jvm 指令的执行地址 特点&#xff1a; 是线程私有的&#xff08;每个线程独有自己的一份&#xff09;不会存在内存溢出 1.1.2 作…

、写入Shellcode到注册表上线

其实本质就是将shellcode写入到注册表中&#xff0c;然后读取注册表中的shellcode&#xff0c;然后创建线程去执行shellcode。 如下图: 写入注册表shellcode 这里将shellcode写入到注册表中&#xff0c;在我们需要的时候再去读取然后执行。 这里用到如下两个Windows API函…

Zookeeper-应用实战

Zookeeper Java客户端实战 ZooKeeper应用的开发主要通过Java客户端API去连接和操作ZooKeeper集群。 ZooKeeper官方的Java客户端API。 第三方的Java客户端API&#xff0c;比如Curator。 ZooKeeper官方的客户端API提供了基本的操作:创建会话、创建节点、读取节点、更新数据、…

Leetcode—415.字符串相加【简单】

2023每日刷题&#xff08;六十八&#xff09; Leetcode—415.字符串相加 实现代码 class Solution { public:string addStrings(string num1, string num2) {string ans;int len1 num1.size();int len2 num2.size();int i len1 - 1, j len2 - 1;int sum 0, c 0;while(i…

面试题 01.01. 判定字符是否唯一(优质解法)

链接&#xff1a;面试题 01.01. 判定字符是否唯一 代码&#xff1a; class Solution {public boolean isUnique(String astr) {//s[i]仅包含小写字母&#xff0c;数据范围小于 32 位&#xff0c;我们可以使用 int 变量的比特位来代替数组// 每个小写字符对应 bitMap 中的一个比…

DETR 【目标检测里程碑的任务】

paper with code - DETR 标题 End-to-End Object Detection with Transformers end-to-end 意味着去掉了NMS的操作&#xff08;生成很多的预测框&#xff0c;nms 去掉冗余的预测框&#xff09;。因为有了NMS &#xff0c;所以调参&#xff0c;训练都会多了一道工序&#xff0c…