【第2期】Springboot如何快速集成SpringSecurity

简单介绍

本专栏主要结合实战讲解,不过多介绍细节的概念,概念可以通过搜索引擎查找,一搜一大把,切入正题。
本专栏的实战项目是基于Springboot+SpringSecurity+RSA+JWT+VUE的全栈开发项目,每个环节都会专门讲,本期讲如何集成SpringSecurity

  • 主要讲解一下部分:

1、基于RBAC的权限系统
2、SpringSecurity核心安全配置
3、登录过滤器
4、权限校验过滤器
5、默认的登录接口

一、基于RBAC的权限系统

设计以下表,用于管理维护用户的角色和权限(表的详细设计可见第1期)

common_user: 用户表,系统的用户都存在这张表里
common_role:角色表,用于表示系统有哪些角色
common_permission:权限表,也可以理解为资源,用于表示系统中所有的资源权限,粒度可大可小
common_user_role:用户和角色的关联表,表示某个用户拥有哪几种角色
common_role_permission:角色和权限的关联表,表示某个角色拥有哪些资源的访问权限

SpringSecurity可以基于角色进行粗粒度的权限控制,也可以基于权限进行细粒度的权限控制,还可以讲二者混合使用进行复杂的权限控制

二、SpringSecurity核心安全配置

1、创建auth模块

在这里插入图片描述

2、添加SpringSecurity的依赖

这里没有指定版本号,是因为在root的pom.xml中定义了依赖管理,统一进行版本号的管理,这是常规操作

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
</dependency>

3、创建一个SpringSecurity自定义配置类

public class WebSecurityConfig extends WebSecurityConfigurerAdapter{}
核心配置如下:

http.csrf:禁用跨站请求伪造。从 Spring Security4 开始 CSRF 防护默认开启。默认会拦截请求。进行 CSRF 处理。CSRF 为了保证不是其他第三方网站访问,要求访问时携带参数名为_csrf 值为 token(token 在服务端产生)的内容,如果token 和服务端的 token 匹配成功,则正常访问。
anthorizeRequests():表示后面的资源通过认证即可访问
antMatchers(“xxxx”).permitAll():SpringSecurity允许这类资源被所有人访问
addFilter:添加自定义的过滤器,这里是添加了登录过滤器和权限认证过滤器
sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS):关闭session管理,前后端分离不涉及session,所以关闭
在这里插入图片描述
如上图,注释掉的几行permitAll如果放开之后,swagger页面可以打开吗?
答案是:如果没有下面的过滤器就可以打开,如果有,那就无法打开,因为会被权限认证过滤器拦截,所以这里配置了上述白名单资源无效

4、有效配置绕过双重认证的白名单

双重认证:SpringSeurity认证+JWT Token认证(这里不展开讲,后续会讲)

  • 如何绕过双重认证:在当前的安全配置类,重写configure(WebSecurity web)方法,将下面的白名单资源和接口设置为忽略认证即可
    在这里插入图片描述

三、登录过滤器

1、创建过滤器,继承UsernamePasswordAuthenticationFilter

public class UserLoginFilter extends UsernamePasswordAuthenticationFilter {
}

2、重写登录成功处理方法

这里可按照自己业务逻辑去实现登录成功以后的逻辑。
这里的逻辑是:

1、获取当前登录成功的用户
2、根据RSA私钥以及用户信息生成JWT Token,有效期设置为24小时(关于RSA安全加解密和JWT的生成和反序列化为用户信息后续为展开讲)
3、将登录用户信息写入Redis缓存,过期时间与JWT时间保持一致
4、将token放入响应header
5、组装接口响应体并响应给接口调用方

在这里插入图片描述
疑问:可能有人发现了,用户信息从哪里来的,和数据库的common_user如何关联得起来?

