SpringBoot整合Spring-Security 认证篇(保姆级教程)

本文项目基于以下教程的代码版本: https://javaxbfs.blog.csdn.net/article/details/135195636

代码仓库: https://gitee.com/skyblue0678/springboot-demo

为了跟shiro区别开,新建了一个分支:

目录

🌹1、友善问候一下 Spring Security

⭐2、 POM依赖

🌹3、登录

⭐4、根据账号从DB中获取用户实体

🌹5、校验密码是否正确

🌹6、全局异常返回

⭐7、测试

🌹8、细说spring security


🌹1、友善问候一下 Spring Security

Spring Security是Spring家族中的安全框架,可以用来做用户验证和权限管理等。Spring Security是一款重型框架,不过功能十分强大。

一般来说,如果项目中需要进行权限管理,具有多个角色和多种权限,我们可以使用Spring Security。如果是较为简单的项目,只需要控制一下某些接口只有登录后才能访问,则可以使用Shiro框架,Shiro也是一款安全框架,它是一款轻量级框架,功能没有Spring Security多,但使用起来要简单不少。

也就是说,Spring Security的颗粒度更细。

SpringSecurity 采用的是责任链的设计模式,是一堆过滤器链的组合,它有一条很长的过滤器链。

不过我们不需要去仔细了解每一个过滤器的含义和用法,只需要搞定以下几个问题即可:怎么登录、怎么校验账户、认证失败处理。

2、 POM依赖

没啥好说的,maven导入即可。

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

不写版本号,默认就会下载最新的版本。

3、登录

不管你用哪种权限框架,第一个要解决的问题就是登录。就是在我们的登录接口中,将账户密码委托给权限框架接管,让权限框架帮我们做校验和权限认证。

新建一个登录方法

@GetMapping("/security/login")
public String securityLogin(String username,String password){UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username,password);// 使用authenticationManager调用loadUserByUsername获取数据库中的用户信息,Authentication authentication = authenticationManager.authenticate(authToken);if(authentication == null) {throw new RuntimeException("Login false");}//获取符合security规范的UserSecurityUser securityUser = (SecurityUser) authentication.getPrincipal();String token = jwtUtil.createToken(securityUser.getUser());return token;
}

这个方法的目的是验证用户的用户名和密码,并在验证成功后为该用户生成一个JWT。 UsernamePasswordAuthenticationToken就是我们委托框架帮我们托管的登录凭证,shiro框架也有类似的东西。 然后是:

Authentication authentication = authenticationManager.authenticate(authToken);

这就是spring security帮我们执行认证和授权的方法,最终返回一个认证结果。大家思考一下,我们正常登录的逻辑无非是四步走:

  1. 输入账号密码

  2. 根据账号从DB中获取用户实体

  3. 校验密码是否正确

  4. 校验成功,将用户生成token后返回

我们再回过来看这段代码,第2步和第3步没见到,只见到spring security帮我们做了。但是,这并不代表我们可以省略这两步,只是需要我们写在别的地方,仅此而已。

4、根据账号从DB中获取用户实体

这个步骤是不可能不写的,只是写到了别处。 先说个事儿哈,spring security中的用户概念,有自己的一套规则,不能直接用我们系统里面的User类。

如果我们要用spring security,就得实现他的用户接口:UserDetails。 所以,我们新建一个SecurityUser类:

