前后端分离项目springsecurity实现用户登录认证快速使用

目录

1、引入依赖

2、创建类继承WebSecurityConfigurerAdapter

(1)重写里面的configure(HttpSecurity http)方法

(2)重写AuthenticationManager authenticationManagerBean()

(3)密码加密工具

3、继承UserDetails

4、登录方法

5、是怎么完成登录的

(1)根据用户名查询用户

(2)密码对比

6、注册用户加密密码

7、登录过滤器

8、认证失败处理器


ps:该文章适合未系统学习springsecurity快速使用,可以直接cv使用,只有部分源码讲解,个人觉得先会用了再深究原理

1、引入依赖

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

引入springsecurity依赖后,该依赖会自动生成默认登陆页面和登录名(user)和密码(控制台)

我们使用这个用户名和密码登陆后才可以对资源进行访问

因为项目是前后端分离项目,因此并不需要它默认生成的登录页面和默认用户名密码吗,需要查询数据库进行登录

2、创建类继承WebSecurityConfigurerAdapter

我们创建一个配置类SecurityConfig(使用@Configuration标记)并且继承WebSecurityConfigurerAdapter类,重写里面的几个配置方法即可对springsecurity进行配置

(1)重写里面的configure(HttpSecurity http)方法

跟进configure(HttpSecurity http)查看源码中的方法做了什么

我们可以看到源码中的该方法中默认对所有的请求进行拦截,并且默认生成表单登录页面,并且使用基本认证。

我们只需要重写该方法就可以自己进行配置了

.csrf().disable()
.cors()        csrf建议关闭,cors前后端分离项目建议打开
.mvcMatchers("/admin/login").anonymous()   对这个接口可以匿名访问,也就是不需要认证
.mvcMatchers("/admin/save").permitAll()    对这个接口也不做认证
.mvcMatchers("/user/save").authenticated()  对这个接口需要认证才能访问

这个.mvcMatchers的参数也可以是数组形式

.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)

添加过滤器,这里的tokenAuthenticationFilter是我自己定义的,这个意思将自定义的这个过滤器放在UsernamePasswordAuthenticationFilter.class这个过滤器之前,这个过滤器是springsecurity提供的认证过滤器,像我们的这个tokenAuthenticationFilter是需要在认证之前进行的

.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint);

这个是添加了一个认证异常处理器authenticationEntryPoint,authenticationEntryPoint也是我们自定义的,就是当用户未经过认证时返回的结果,通常当未登录访问接口时返回给前端的异常信息就在这里定义

这样我们就大致完成了这个方法中登录认证功能的一些配置

(2)重写AuthenticationManager authenticationManagerBean()

我们需要使用他里面的方法进行登录认证,并使用@Bean标注到spring容器中

@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();
}

(3)密码加密工具

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

使用这个进行密码加密,springsecurity提供了很多密码加密方法,用这个就可以,可以点进去查看PasswordEncoder这个方法,这是个接口,实现了很多加密方法

然后这样我们就大致完成了这个类的配置

如果有swagger等静态资源配置,可以重写这个方法

