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,一经查实,立即删除!

相关文章

数字安全二之密钥结合消息摘要

HMACSHA256的定义 HMACSHA256是一种使用 SHA-256 哈希算法的 HMAC&#xff08;基于哈希的消息认证码&#xff0c;Hash-based Message Authentication Code&#xff09; 机制。它结合了【散列函数】 和 【密钥】&#xff0c;用于确保消息的完整性和真实性 HMAC 与 SHA-256 的作…

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

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

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

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

如何选择适合的编程工具提高工作效率

---  随着科技的发展&#xff0c;编程已经成为各行各业不可或缺的一部分。在忙碌的工作环境中&#xff0c;如何选择合适的编程工具成为了每个开发者提升工作效率的关键。智能的代码编辑器、强大的版本控制工具、还是那些让你事半功倍的自动化脚本&#xff1f;让我们一起探讨&…

GO入门之值传递于引用(指针、内存地址)传递扫盲

GO入门之值传递于引用&#xff08;指针、内存地址&#xff09;传递扫盲 Go 语言中&#xff0c;值传递和引用&#xff08;指针&#xff09;传递是两个关键的概念。通过案例可以很好地展示两者的区别。 值传递与引用传递的区别&#xff1a; 值传递&#xff1a;传递的是变量的副…

LabVIEW软件出现Bug如何解决

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

Github 2024-09-26 Go开源项目日报 Top10

根据Github Trendings的统计,今日(2024-09-26统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Go项目10C项目1Prometheus监控系统和时间序列数据库 创建周期:4149 天开发语言:Go协议类型:Apache License 2.0Star数量:52463 个Fork数量:…

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

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

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

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

python库 | lxml库

1、lxml是一个用C语言编写的Python库&#xff0c;专为解析大型文档和处理复杂的XML文件设计。它提供了快速、易用、功能丰富的接口来处理XML和HTML文档。 2、支持xpath&#xff0c;利用xpath语法&#xff0c;可以快速定位特定元素以及节点信息&#xff0c;提取出html、xml目标数…

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

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统计的时候无…

网络类 面试题-选择题

1、在TCP/IP协议中&#xff0c;A类地址第一个字节的范围是( B ) A、0-126 B、0–127 C、1- 126 D、1—127 2、DHCP 客户端是使用地址(D )来申请一个新的IP地址的 A、0.0.0.1 B、0.0.0.0 C、127. 0.0.1 D、255. 255. 255. 255 3、windows主机中tracert命令的功能是( B C ) A…

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

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

Pytest-allure如何在测试完成后自动生成完整报告?

一、完整步骤 常规allure报告的生成方法是在pytest全部用例执行完成后&#xff0c;手动在命令行执行如 allure generate ./temps -o ./report --clean每次用例执行完成后都要重复如此的操作&#xff0c;十分繁琐。 可以使用如下方式让用例执行完成后自动生成报告到当前目录下…

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

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

Linux 挂载硬盘指南

一、挂载磁盘 查看硬盘信息 使用命令 fdisk -l 查看硬盘信息。 新增数据盘执行分区 进入 fdisk 模式对新增数据盘执行分区操作&#xff1a;fdisk /dev/sdb。 新建分区 输入 n 开始新建分区。 创建一个主分区 输入 p 创建一个主分区。 分区编号 选择主分区编号&#xff0c;例…

SSH防止暴力破解

案例&#xff1a;最近公司网站一直被别人暴力破解sshd服务密码。虽然没有成功&#xff0c;但会导致系统负载很高&#xff0c;原因是在暴力破解的时候&#xff0c;系统会不断地认证用户&#xff0c;从而增加了系统资源额外开销&#xff0c;导致访问公司网站速度很慢 fail2ban可以…

【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 解引…