// 使用Lombok库的@Data注解,自动生成getter、setter、equals、hashCode和toString方法
// 同样使用@NoArgsConstructor注解,自动生成无参构造函数
@Data
@NoArgsConstructor
public class SecurityUser implements UserDetails {// 使用聚合模式,将我们自己的User对象聚合到SecurityUser中// user字段存储了用户的一些信息,例如用户名和密码等private User user;// 覆盖UserDetails接口中的getAuthorities方法,返回用户的权限集合// 这里返回null,表示未实现获取权限的逻辑@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}// 覆盖UserDetails接口中的getPassword方法,返回用户的密码// 这里通过调用user对象的getPwd方法获取密码@Overridepublic String getPassword() {return user.getPwd();}// 覆盖UserDetails接口中的getUsername方法,返回用户的用户名// 这里通过调用user对象的getUserName方法获取用户名,并注释说明有的地方可能会用email作为用户名,这里还是使用userName@Overridepublic String getUsername() {// 用户名:有的地方可能会用email作为用户名,我们这还是userNamereturn user.getUserName();}// 覆盖UserDetails接口中的isAccountNonExpired方法,判断账户是否过期// 这里直接返回true,表示账户不过期@Overridepublic boolean isAccountNonExpired() {return true;}// 覆盖UserDetails接口中的isAccountNonLocked方法,判断账户是否被锁定// 这里直接返回true,表示账户未被锁定@Overridepublic boolean isAccountNonLocked() {return true;}// 覆盖UserDetails接口中的isCredentialsNonExpired方法,判断凭证是否过期// 这里直接返回true,表示凭证不过期@Overridepublic boolean isCredentialsNonExpired() {return true;}// 覆盖UserDetails接口中的isEnabled方法,判断用户是否启用// 这里直接返回true,表示用户启用状态为true@Overridepublic boolean isEnabled() {return true;}
}

我们系统里面有自己的user了,但是为了适配,所以就聚合进来。

然后是如何查询DB呢,是不是得有个Service去查询,我们依据有自己的UserService了,但是很可惜,spring security有自己的规范,我们自己写的user Service,他不认,气死偶了。

没办法,重新写个Service,我们自己写都写了,也不能不管对不对?嗯,那还是聚合进来。

