SpringSecurity-用户认证

1、用户认证

1.1 用户认证核心组件

我们系统中会有许多用户,确认当前是哪个用户正在使用我们系统就是登录认证的最终目的。这里我们就提取出了一个核心概念:当前登录用户/当前认证用户。整个系统安全都是围绕当前登录用户展开的,这个不难理解,要是当前登录用户都不能确认了,那A下了一个订单,下到了B的账户上这不就乱套了。这一概念在Spring Security中的体现就是 Authentication,它存储了认证信息,代表当前登录用户。

我们在程序中如何获取并使用它呢?我们需要通过 SecurityContext 来获取AuthenticationSecurityContext就是我们的上下文对象!这个上下文对象则是交由 SecurityContextHolder 进行管理,你可以在程序任何地方使用它:

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

SecurityContextHolder原理非常简单,就是使用ThreadLocal来保证一个线程中传递同一个对象,什么是ThreadLocal呢?ThreadLocal也叫线程变量,提供了线程本地的实例

1.2 Spring Security中三个核心组件

1、Authentication:存储了认证信息,代表当前登录用户

2、SeucirtyContext:上下文对象,用来获取Authentication

3、SecurityContextHolder:上下文管理对象,用来在程序任何地方获取SecurityContext

Authentication中是什么信息呢:

1、Principal:用户信息,没有认证时一般是用户名,认证后一般是用户对象

2、Credentials:用户凭证,一般是密码

3、Authorities:用户权限

2. 进行用户认证的流程

AuthenticationManager 就是Spring Security用于执行身份验证的组件,只需要调用它的authenticate方法即可完成认证。Spring Security默认的认证方式就是在UsernamePasswordAuthenticationFilter这个过滤器中进行认证的,该过滤器负责认证逻辑。

Spring Security用户认证关键代码如下:

// 生成一个包含账号密码的认证信息
Authentication authenticationToken = new UsernamePasswordAuthenticationToken(username, passwrod);
// AuthenticationManager校验这个认证信息,返回一个已认证的Authentication
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 将返回的Authentication存到上下文中
SecurityContextHolder.getContext().setAuthentication(authentication);

2.1 认证接口分析

AuthenticationManager的校验逻辑比较简单:

根据用户名先查询出用户对象(没有查到则抛出异常)将用户对象的密码和传递过来的密码进行校验,密码不匹配则抛出异常。

重点是这里每一个步骤Spring Security都提供了相关的组件:

1、是谁执行 根据用户名查询出用户对象 逻辑的呢?用户对象数据可以存在内存中、文件中、数据库中,你得确定好怎么查才行。这一部分就是交由**UserDetialsService** 处理,该接口只有一个方法loadUserByUsername(String username),通过用户名查询用户对象,默认实现是在内存中查询。

2、那查询出来的 用户对象 又是什么呢?每个系统中的用户对象数据都不尽相同,咱们需要确认我们的用户数据是啥样的才行。Spring Security中的用户数据则是由**UserDetails** 来体现,该接口中提供了账号、密码等通用属性。

3、对密码进行校验:Spring Security框架除了if、else外还解决了密码加密的问题,这个组件就是**PasswordEncoder**,负责密码加密与校验。

PasswordEncoder接口如下:

package org.springframework.security.crypto.password;public interface PasswordEncoder {String encode(CharSequence var1);boolean matches(CharSequence var1, String var2);default boolean upgradeEncoding(String encodedPassword) {return false;}
}

2.2 分析结果:

UserDetialsServiceUserDetailsPasswordEncoder,这三个组件Spring Security都有默认实现,这一般是满足不了我们的实际需求的,所以这里需要我们自己来实现这些组件,对组件自定义实现。

3.在项目实现用户认证

这里我们采用MD5加密方式