3、登录的用户信息从哪里来

  • 用户持久化对象实现SpringSecurity的UserDetails接口
    此接口用于获取用户的关键信息,如账号、密码、权限集合、是否没过期、是否没被锁定、是否认证没过期等等,这些逻辑可以按照业务重写
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class UserPo extends BaseEntity<Long> implements UserDetails {
    private String loginName;

    private String password;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    private LocalDateTime loginExpireTime;

    private LoginStatusEnum status;

    private String phone;

    @JsonFormat(pattern = "yyyy-MM-dd")
    @JsonSerialize(using = LocalDateSerializer.class)
    @JsonDeserialize(using = LocalDateDeserializer.class)
    private LocalDate born;

    private Integer failCount;

    private List<RolePo> roles;

    private List<PermissionPo> permissions;

    @JsonIgnore
    @Override
    public Collection<GrantedAuthority> getAuthorities() {
        // 保存的角色要加上ROLE_,接口配置角色时不带ROLE_
        List<GrantedAuthority> authorities = Lists.newArrayList();
        if (roles != null) {
            for (RolePo rolePo : roles) {
                authorities.add(new SimpleGrantedAuthority("ROLE_" + rolePo.getName()));
            }
        }
        if (permissions != null) {
            for (PermissionPo perm : permissions) {
                authorities.add(new SimpleGrantedAuthority(perm.getName()));
            }
        }
        return authorities;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @JsonProperty(value = "loginName")
    @Override
    public String getUsername() {
        return this.loginName;
    }

    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @JsonIgnore
    @Override
    public boolean isEnabled() {
        return true;
    }
  • 数据库的用户信息从哪里来呢,请看用户接口及实现类
public interface UserService extends BaseService<UserPo>, UserDetailsService {
}
@Service
public class UserServiceImpl extends BaseServiceImpl<UserPo> implements UserService {
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RoleService roleService;

    @Autowired
    PermissionService permissionService;

    @Override
    public BaseMapper<UserPo> getMapper() {
        return userMapper;
    }

    @Override
    public UserDetails loadUserByUsername(String loginName) throws UsernameNotFoundException {
        UserPo userPo = userMapper.selectByLoginName(loginName);
        if (userPo == null) {
            throw new ForbiddenException("用户不存在");
        }
        List<RolePo> roles = roleService.queryRolesByUserId(userPo.getId());
        List<PermissionPo> permissions = permissionService.queryPermissionsByUserId(userPo.getId());
        userPo.setRoles(roles);
        userPo.setPermissions(permissions);
        return userPo;
    }
    }

从上面代码可以看出来,用户信息是通过UserDetailsService接口的loadUserByUsername加载的,而参数loginName就是登录传入的用户名
这里不仅查询了用户信息,还查询了用户的角色和权限,用于接下来的权限校验过滤器校验请求的合法性

4、登录失败处理

如果账号密码不正确,登录失败,构造无权的响应即可

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        UserPo sysUser = null;
        try {
            sysUser = new ObjectMapper().readValue(request.getInputStream(), UserPo.class);

            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword());
            return authenticationManager.authenticate(authRequest);
        } catch (Exception exception) {
            try {
                log.error("用户:{}登录出现异常:{}", sysUser == null ? "未知" : sysUser.getLoginName(), exception.getMessage(),exception);
                response.setContentType("application/json;charset=utf-8");
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                PrintWriter out = response.getWriter();
                Response<String> denied = ResponseResult.denied("用户名或密码错误!");
                out.write(new ObjectMapper().writeValueAsString(denied));
                out.flush();
                out.close();
            } catch (Exception outEx) {
                outEx.printStackTrace();
            }
        }
        return null;
    }

四、权限校验过滤器

创建权限校验过滤器类,继承BasicAuthenticationFilter过滤器类

public class TokenVerifyFilter extends BasicAuthenticationFilter {
}
在这里插入图片描述

核心逻辑说明:

  • 1、从请求头获取token
String xAuthToken = request.getHeader(Const.Header.AUTH_KEY);
  • 2、token没传的话,交给后续其他过滤器处理(如果有的话),最后构造重新登录的响应
if (xAuthToken == null) {
            //没有携带token,则给用户提示请登录!
            chain.doFilter(request, response);
            this.responseReLogin(response);
            return;
}
  • 3、通过token从redis获取用户信息
String userInfo = redisClient.get(Const.Header.AUTH_KEY + ":" + xAuthToken);
  • 4、如果用户信息没获取到,那代表用户登录的时效过了,需要重新登录
if (StringUtils.isBlank(userInfo)) {
            this.responseReLogin(response);
            return;
}
  • 5、从jwt token中反序列化出token中的载荷信息,jwt的组成部分可以搜一下,这里传的是公钥
Payload<UserPo> payload = JwtUtils.getInfoFromToken(xAuthToken, prop.getPublicKey(), UserPo.class);
  • 6、从载荷中获取用户信息并使用用户名和用户的权限构造认证信息传给SpringSecurity进行权限的认证
UsernamePasswordAuthenticationToken authResult = new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);

五、默认的登录接口