@Service
@Slf4j
public class UserDetailService implements UserDetailsService {@ResourceUserService userService;/*** 根据用户名直接从DB中查询用户数据,作为登录校验的依据* @param username* @return* @throws UsernameNotFoundException*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userService.getByUsername(username);if (user == null) {log.info("username not found");throw new UsernameNotFoundException("username not found");}SecurityUser securityUser = new SecurityUser();securityUser.setUser(user);return securityUser;}}

核心逻辑就是,我们还是用之前的方法拿到User,但为了适配,就塞到SecurityUser里面去。

UserDetailsService是spring security认可的接口,我们得实现这个接口,并且实现loadUserByUsername方法,这个方法在spring security的认证逻辑里面会用到。目的就是拿到DB中真实的User,跟我们登录的账号密码进行比对。

5、校验密码是否正确

为什么会有这一步呢,因为很多时候我们的密码是要进行加密的,但是我们登录肯定传的是明文密码,所以会需要转换后再去比对,否则肯定是校验失败了。

这个校验密码的逻辑,需要写在spring security的配置类中。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@ResourceUserDetailService userService;/*** 新增security账户* @param auth* @throws Exception*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService).passwordEncoder(new PasswordEncoder() {@Overridepublic String encode(CharSequence rawPassword) {return rawPassword.toString();}@Overridepublic boolean matches(CharSequence rawPassword, String encodedPassword) {return rawPassword.equals(encodedPassword);}});}}

因为我们的项目并没有对密码加密,所以就直接比较了。

在LoginController中,我们用到了AuthenticationManager这个对象,需要在配置类中注册。

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {// 身份验证管理器, 直接继承即可.return super.authenticationManagerBean();
}

AuthenticationManager是Spring Security框架中的一个核心接口,它负责处理身份验证请求。在认证过程中,用户提交身份验证信息(如用户名和密码),AuthenticationManager会验证这些信息的有效性。

最后是路由的相关配置

@Override  protected void configure(HttpSecurity http) throws Exception {  http  // 禁用跨站请求伪造保护  .csrf().disable()   // 设置会话管理策略为无会话,因为我们使用token作为信息传递介质  .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)   .and()  // 进行认证请求的配置  .authorizeRequests()   // 将所有登入和注册的接口放开,这些都是无需认证就访问的  .antMatchers("/security/login").anonymous()   // 除了上面的那些,剩下的任何接口请求都需要经过认证  .anyRequest().authenticated()   .and()  // 允许跨域请求  .cors()   ;  // 在UsernamePasswordAuthenticationFilter之前添加JWT认证过滤器  http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);  }

这段代码是Spring Security框架中用于配置HTTP安全的部分。它主要涉及到跨站请求伪造(CSRF)的禁用、会话管理策略的设置、认证请求的配置以及跨域请求的允许。同时,还添加了一个JWT(JSON Web Token)认证过滤器。

因为我们项目用到了jwt,所以在进行账号密码验证之前,要先走jwt的过滤器。

jwt过滤器代码如下:

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@ResourceJWTUtil jwtUtil;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, ServletException, IOException {//获取tokenString token = request.getHeader("token");if (!StringUtils.hasText(token)) {//token为空的话, 就不管它, 让SpringSecurity中的其他过滤器处理请求//请求放行filterChain.doFilter(request, response);return;}//token不为空时, 解析tokenUser user = null;try {user = jwtUtil.verify(token);} catch (Exception e) {// 过滤器中抛出的异常,无法被统一异常捕获,所以在这里直接返回e.printStackTrace();Result result = new Result();result.setCode(403);result.setMsg("Token无效:" + e.getMessage());WebUtils.response(response,result);return;}SecurityUser securityUser = new SecurityUser();securityUser.setUser(user);//将用户安全信息存入SecurityContextHolder, 在之后SpringSecurity的过滤器就不会拦截UsernamePasswordAuthenticationToken authenticationToken =new UsernamePasswordAuthenticationToken(securityUser, null, null);SecurityContextHolder.getContext().setAuthentication(authenticationToken);//放行filterChain.doFilter(request, response);}
}

流程可以参考这个图:

如果校验成功了,那么在登录方法中就会往下走,生成token返回,结束。

6、全局异常返回

认证过程中,难免出现各种异常,我们一般会做一个通用的返回,直接上代码,没啥好说的,网上一大堆

Result

Data
public class Result implements Serializable {private int code;private String msg;private Object data;public static Result succ(Object data) {return success(200, "操作成功", data);}public static Result error(String msg) {return error(400, msg, null);}public static Result success (int code, String msg, Object data) {Result result = new Result();result.setCode(code);result.setMsg(msg);result.setData(data);return result;}public static Result error (int code, String msg, Object data) {Result result = new Result();result.setCode(code);result.setMsg(msg);result.setData(data);return result;}
}

GlobalExceptionHandler

/*** 全局异常处理*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {/*** 400 错误:运行时异常* @param e* @return*/@ResponseStatus(HttpStatus.BAD_REQUEST)@ExceptionHandler(value = RuntimeException.class)public Result handler(RuntimeException e) {log.error("运行时异常:----------------{}", e.getMessage());return Result.error(e.getMessage());}/*** 403 错误:权限不足* @param e* @return*/@ResponseStatus(HttpStatus.FORBIDDEN)@ExceptionHandler(value = AccessDeniedException.class)public Result handler(AccessDeniedException e) {log.info("security权限不足:----------------{}", e.getMessage());return Result.error("权限不足");}/*** 400 错误:异常请求-方法参数不匹配* @param e* @return*/@ResponseStatus(HttpStatus.BAD_REQUEST)@ExceptionHandler(value = MethodArgumentNotValidException.class)public Result handler(MethodArgumentNotValidException e) {log.info("实体校验异常:----------------{}", e.getMessage());BindingResult bindingResult = e.getBindingResult();ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get();return Result.error(objectError.getDefaultMessage());}/*** 400 错误:异常请求-非法参数* @param e* @return*/@ResponseStatus(HttpStatus.BAD_REQUEST)@ExceptionHandler(value = IllegalArgumentException.class)public Result handler(IllegalArgumentException e) {log.error("Assert异常:----------------{}", e.getMessage());return Result.error(e.getMessage());}}