/*** 配置哪些请求不拦截* 排除swagger相关请求* @param web* @throws Exception*/
@Override
public void configure(WebSecurity web) throws Exception {web.ignoring().antMatchers("/favicon.ico","/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/doc.html");
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate TokenAuthenticationFilter tokenAuthenticationFilter;@Autowiredprivate AuthenticationEntryPointImpl authenticationEntryPoint;/* @Autowiredprivate UserDetailsService userDetailsService;*/@Override@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().cors().and().authorizeRequests().mvcMatchers("/admin/login").anonymous().mvcMatchers("/admin/save").permitAll().mvcMatchers("/wx/user/login").permitAll().mvcMatchers("/wx/user/save").permitAll().anyRequest().authenticated().and().addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class).exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);}@Beanpublic PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}/*** 配置哪些请求不拦截* 排除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");}}

防止报错还有这个自定义的登录拦截器跟认证失败处理器也整上

@Component
public class TokenAuthenticationFilter extends OncePerRequestFilter {@Autowiredprivate StringRedisTemplate redisTemplate;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {String token = request.getHeader("token");if (ObjectUtils.isEmpty(token)){filterChain.doFilter(request,response);return;}Claims claims = null;try {claims = JwtUtil.parseJWT(token);} catch (Exception e) {e.printStackTrace();Map<String, String> errMsg = new HashMap<>();errMsg.put("code","200");errMsg.put("msg","访问失败,请重新登录");response.setContentType("text/json;charset=utf-8");response.getWriter().print(errMsg.toString());return;}Integer userId = Integer.valueOf(claims.getSubject());UserContext.setUser(userId);String userAdmin = redisTemplate.opsForValue().get("userId" + userId);AdminLogin adminLogin = JSONUtil.toBean(userAdmin, AdminLogin.class);UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(adminLogin.getUsername(), adminLogin.getUsername(), null);
//        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(null, null, null);SecurityContextHolder.getContext().setAuthentication(authenticationToken);filterChain.doFilter(request,response);}
}
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable
{private static final long serialVersionUID = -8970718410437077606L;@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)throws IOException{Map<String, String> errMsg = new HashMap<>();response.setContentType("text/json;charset=utf-8");errMsg.put("code","200");errMsg.put("msg","访问失败,该资源受到保护...");response.getWriter().print(errMsg.toString());}
}

等会再说这两个配置

3、继承UserDetails

用我们的登录的用户类继承UserDetails,我这里是Admin

@Data
@AllArgsConstructor
@NoArgsConstructor
public class AdminLogin implements UserDetails {private Admin admin;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}@Overridepublic String getPassword() {return admin.getPassword();}@Overridepublic String getUsername() {return admin.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

我们得重写里面的几个方法,并且把我们的用户类给整进来

Collection<? extends GrantedAuthority> getAuthorities()

这个是权限,我们返回null就行了,这会登录用不到

public String getPassword() {return admin.getPassword();}

这个方法是获取密码,也就是security会从这里获取登录的密码,我们就把我们的用户类的密码让他返回

public String getUsername() { return admin.getUsername();}

这个是获取用户名的方法,也就是security会从这里获取登录的用户名,我们就把我们的用户类的用户名让他返回

@Override
public boolean isAccountNonExpired() {return true;
}@Override
public boolean isAccountNonLocked() {return true;
}@Override
public boolean isCredentialsNonExpired() {return true;
}@Override
public boolean isEnabled() {return true;
}

这几个都是账号相关的,什么账号是否被锁定、是否启用在这里返回结果,我们返回true,如果返回false就登录不了了

4、登录方法

@Service
public class AdminLoginServiceImpl implements AdminLoginService {@Autowiredprivate AuthenticationManager authenticationManager;//管理员登录@Overridepublic Result adminLogin(LoginDto loginDto) {UsernamePasswordAuthenticationToken authenticationToken = newUsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());Authentication authenticate = authenticationManager.authenticate(authenticationToken);AdminLogin adminLogin = (AdminLogin) authenticate.getPrincipal();String jwt = JwtUtil.createJWT(String.valueOf(adminLogin.getAdmin().getId()));//用户信息redisTemplate.opsForValue().set("userId"+adminLogin.getAdmin().getId(), JSONUtil.toJsonStr(adminLogin));return Result.success(jwt);}
}

登录的方法就是调用

Authentication authenticate = authenticationManager.authenticate(authenticationToken);

它里面需要接受的参数类型必须是Authentication类型的

这就是为啥在配置的时候将

AuthenticationManager authenticationManagerBean()这个使用@Bean标记

 UsernamePasswordAuthenticationToken authenticationToken = new
                UsernamePasswordAuthenticationToken(loginDto.getUsername(),loginDto.getPassword());

这个类就可以将我们的用户名和密码封装成继承了Authentication类型的类然后用于登录

UsernamePasswordAuthenticationToken()他的参数是

Object principal, Object credentials

这就分别是用户名和登陆凭证也就是密码

然后登陆成功后返回一个Authentication类型,然后.getPrincipal()这个方法就可以获取登录的用户信息。

5、是怎么完成登录的

这时候我们来看登录流程图(图是盗的)

当我们调用Authentication authenticate = authenticationManager.authenticate(authenticationToken);这个方法的时候做了什么?

我们关注两步就可以了

第一个就是根据用户名查询用户,第二个就是进行密码比对

(1)根据用户名查询用户

因为在执行认证的方法后,会调用DaoAuthencationProvider中的UserDetailService对象中的loadUserByUsername这个方法,如果基于springsecurity的默认配置,这个方法就是实现了UserDetailService这个接口的InMemoryUserDetailsManager这个方法中的loadUserByUsername

我们可以看到进行登录时候调用了loadUserByUsername的方法,这个方法是在

UserDetailsService中写的,看方法名也知道是根据用户名查找用户

因为我们要查的是数据库中的用户数据,我们就可以也可以实现UserDetailService并且重写里面的loadUserByUsername方法   根据数据库查询出用户信息并返回继承了UserDetail的AdminLogin 类

@Service
public class AdminDetailsServiceImpl implements UserDetailsService {@Autowiredprivate AdminService adminService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//根据用户名查询数据库中的用户LambdaQueryWrapper<Admin> wrapper = new LambdaQueryWrapper<>();wrapper.eq(Admin::getUsername,username);Admin admin = adminService.getOne(wrapper);//如果根据用户名查找不到用户if (ObjectUtils.isEmpty(admin)){throw new TxdException(208,"用户不存在");}//返回adminLoginAdminLogin adminLogin = new AdminLogin(admin);return adminLogin;}
}

如果能查到用户就返回,然后进行下一步密码对比,如果用户不存在就抛异常

(2)密码对比

这个springsecurity都已经写好了,我们看看源码就行,找到那个我们自定义的security的配置类

ctrl点进BCryptPasswordEncoder加密方式

里面的boolean matches(CharSequence rawPassword, String encodedPassword)这个方法就是密码对比,它里面又会执行BCrypt.checkpw(rawPassword.toString(), encodedPassword)这个方法。总之就是将前端传来的密码进行加密后与数据库的进行对比

为啥不能将数据库的密码解析后对比传来的明文密码呢?因为他这个加密之后是不可逆的

然后到这登录基本就完事了

新问题,数据库中还没加密后的用户数据怎么办?

6、注册用户加密密码

将前端传来的密码使用security配置类中的加密方式加密后就行

7、登录过滤器

在前面配置的时候已经整过代码了,在这里获取token并校验,校验完之后获取里面的用户id,根据用户id获取redis里面的数据,并将用户信息使用UsernamePasswordAuthenticationToken 封装并且放入SecurityContextHolder.getContext().setAuthentication(authenticationToken);中

@Component
public class TokenAuthenticationFilter extends OncePerRequestFilter {@Autowiredprivate StringRedisTemplate redisTemplate;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {String token = request.getHeader("token");if (ObjectUtils.isEmpty(token)){filterChain.doFilter(request,response);return;}Claims claims = null;try {claims = JwtUtil.parseJWT(token);} catch (Exception e) {e.printStackTrace();Map<String, String> errMsg = new HashMap<>();errMsg.put("code","200");errMsg.put("msg","访问失败,请重新登录");response.setContentType("text/json;charset=utf-8");response.getWriter().print(errMsg.toString());return;}Integer userId = Integer.valueOf(claims.getSubject());UserContext.setUser(userId);String userAdmin = redisTemplate.opsForValue().get("userId" + userId);AdminLogin adminLogin = JSONUtil.toBean(userAdmin, AdminLogin.class);UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(adminLogin.getUsername(), adminLogin.getUsername(), null);
//        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(null, null, null);SecurityContextHolder.getContext().setAuthentication(authenticationToken);filterChain.doFilter(request,response);}
}

8、认证失败处理器

当用户未认证时访问资源提示的信息

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable
{private static final long serialVersionUID = -8970718410437077606L;@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)throws IOException{Map<String, String> errMsg = new HashMap<>();response.setContentType("text/json;charset=utf-8");errMsg.put("code","200");errMsg.put("msg","访问失败,该资源受到保护...");response.getWriter().print(errMsg.toString());}
}

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

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

相关文章

k8s kubectl 常用命令

查询节点node kubectl get node查询节点标签 kubectl get node --show-labels查询所有pod kubectl get pod -A查询指定命名空间下的pod kubectl get pod -n project-3428查询pod详细 kubectl -n project-3428 get pod xxxxxxxxxxxx -o widePOD打标签 kubectl label pod my…

vue前端面试题

描述一下Vue.js的响应式数据绑定原理。 答案&#xff1a; Vue.js 使用 Observer、Compile 和 Watcher 三个组件来实现响应式数据绑定。Observer 负责监听数据对象的属性变化&#xff0c;Compile 负责解析模板指令并建立依赖关系&#xff0c;Watcher 则负责在数据变化时执行相应…

实战打靶集锦-027-SoSimple1

文章目录 1.主机发现2. 端口扫描3. 服务枚举4. 服务探查5. 提权5.1 枚举系统信息5.2 枚举定时任务5.3 查看passwd文件5.4 枚举可执行文件5.5 查看家目录5.6 Linpeas提权 6. 获取flag 靶机地址&#xff1a;https://download.vulnhub.com/sosimple/So-Simple-1.7z 1.主机发现 目…

阿里云99元服务器40G ESSD Entry系统盘够用吗?

阿里云99元服务器40G ESSD Entry云盘够用吗&#xff1f;够用&#xff0c;操作系统占15GB左右&#xff0c;还有25G富余。如果是40G ESSD Entry系统盘不够用&#xff0c;还可以为云服务器另外挂载数据盘&#xff0c;所以不用担心40G系统盘不够用。可以在阿里云CLUB中心查看 aliyu…

Flutter 3.13 之后如何监听 App 生命周期事件

在 Flutter 中&#xff0c;您可以监听多个生命周期事件来处理应用程序的不同状态&#xff0c;但今天我们将讨论 didChangeAppLifecycleState 事件。每当应用程序的生命周期状态发生变化时&#xff0c;就会触发此事件。可能的状态有 resumed 、 inactive 、 paused 、 detached …

android 音频焦点,音频策略梳理

音频焦点和音频策略两个不同的概念&#xff0c;容易搞混 先来看下音频焦点和音频策略直接的区别和联系 音频策略的主要功能是为该音频找到合适的硬件设备播放 1 音频策略流程&#xff1a; (从usage->device) attributesBuilder.setUsage--->audioservice.mCarAudioCont…

SpringBoot特性--Profiles

Spring Profiles提供了一种方法来隔离你的应用程序配置的一部分&#xff0c;并使其仅在某些环境中可用。任何Component&#xff0c;Configuration或ConfigurationProperties都可以用Profile标记&#xff0c;以限制它的加载时机&#xff0c;如下面的例子所示。 Configuration(p…

大数据扩展

层面控制点四级三级二级 安全 物理 环境 基础 设施 位置 应保证承载大数据存储、处理和分析的设备机房位于中国境内。应保证承载大数据存储、处理和分析的设备机房位于中国境内。应保证承载大数据存储、处理和分析的设备机房位于中国境内。安全 通信 网络网络 架构b)应保证大数…

idea使用git笔记

1.创建分支和切换分支 创建分支 切换分支 2.把新创建的分支提交到远程服务器上&#xff08;注&#xff1a;如果没有提交的&#xff0c;随便找个文件修改再提交&#xff09; (1)切换到要提交的分支&#xff0c;add (2)commit (3)push 3.在自己分支修改代码及提交到自己的远…

【讲解Node.js常用的命令】进阶版

Node.js常用命令 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境&#xff0c;它使得可以在服务器端运行 JavaScript 代码。Node.js 采用了事件驱动、非阻塞 I/O 模型&#xff0c;非常适用于构建高效的网络应用程序。以下是一些Node.js开发中常用的命令&#xff1…

【K3s】在 AWS EC2 上运行生产 K3s 集群

【K3s】在 AWS EC2 上运行生产 K3s 集群 本文将介绍如何在 AWS EC2 上运行 K3s。您将创建一个 EC2 实例并在 AWS 上运行单节点 K3s 集群。 自托管的 K3s 允许您在裸金属上运行 Kubernetes,并应用较小、低资源工作负载的原则。哦,你还可以控制你的控制平面。这有点像逃离托管…

探索软件工程:构建可靠、高效的数字世界

软件工程是一门涵盖了设计、开发、测试、维护和管理软件的学科&#xff0c;它在如今数字化时代的发展中扮演着至关重要的角色。随着科技的不断进步和社会的不断变迁&#xff0c;软件工程的意义也愈发凸显。本文将探索软件工程的重要性、原则和实践&#xff0c;以及其对当今社会…

Python Qt Designer 初探

代码下载在最下面 #开发环境安装# 本示例在Windows11下, 使用VSCode开发, Python 3.12.2, Qt Designer 5.11 VSCode插件Python、Python Debugger、PYQT Integration、Pylance (准备) VSCode自行官网下载 Visual Studio Code - Code Editing. Redefined (准备) Python 直接…

Hive自定义UpperGenericUDF函数

Hive自定义UpperGenericUDF函数 当创建自定义函数时&#xff0c;推荐使用 GenericUDF 类而不是 UDF 类&#xff0c;因为 GenericUDF 提供了更灵活的功能和更好的性能。以下是使用 GenericUDF 类创建自定义函数的步骤&#xff1a; 编写Java函数逻辑&#xff1a;编写继承自 Gener…

上位机图像处理和嵌入式模块部署(qmacvisual拟合直线)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 测量是图像处理的一个基本技能。那么测量的前提&#xff0c;就是我们需要在图像中找出特定的集合图形&#xff0c;比如说直线。当然&#xff0c;发…

# Maven Bom 的使用

Maven Bom 的使用 文章目录 Maven Bom 的使用概述BOM特点优点缺点 MavenMaven 安装安装步骤settingx.ml常用仓库地址Idea 使用maven常见坑 SpringBoot 项目Bom使用案例项目结构主项目 zerocode-back-servezc-dependency&#xff08;第三方jar管理&#xff09;子模块zc-serve子模…

Java中的监视器锁 (synchronized 关键字)

在使用多线程的时候,我们会经常遇到线程不安全的问题,即多个线程访问共享数据时出现不确定的结果或异常,此时引入我们今天要介绍的synchronized 关键字 一.synchronized 的特性 1) 互斥 synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如…

手机运营商二要素检测:重塑信任基石,筑牢信息安全屏障

随着移动互联网的普及和数字经济的快速发展&#xff0c;用户信息安全的重要性日益凸显。运营商二要素检测作为一种强大的安全验证机制&#xff0c;以其精准匹配与实时验证的特性&#xff0c;为各类应用场景提供了一种可靠的身份识别解决方案&#xff0c;正在成为众多企业和服务…

PyTorch 深度学习(GPT 重译)(六)

十四、端到端结节分析&#xff0c;以及接下来的步骤 本章内容包括 连接分割和分类模型 为新任务微调网络 将直方图和其他指标类型添加到 TensorBoard 从过拟合到泛化 在过去的几章中&#xff0c;我们已经构建了许多对我们的项目至关重要的系统。我们开始加载数据&#xf…

11种创造型设计模式(下)

观察者模式 我们可以比喻观察者模式是一种类似广播的设计模式 介绍 观察者模式&#xff1a;对象之间多对一依赖的一种设计方案&#xff0c;被依赖的对象是Subject&#xff0c;依赖的对象是Observer&#xff0c;Subject通知Observer变化。 代码 说明&#xff1a; WeatherStat…