在项目中建立utils包,导入MD5工具类

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;public final class MD5 {public static String encrypt(String strSrc) {try {char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8','9', 'a', 'b', 'c', 'd', 'e', 'f' };byte[] bytes = strSrc.getBytes();MessageDigest md = MessageDigest.getInstance("MD5");md.update(bytes);bytes = md.digest();int j = bytes.length;char[] chars = new char[j * 2];int k = 0;for (int i = 0; i < bytes.length; i++) {byte b = bytes[i];chars[k++] = hexChars[b >>> 4 & 0xf];chars[k++] = hexChars[b & 0xf];}return new String(chars);} catch (NoSuchAlgorithmException e) {e.printStackTrace();throw new RuntimeException("MD5加密出错!!+" + e);}}public static void main(String[] args) {System.out.println(MD5.encrypt("111111"));}
}

3.1 自定义加密处理组件:CustomMd5PasswordEncoder

提示:@component:  标注一个类为Spring容器的Bean

import com.atguigu.common.utils.MD5;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;@Component
public class CustomMd5PasswordEncoder implements PasswordEncoder {public String encode(CharSequence rawPassword) {return MD5.encrypt(rawPassword.toString());}public boolean matches(CharSequence rawPassword, String encodedPassword) {return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));}
}

3.2 用户对象UserDetails

该接口就是我们所说的用户对象,它提供了用户的一些通用属性,源码如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package org.springframework.security.core.userdetails;import java.io.Serializable;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;public interface UserDetails extends Serializable {Collection<? extends GrantedAuthority> getAuthorities();String getPassword();String getUsername();boolean isAccountNonExpired();boolean isAccountNonLocked();boolean isCredentialsNonExpired();boolean isEnabled();
}

实际开发中我们的用户属性各种各样,这些默认属性可能是满足不了,所以我们一般会自己实现该接口,然后设置好我们实际的用户实体对象。实现此接口要重写很多方法比较麻烦,我们可以继承Spring Security提供的org.springframework.security.core.userdetails.User类,该类实现了UserDetails接口帮我们省去了重写方法的工作:

添加自定义对象

3.3 添加UserDetailsServiceImpl类,实现UserDetailsService接口

到此AuthenticationManager校验所调用的三个组件已经进行自定义实现,接下来编写自定义用户认证接口,在filter包下创建TokenLoginFilter类