7、测试

localhost:8080/security/login?username=jack&password=1

将token放到header,发送 localhost:8080

能正常返回就OK了。

如果token过期,就会报:

{"msg": "Token无效:The Token has expired on Tue Dec 26 15:43:42 CST 2023.","code": 403
}

8、细说spring security

Spring Security 并非一个新生的事物,它最早不叫 Spring Security ,叫 Acegi Security,叫 Acegi Security 并不是说它和 Spring 就没有关系了,它依然是为 Spring 框架提供安全支持的。事实上,Java 领域的框架,很少有框架能够脱离 Spring 框架独立存在。

当 Spring Security 还叫 Acegi Security 的时候,虽然功能也还可以,但是实际上这个东西并没有广泛流行开来。最重要的原因就是它的配置太过于繁琐,当时网上流传一句话:“每当有人要使用 Acegi Security,就会有一个精灵死去。” 足见 Acegi Security 的配置是多么可怕。直到今天,当人们谈起 Spring Security 的时候,依然在吐槽它的配置繁琐。

后来 Acegi Security 投入 Spring 的怀抱,改名叫 Spring Security,事情才慢慢开始发生变化。新的开发团队一直在尽力简化 Spring Security 的配置,Spring Security 的配置相比 Acegi Security 确实简化了很多。但是在最初的几年里,Spring Security 依然无法得到广泛的使用。

直到有一天 Spring Boot 像谜一般出现在江湖边缘,彻底颠覆了 JavaEE 的世界。一人得道鸡犬升天,自从 Spring Boot 火了之后,Spring 家族的产品都被带了一把,Spring Security 就是受益者之一,从此飞上枝头变凤凰。

Spring Boot/Spring Cloud 现在作为 Java 开发领域最最主流的技术栈,这一点大家应该都没有什么异议,而在 Spring Boot/Spring Cloud 中做安全管理,Spring Security 无疑是最方便的。

你想保护 Spring Boot 中的接口,添加一个 Spring Security 的依赖即可,事情就搞定了,所有接口就保护起来了,甚至不需要一行配置。

所以说,因为 Spring Boot/Spring Cloud 火爆,让 Spring Security 跟着沾了一把光。

「有的人觉得 Spring Security 配置臃肿。」

如果是 SSM + Spring Security 的话,我觉得这话有一定道理。

但是如果是 Spring Boot 项目的话,其实并不见得臃肿。Spring Boot 中,通过自动化配置 starter 已经极大的简化了 Spring Security 的配置,我们只需要做少量的定制的就可以实现认证和授权了。

「有人觉得 Spring Security 中概念复杂。」这个是这样的,没错。

Spring Security 由于功能比较多,支持 OAuth2 等原因,就显得比较重量级,不像 Shiro 那样轻便。

但是如果换一个角度,你可能会有不一样的感受。

在 Spring Security 中你会学习到许多安全管理相关的概念,以及常见的安全攻击。这些安全攻击,如果你不是 web 安全方面的专家,很多可能存在的 web 攻击和漏洞你可能很难想到,而 Spring Security 则把这些安全问题都给我们罗列出来并且给出了相应的解决方案。

所以我说,我们学习 Spring Security 的过程,也是在学习 web 安全,各种各样的安全攻击、各种各样的登录方式、各种各样你能想到或者想不到的安全问题,Spring Security 都给我们罗列出来了,并且给出了解决方案,从这个角度来看,你会发现 Spring Security 好像也不是那么让人讨厌。

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

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

相关文章

短视频矩阵系统:赋予用户创造与分享的力量