SpringSecurity默认提供了http://127.0.0.1:8080/login的登录接口,所以很多新手包括本人最初也是困扰了很久,登录接口没有却能处理登录,最终登录的逻辑还是在登录的过滤器中。
包括退出登录接口,SpringSecurity也是提供了,如果要自定义退出的逻辑,安全设置中禁用退出即可,自定义退出登录逻辑

http.logout().disable()

自定义退出

    @Operation(tags = "用户退出")
    @PostMapping("/api/v1/logout")
    public Response<String> logout(HttpServletRequest request) {
        loginService.logout(request);
        return ResponseResult.success("成功退出");
    }

这里的退出逻辑很简单,就是从redis删除用户信息即可,其他退出业务逻辑也可在退出的方法中实现,比如用户退出时间、地点、本次登录时长等等。下方代码的token校验其实可以去掉的,因为退出接口也是需要鉴权的,如果执行到了这里,那说明token是有的并且是正确的没过期的。

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void logout(HttpServletRequest request) {
        String token = request.getHeader(Const.Header.AUTH_KEY);
        if (token == null) {
            // 没有携带token,不允许退出,不是正常的操作
            throw new DeniedException("无权退出");
        }
        redisClient.deleteKey(Const.Header.AUTH_KEY + ":" + token);
    }

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

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

相关文章

音频DAC,ADC,CODEC的选型分析,高性能立体声

想要让模拟信号和数字信号顺利“交往”&#xff0c;就需要一座像“鹊桥”一样的中介&#xff0c;将两种不同的语言转变成统一的语言&#xff0c;消除无语言障碍。这座鹊桥就是转换器芯片&#xff0c;也就是ADC芯片。ADC芯片的全称是Analog-to-Digital Converter, 即模拟数字转换…

MATLAB 计算两片点云间的最小距离(2种方法) (39)

MATLAB 计算两片点云间的最小距离 (39) 一、算法介绍二、算法实现1.常规计算方法2.基于KD树的快速计算一、算法介绍 假设我们现在有两片点云 1 和 2 ,需要计算二者之间的最小距离,这里提供两种计算方法,分别是常规计算和基于KD树近邻搜索的快速计算方法,使用的测试数据如…

遥感图像分割系统:融合空间金字塔池化(FocalModulation)改进YOLOv8

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义 遥感图像分割是遥感技术领域中的一个重要研究方向&#xff0c;它的目标是将遥感图像中的不同地物或地物类别进行有效的分割和识别。随着遥感技术的不断发展和遥感…

OceanBase数据库部署

文章目录 OceanBase基础概念集群、Zone和OB ServerRootService总控服务&#xff08;RS&#xff09;多租户机制&#xff1a;资源隔离&#xff0c;数据隔离每个租户拥有若干资源池&#xff08;Resource Pool&#xff09; 部署形式部署流程OceanBase客户端工具 学习体验部署实现 O…

挑战52天学小猪佩奇笔记--day24

52天学完小猪佩奇--day24 ​【本文说明】 本文内容来源于对B站UP 脑洞部长 的系列视频 挑战52天背完小猪佩奇----day24 的视频内容总结&#xff0c;方便复习。强烈建议大家去关注一波UP&#xff0c;配合UP视频学习。 注&#xff1a;这集开始变成一段一段的猜台词&#xff0c;加…

区块链的可扩展性研究【06】Plasma

1.Plasma&#xff1a;Plasma 是一种基于以太坊区块链的 Layer2 扩容方案&#xff0c;它通过建立一个分层结构的区块链网络&#xff0c;将大量的交易放到子链上进行处理&#xff0c;从而提高了以太坊的吞吐量。Plasma 还可以通过智能合约实现跨链交易&#xff0c;使得不同的区块…

Element的安装以及基本使用

Element是基于Vue的网站组件库&#xff0c;用于快捷构建网页 像上面这样的样式 官网地址 Element - 网站快速成型工具 安装 npm i element-ui -S 装包命令 npm install babel-plugin-component -D 安装好之后会在package.json里面显示版本 在node_modules中会自动初始化一个 …

云原生之深入解析OOM和CPU节流

一、前言 使用 Kubernetes 时&#xff0c;内存不足 (OOM) 错误和 CPU 节流是云应用程序中资源处理的主要难题&#xff0c;这是为什么呢&#xff1f;云应用程序中的 CPU 和内存要求变得越来越重要&#xff0c;因为它们与云成本直接相关。通过 limits 和 requests &#xff0c;可…

Java数据结构篇——单链表的基本操作