public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {public TokenLoginFilter(AuthenticationManager authenticationManager) {this.setAuthenticationManager(authenticationManager);this.setPostOnly(false);//指定登录接口及提交方式,可以指定任意路径this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/system/index/login","POST"));}/*** 登录认证* @param req* @param res* @return* @throws AuthenticationException*/@Overridepublic Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)throws AuthenticationException {try {LoginVo loginVo = new ObjectMapper().readValue(req.getInputStream(), LoginVo.class);Authentication authenticationToken = new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword());return this.getAuthenticationManager().authenticate(authenticationToken);} catch (IOException e) {throw new RuntimeException(e);}}/*** 登录成功* @param request* @param response* @param chain* @param auth* @throws IOException* @throws ServletException*/@Overrideprotected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,Authentication auth) throws IOException, ServletException {CustomUser customUser = (CustomUser) auth.getPrincipal();String token = JwtHelper.createToken(customUser.getSysUser().getId(), customUser.getSysUser().getUsername());Map<String, Object> map = new HashMap<>();map.put("token", token);ResponseUtil.out(response, Result.ok(map));}/*** 登录失败* @param request* @param response* @param e* @throws IOException* @throws ServletException*/@Overrideprotected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,AuthenticationException e) throws IOException, ServletException {if(e.getCause() instanceof RuntimeException) {ResponseUtil.out(response, Result.build(null, 204, e.getMessage()));} else {ResponseUtil.out(response, Result.build(null, ResultCodeEnum.LOGIN_MOBLE_ERROR));}}
}

3.4 认证解析token

分析:由于用户登录状态在token中存储在客户端,所以每次请求接口请求头携带token, 后台通过自定义token过滤器拦截解析token完成认证并填充用户信息实体。

filter包中创建TokenAuthenticationFilter类,实现认证解析过程

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;public class TokenAuthenticationFilter extends OncePerRequestFilter {private RedisTemplate redisTemplate;public TokenAuthenticationFilter(RedisTemplate redisTemplate){this.redisTemplate = redisTemplate;}@Overrideprotected void doFilterInternal(HttpServletRequest request,//请求HttpServletResponse response,//响应FilterChain chain) throws ServletException, IOException {//获取请求头中的tokenlogger.info("uri:"+request.getRequestURI());//如果是登录接口,直接放行if("/admin/system/index/login".equals(request.getRequestURI())) {chain.doFilter(request, response);return;}//如果不是登录接口,则需要验证tokenUsernamePasswordAuthenticationToken authentication = getAuthentication(request);if(null != authentication) {//如果token不为空,则设置到SecurityContextHolder中SecurityContextHolder.getContext().setAuthentication(authentication);//放行chain.doFilter(request, response);} else {//如果token为空,则返回错误信息ResponseUtil.out(response, Result.build(null, ResultCodeEnum.LOGIN_MOBLE_ERROR));}}//获取tokenprivate UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {//从请求头中获取tokenString token = request.getHeader("token");if(!StringUtils.isEmpty(token)){//从token中获取用户名称String username = JwtHelper.getUsername(token);if(!StringUtils.isEmpty(username)){//从redis中获取用户权限信息String authString = (String) redisTemplate.opsForValue().get(username);if(!StringUtils.isEmpty(authString)){//将权限信息转换为SimpleGrantedAuthority对象List<Map> mapList = JSON.parseArray(authString,Map.class);System.out.println(mapList);//遍历权限信息,将每个权限转换为SimpleGrantedAuthority对象List<SimpleGrantedAuthority> authList = new ArrayList<>();for (Map map:mapList) {//获取权限字符串String authority = (String) map.get("authority");authList.add(new SimpleGrantedAuthority(authority));}//返回UsernamePasswordAuthenticationToken对象return new UsernamePasswordAuthenticationToken(username,null, authList);}else {//如果redis中没有权限信息,则返回空的SimpleGrantedAuthority对象return new UsernamePasswordAuthenticationToken(username,null, new ArrayList<>());}}}return null;}}

3.5 进行用户认证配置

import com.atguigu.security.custom.CustomMd5PasswordEncoder;
import com.atguigu.security.filter.TokenAuthenticationFilter;
import com.atguigu.security.filter.TokenLoginFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsUtils;@Configuration
@EnableWebSecurity //@EnableWebSecurity是开启SpringSecurity的默认行为
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启注解功能,默认禁用注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate RedisTemplate redisTemplate;@Autowiredprivate UserDetailsService userDetailsService;//自定义的UserDetailsService@Autowiredprivate CustomMd5PasswordEncoder customMd5PasswordEncoder; //自定义的密码加密器@Bean // 将AuthenticationManager注册为Bean,方便在其他地方使用@Overrideprotected AuthenticationManager authenticationManager() throws Exception {// 重写authenticationManager方法,返回AuthenticationManagerBuilderreturn super.authenticationManager();}@Overrideprotected void configure(HttpSecurity http) throws Exception {// 这是配置的关键,决定哪些接口开启防护,哪些接口绕过防护http//关闭csrf跨站请求伪造.csrf().disable()// 开启跨域以便前端调用接口.cors().and().authorizeRequests()// 指定某些接口不需要通过验证即可访问。登陆接口肯定是不需要认证的.antMatchers("/admin/system/index/login").permitAll()// 这里意思是其它所有接口需要认证才能访问.anyRequest().authenticated().and()//TokenAuthenticationFilter放到UsernamePasswordAuthenticationFilter的前面,这样做就是为了除了登录的时候去查询数据库外,其他时候都用token进行认证。.addFilterBefore(new TokenAuthenticationFilter(redisTemplate), UsernamePasswordAuthenticationFilter.class).addFilter(new TokenLoginFilter(authenticationManager(),redisTemplate));//禁用sessionhttp.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 指定UserDetailService和加密器auth.userDetailsService(userDetailsService).passwordEncoder(customMd5PasswordEncoder);}/*** 配置哪些请求不拦截* 排除swagger相关请求* @param web* @throws Exception*/@Overridepublic void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/favicon.ico","/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/doc.html");}
}

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

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

相关文章

大数据新视界 --大数据大厂之HBase 在大数据存储中的应用与表结构设计

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

如何进行Ubuntu磁盘空间深度清理?

近期使用AutoDL算力云&#xff0c;发现系统盘只有30G&#xff0c;数据盘只有50G&#xff0c;跑一个稍微大一点的模型&#xff0c;马上空间就拉爆了&#xff0c;现在做一个磁盘深度清理操作&#xff0c;看看效果。 清理前磁盘占用如下&#xff1a; 在 Ubuntu 系统中进行磁盘深度…

LabVIEW软件出现Bug如何解决

在LabVIEW开发中&#xff0c;程序出现bug是不可避免的。无论是小型项目还是复杂系统&#xff0c;调试与修复bug都是开发过程中的重要环节。下文介绍如何有效解决LabVIEW软件中的bug&#xff0c;包括常见错误类型、调试工具、错误处理机制。 1. 常见Bug类型分析 在LabVIEW中&am…

WGS1984快速度确定平面坐标系UTM分带(快速套表、公式计算、软件范围判定)

之前我们介绍了坐标系3带6带快速确定带号及中央经线&#xff08;快速套表、公式计算、软件范围判定&#xff09;就&#xff0c;讲的是CGCS2000 高斯克吕格的投影坐标系。 那还有我们经常用的WGS1984的平面坐标系一般用什么投影呢? 对于全球全国的比如在线地图使用&#xff1a…

计算机视觉硬件整理(四):相机与镜头参数介绍

文章目录 前言一、工业相机常用分类二、工业相机的基本参数三、工业相机的接口四、工业镜头的参数五、工业镜头的选择要点 前言 随着科技的飞速发展&#xff0c;工业自动化和智能制造在当今社会扮演着越来越重要的角色。在这个背景下&#xff0c;工业相机作为一种关键的视觉检…

面试系列-携程暑期实习一面

Java 基础 1、Java 中有哪些常见的数据结构&#xff1f; 图片来源于&#xff1a;JavaGuide Java集合框架图 Java 中常见的数据结构包含了 List、Set、Map、Queue&#xff0c;在回答的时候&#xff0c;只要把经常使用的数据结构给说出来即可&#xff0c;不需要全部记住 如下&…

SpringBoot集成阿里easyexcel(一)基础导入导出

easyexcel主要用于excel文件的读写&#xff0c;可使用model实体类来定义文件读写的模板&#xff0c;对开发人员来说实现简单Excel文件的读写很便捷。可参考官方文档 https://github.com/alibaba/easyexcel 一、引入依赖 <!-- 阿里开源EXCEL --><dependency><gr…

调用飞书接口导入供应商bug

1、业务背景 财务这边大部分系统都是供应商项目&#xff0c;由于供应商的研发人员没有飞书项目的权限&#xff0c;涉及到供应商系统需求 财务这边都是通过多维表格进行bug的生命周期管理如图&#xff1a; 但多维表格没有跟飞书项目直接关联&#xff0c;测试组做bug统计的时候无…

redis Redis-Cluster常用命令与Redis性能监控

起因&#xff1a;随着项目的进一步推广&#xff0c;数据量的增大&#xff0c;直接访问mysql数据库获取数据所使用的时间越来越长&#xff0c;为解决当前主要矛盾&#xff0c;决定引入redis非关系型数据库作为缓存层&#xff0c;使得数据并不能直接命中数据库&#xff0c;减少访…

【洛谷】P10417 [蓝桥杯 2023 国 A] 第 K 小的和 的题解

【洛谷】P10417 [蓝桥杯 2023 国 A] 第 K 小的和 的题解 题目传送门 题解 CSP-S1 补全程序&#xff0c;致敬全 A 的答案&#xff0c;和神奇的预言家。 写一下这篇的题解说不定能加 CSP 2024 的 RP 首先看到 k k k 这么大的一个常数&#xff0c;就想到了二分。然后写一个判…

【C++】list详解及模拟实现

目录 1. list介绍 2. list使用 2.1 修改相关 2.2 遍历 2.3 构造 2.4 迭代器 2.5 容量相关 2.6 元素访问 2.7 操作相关 3. 模拟实现 3.1 节点类 3.1.1 初始结构 3.1.2 节点的构造函数 3.2 迭代器类 3.2.1 初始结构 3.2.2 迭代器 3.2.3 迭代器-- 3.2.4 解引…

path_provider插件的用法

文章目录 1. 概念介绍2. 实现方法3. 示例代码我们在上一章回中介绍了"如何实现本地存储"相关的内容,本章回中将介绍如何实现文件存储.闲话休提,让我们一起Talk Flutter吧。 1. 概念介绍 我们在上一章回中介绍的本地存储只能存储dart语言中基本类型的数值,如果遇到…

什么是远程过程调用(RPC)

进程间通信(IPC) 进程间通信(Inter-Process Communication)是指两个进程或者线程之间传送数据或者信号的一些技术或者方法。进程是计算机进行资源分配的最小的单位。每个进程都有自己独立的系统资源,而且彼此之间是相对隔离的。为了使得不同的进程之间能够互相访问,相互协…

AI-Talk开发板之wifi scan

一、说明 AI-Talk开发板使用ESP32-C3扩展WIFI通信功能&#xff0c;与CSK6011A通过SPI接口通信。 与处理器的信号连接&#xff1a; ESP32-C3需要烧录hosted固件&#xff0c;参考&#xff1a;AI-Talk开发板更新ESP32固件_esp32 固件-CSDN博客 二、工程 1、创建项目 进入exampl…

C语言中的一些小知识(三)

一、你了解printf()吗&#xff1f; 你知道下面代码的输出结果吗&#xff1f; int a123; printf("%2d \n",a); printf() 函数是 C 语言中用于格式化输出的标准函数&#xff0c;它允许你将数据以特定的格式输出到标准输出设备&#xff08;通常是屏幕&#xff09;。p…

Linux应用开发实验班——JSON-RPC

目录 前言 1.是什么JSON-RPC 2.常用的JSON函数 1.创建JSON 2.根据名字获取JSON 3.获取JSON的值 4.删除JSON 3.如何进行远程调用 服务器 客户端 4.基于JSON-RPC进行硬件操作 课程链接 前言 学习的课程是百问网韦东山老师的课程&#xff0c;对更详细步骤感兴趣的同学…

89个H5小游戏源码

下载地址&#xff1a;https://download.csdn.net/download/w2sft/89791650 亲测可用&#xff0c;代码完整&#xff0c;都是htmljs&#xff0c;保存到本地即可。 游戏截图&#xff1a;

3519嵌入式如何通过ssh 或者telnet连接嵌入式设备

需求 PC电脑连接嵌入式设备&#xff0c;已经能够连接串口&#xff0c;想要额外连接嵌入式设备&#xff0c;查看终端信息。 尝试了两种方法&#xff1a;1.通过SSH登录(失败) 2.通过telnet登录(成功) 问题描述 1.SSH登录 3519通过网线和串口线连接PC windows&#xff0c;并在…

JVM(HotSpot):虚拟机栈(JVM Stacks)之线程问题排查方法

文章目录 前言一、CPU占用过大二、程序运行很长时间没有结果三、总结 前言 本篇讲的排查都是基于Linux环境的。 一、CPU占用过大 这个一般是出现了死循环导致的。 1、先用top命令查看占用CPU的进程ID top2、再用ps命令查看对应的线程 就看一查看到对应的线程id ps H -eo …

hexo github部署,通过域名访问你的博客

hexo github部署&#xff0c;通过域名访问你的博客 hexo 常用命令hexo github 部署 在部署之前&#xff0c;了解一下hexo的常用命令 hexo 常用命令 hexo new "My New Post" # 新建文章 hexo n "My New Post"hexo generate # 生成静态文件 hexo ghexo serv…