在如今快节奏的社交网络时代&#xff0c;人们对于信息获取和娱乐方式的需求也逐渐发生了变化。作为当下最受欢迎的短视频平台之一&#xff0c;抖音短视频矩阵系统正以其独特的魅力和吸引力&#xff0c;深深地打动着亿万用户。 抖音短视频矩阵系统是一种基于移动端的短视频分享…

3D换肤在服装行业的应用

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 通过采用高质量的 3D 模型&#xff0c;企业可以提供更加身临其境的体…

解决阿里云远程连接yum无法安装问题(Ubuntu 22.04)

解决阿里云远程连接yum无法安装问题&#xff08;Ubuntu 22.04&#xff09; 第一步 进入阿里云远程连接后&#xff0c;尝试安装宝塔面包第二步&#xff1a;尝试更新软件包等一些列操作第三步&#xff1a;完成上述操作之后&#xff0c;尝试安装yum第四步&#xff1a;尝试更换清华…

FPGA——XILINX原语(1)

FPGA——XILINX原语&#xff08;1&#xff09; 1.时钟组件&#xff08;1&#xff09;BUFG&#xff08;2&#xff09;BUFH&#xff08;3&#xff09;BUFR&#xff08;4&#xff09;BUFIO&#xff08;5&#xff09;使用场景 2.IO端口组件&#xff08;1&#xff09;IDDR&#xff0…

PostGIS学习教程十五:几何图形的有效性

PostGIS学习教程十五&#xff1a;几何图形的有效性 在90%的情况下&#xff0c;“为什么我的查询给了我一个’TopologyException’错误"的问题的答案是"一个或多个输入的几何图形是无效的”&#xff0c;这就引出了这样一个问题:几何图形"无效"是什么意思&a…

Pandas教程(二)—— 不同格式的数据读取

前言&#xff1a;几种常用数据格式的介绍 csv文件 1. 逗号分隔值文件&#xff0c;以纯文本形式&#xff08;记事本&#xff09;存储表格数据 2. 它是一种平面文件&#xff1a;即只存储数据和文字&#xff0c;不能存储公式、图表等 3. 更适合存储大数据&#xff0c;一般用来批…

Codeforces Round 917 (Div. 2)更新中...

A.Least Product(思维) 题意&#xff1a; 给出一个数组 a 1 , a 2 , . . . , a n a_1, a_2, ..., a_n a1​,a2​,...,an​&#xff0c;你可以进行若干次以下操作&#xff1a; 选择数组中的一个元素 a i a_i ai​&#xff0c;将这个数字修改为 0 ∼ a i 0 \sim a_i 0∼ai​之…

Stream流的简单使用

stream流的三类方法 获取Stream流 ○ 创建一条流水线,并把数据放到流水线上准备进行操作中间方法 ○ 流水线上的操作 ○ 一次操作完毕之后,还可以继续进行其他操作终结方法 ○ 一个Stream流只能有一个终结方法 ○ 是流水线上的最后一个操作 其实Stream流非常简单&#xff0c;只…

Unity3D 安装和下载指南及汉化

Unity3D是一款强大的游戏开发引擎&#xff0c;为开发者提供了丰富的工具和资源&#xff0c;使得游戏制作变得更加简单和高效。本文将介绍Unity3D的安装和下载步骤&#xff0c;以帮助初学者迅速入门。 步骤一&#xff1a;访问Unity官网 首先&#xff0c;打开浏览器&#xff0c…

Qt 中使用 MySQL 数据库保姆级教程(上)

作者&#xff1a;billy 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 前言 在 Qt 中默认只搭载了 QSqlLite 数据库驱动&#xff0c;若要使用其他数据库需要自己下载数据库&#xff0c;并将数据库驱动加载到…

虚拟机Windows Server 2012 与ubuntu的安装与布置

介绍虚拟机 虚拟机&#xff08;Virtual Machine&#xff0c;简称VM&#xff09;是一种通过软件模拟的计算机系统&#xff0c;可以在一台物理计算机上同时运行多个独立的操作系统和应用软件。虚拟机将物理计算机的硬件资源&#xff08;如处理器、内存、硬盘等&#xff09;虚拟化…