1. 前言 在上一篇《Java数据结构篇——实现顺序表的增删查改》&#xff0c;我们已经熟悉了 ArrayList 的使用并且进行了简单的模拟实现。ArrayList底层使用数组来存储元素&#xff0c;由于其底层是一段连续的空间&#xff0c;当ArrayList 任意位置插入或者删除元素时&#xff…

使用下载代替物理串口输出-STM32 Debug (printf) Viewer

使用下载代替物理串口输出-STM32 Debug 硬件要求配置方法代码要求打印输出结果 硬件要求 STM32的PB9、PB10引脚的串口1通常用作其他功能使用后&#xff0c;无法通过printf()函数打印输出想要调试输出查看变量或调试信息。现已使用另外一种方法实现printf()函数打印输出。 ST…

AutoGen多代理对话项目示例和工作流程分析

在这篇文章中&#xff0c;我将介绍AutoGen的多个代理的运行。这些代理将能够相互对话&#xff0c;协作评估股票价格&#xff0c;并使用AmCharts生成图表。 我们创建对话的目的是要求代理分析特定公司的股票价格&#xff0c;并制作股票价格图表。 为了实现这一目标&#xff0c;…

oracle DG 三种应用机制

首先理解不管是哪种机制&#xff0c;oracle都不是从主库直接传归档文件到备库&#xff0c;而是通过网络将主库的redo数据传输到备库&#xff1a; 1、普通DG是主库发生日志切换&#xff0c;备库把接收到的redo数据在备库通过归档进程生成为归档文件进行应用 2、ADG则是备库把接收…

Windows mysql5.7 执行查询/开启/测试binlog---简易记录

前言&#xff1a;基于虚拟机mysql版本为5.7&#xff0c;增量备份测试那就要用到binlog… 简述&#xff1a;二进制日志&#xff08;binnary log&#xff09;以事件形式记录了对MySQL数据库执行更改的所有操作。 binlog是记录所有数据库表结构变更&#xff08;例如CREATE、ALTER…

轻松搭建FPGA开发环境:第三课——Vivado 库编译与设置说明

工欲善其事必先利其器&#xff0c;很多人想从事FPGA的开发&#xff0c;但是不知道如何下手。既要装这个软件&#xff0c;又要装那个软件&#xff0c;还要编译仿真库&#xff0c;网上的教程一大堆&#xff0c;不知道到底应该听谁的。所以很多人还没开始就被繁琐的开发环境搭建吓…

在非联网、无网络环境下,fpm的安装和生成RPM包的使用案例

文章目录 前言1、安装fpm1.1、安装Ruby环境1.2、gem 安装 fpm 2、fpm使用2.1、fpm常用参数2.2、fpm使用案例2.2.1、fpmFirstDemo文件夹2.2.3、编写脚本文件2.2.4、生成RPM包2.2.5、RPM安装与卸载测试 前言 由于fpm采用Ruby语言开发&#xff0c;因此在使用之前需要先在您的虚拟…

Java只有值传递,没有引用传递!

结论&#xff1a;Java中的参数传递&#xff0c;只有值传递&#xff0c;没有引用传递&#xff01; 以下均为错误理解&#xff1a; 值传递和引用传递&#xff0c;区别在于传递的内容。如果是个值&#xff0c;就是值传递&#xff1b;如果是个引用&#xff0c;就是引用传递Java是引…

力扣日记12.13-【二叉树篇】从中序与后序遍历序列构造二叉树

力扣日记&#xff1a;【二叉树篇】从中序与后序遍历序列构造二叉树 日期&#xff1a;2023.12.13 参考&#xff1a;代码随想录、力扣 106. 从中序与后序遍历序列构造二叉树 题目描述 难度&#xff1a;中等 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二…

Prometheus

Prometheus [系统性能优化实践]JVM进阶实战之监控工具(Prometheus) https://www.cnblogs.com/johnnyzen/p/17388354.html ubuntu 22.04 配置 Prometheus 和 Grafana 服务器监控 https://blog.csdn.net/nvd11/article/details/128030197 Prometheus 是一个开源的监控系统&…

深入理解网络 I/O:单 Group 混杂模式|多 Group 主从模式

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…

Threejs漫天多彩粒子天空--粒子系统打造

一、导语 漫天多彩粒子天空特效应该也是Threejs项目中挺常见的一个需求&#xff0c;因为它是基于粒子系统&#xff0c;可以衍生出许多的不一样的方案&#xff0c;比如&#xff0c;星空特效&#xff0c;下雨特效&#xff0c;飘雪特效等等&#xff0c;不仅可以用在项目中增加氛围…