2023年12月30日(星期六)骑行太平

2023年12月30日 (星期六) 骑行太平&#xff0c;早8:30到9:00&#xff0c; 大观公园门囗集合&#xff0c;9:30准时出发 【因迟到者&#xff0c;骑行速度快者&#xff0c;可自行追赶偶遇。】 偶遇地点:大观公园门囗集合 &#xff0c;家住东&#xff0c;南&#xff0c;北的骑友在…

Nginx快速入门:return、rewrite重定向、重写详解(六)

0. 引言 我们在日常的生产过程中&#xff0c;常常有需要重定向转发的需求&#xff0c;比如企业更换了域名&#xff0c;但又要保证之前的域名能访问&#xff0c;这就需要做重定向的跳转。 我们在之前的章节中学习了Nginx的负载均衡、各类转发代理配置&#xff0c;今天继续来补…

Camtasia2024中文最新免授权版下载

TechSmith Camtasia2024免费版是一款非常专业且功能强大的屏幕录制工具&#xff01;使用集屏幕录像机、视频编辑器的功能于一身&#xff0c;能够轻松的录制和创建具有专业外观的视频。这套非常强大的专业录屏与视频创作大型软件套装包含捕获屏幕录制、视频剪辑和编辑、视频录音…

MySQL日期查询 今天、明天、本月、下月、星期、本周第一天、本周最后一天、本周七天日期

文章目录 今天日期明天日期本月第一天本月最后一天下个月第一天当前月已过几天当前月天数当前月所有日期获取星期本周第一天本周最后一天获取本周的七天日期 今天日期 select curdate()明天日期 select DATE_SUB(curdate(),INTERVAL -1 DAY) AS tomorrow本月第一天 select d…

嵌入式——RTC内置实时时钟

学习目标 理解原理图RTC设计部分掌握初始化RTC掌握设置时间掌握读取时间学习内容 RTC原理图 RTC结构框图 RTC时钟 开发流程 加载依赖。gd32f4xx_rtc.c,gd32f4xx_pmu.c初始化RTC。时钟配置。获取时钟。RTC初始化 // 电池管理加载 rcu_periph_clock_enable(RCU_PMU); pmu_back…

C#调用(python通过excel坐标生成的曲面地形图)案例

效果图: 文件图: 详解一:环境和python库问题 1.python 中只需要下载 matplotlib3.8.2和scipy1.11.4 2.我安装的python版本 详解二:解释器问题 python解释器这里有两种形式 第一种形式 1.调用 pycharm项目下的解释器,需要安装python必须的包(命令安装或者搜索安装)。 2.修改…

“一键批量翻译与重命名,轻松将西班牙文件名转换为中文名“

你是否遇到过需要将大量的西班牙文件名翻译成中文名&#xff0c;却无从下手的情况&#xff1f;现在&#xff0c;我们为你带来了一款强大的文件名批量翻译与重命名软件&#xff0c;让你能够轻松完成这项任务&#xff0c;提高工作效率。 首先&#xff0c;进入文件批量改名高手的主…

MEMS麦克风具备哪些优势? MP34DT05TR-A 一款MEMS音频传感器全向数字麦克风

MP34DT05TR-A是一款超紧凑、低功耗、全向数字式 MEMS 麦克风&#xff0c;内置电容式感应元件和 IC 接口。 传感元件能够检测声波&#xff0c;采用专门用来生产音频传感器的硅微加工工艺制造。IC 接口采用 CMOS 工艺制造&#xff0c;允许专用电路设计以 PDM 格式从外部提供数字…

Java自动化测试系列[v1.0.0][常见页面操作处理附源码]

[控制滚动] package util; import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement;public class ScrollBarUtil {/*** 控制滚动条向下拉到底* param driver 浏览器驱动*/public static void toBottom…