SpringBoot security 安全认证(一)——登录验证

本节内容:使用springboot自动security模块实现用户登录验证功能;
登录过程如下图:
在这里插入图片描述
AuthenticationManager内容实现用户账号密码验证,还可以对用户状态(启用/禁用),逻辑删除,账号是否被锁定等判断。密码加密方式内置了好几种,我使用的是BCryptPasswordEncoder。那么我们在用户注册时密码要使用 new BCryptPasswordEncoder().encode(pwd)进行加密。

代码实现过程:
1、引入相关依赖;
2、创建UserDetails实现类LoginUser;
3、创建UserDetailsService实现类UserDetailsServiceImpl;
4、SecurityConfiguration配置,将UserDetailsServiceImpl注入相关对象,并配置加密算法;
5、实现账号密码验证;

pom.xml引入依赖

<dependencies><!-- 实现对 Spring MVC 的自动化配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- spring security 安全认证 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- Token生成与解析--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency><!-- 解析客户端操作系统、浏览器等 --><dependency><groupId>eu.bitwalker</groupId><artifactId>UserAgentUtils</artifactId><version>1.21</version></dependency>
<!-- String工具类 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.12.0</version></dependency><!-- 阿里JSON解析器 --><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.34</version></dependency><!-- 方便等会写单元测试 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!-- 引入 Swagger 依赖 --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version></dependency><!-- 引入 Swagger UI 依赖,以实现 API 接口的 UI 界面 --><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version></dependency></dependencies>

UserDetails的实现类LoginUser

是数据库用户到Spring用户的转换,提供给Spring内部获取用户账号、状态等的。

package com.luo.chengrui.labs.lab04.model;import com.alibaba.fastjson2.annotation.JSONField;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;/*** 登录用户身份权限** @author ruoyi*/
public class LoginUser implements UserDetails {private static final long serialVersionUID = 1L;/*** 用户ID*/private String userId;/*** 用户唯一标识*/private String token;/*** 登录时间*/private Long loginTime;/*** 过期时间*/private Long expireTime;/*** 登录IP地址*/private String ipaddr;/*** 登录地点*/private String loginLocation;/*** 浏览器类型*/private String browser;/*** 操作系统*/private String os;//数据库用户映射表private SysUser user;public LoginUser(String userId, SysUser user) {this.userId = userId;this.user = user;}public static long getSerialVersionUID() {return serialVersionUID;}public String getUserId() {return userId;}public void setUserId(String userId) {this.userId = userId;}public String getToken() {return token;}public void setToken(String token) {this.token = token;}public Long getLoginTime() {return loginTime;}public void setLoginTime(Long loginTime) {this.loginTime = loginTime;}public Long getExpireTime() {return expireTime;}public void setExpireTime(Long expireTime) {this.expireTime = expireTime;}public String getIpaddr() {return ipaddr;}public void setIpaddr(String ipaddr) {this.ipaddr = ipaddr;}public String getLoginLocation() {return loginLocation;}public void setLoginLocation(String loginLocation) {this.loginLocation = loginLocation;}public String getBrowser() {return browser;}public void setBrowser(String browser) {this.browser = browser;}public String getOs() {return os;}public void setOs(String os) {this.os = os;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}@JSONField(serialize = false)@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getUsername();}//账号未过期 @JSONField(serialize = false)@Overridepublic boolean isAccountNonExpired() {return true;}//账号未被锁定@JSONField(serialize = false)@Overridepublic boolean isAccountNonLocked() {return true;}//密码未过期@JSONField(serialize = false)@Overridepublic boolean isCredentialsNonExpired() {return true;}/*** 是否可用 ,禁用的用户不能身份验证** @return*/@JSONField(serialize = false)@Overridepublic boolean isEnabled() {return true;}
}

LoginUser类看着字段较多,基本都是和业务相关,比如记录登录用户的IP,地址,登录时间等的。看实际情况,可以优化掉的,仅保留SysUser对象也是完全可以的。

UserDetailsService接口实现类 UserDetailsServiceImpl

实现根据用户名获取用户信息,提供给spring内部调用的。

package com.luo.chengrui.labs.lab04.service;import com.luo.chengrui.labs.lab04.model.LoginUser;
import com.luo.chengrui.labs.lab04.model.SysUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import java.util.Objects;/*** 用户验证处理** @author ruoyi*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);@Autowiredprivate UserService userService;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SysUser user = userService.selectUserByUserName(username);if (user == null) {log.info("登录用户:{} 不存在.", username);throw new RuntimeException("登录用户:" + username + " 不存在");} else if (Objects.equals(1, user.getDelFlag())) {log.info("登录用户:{} 已被删除.", username);throw new RuntimeException("对不起,您的账号:" + username + " 已被删除");} else if (Objects.equals(0, user.getStatus())) {log.info("登录用户:{} 已被停用.", username);throw new RuntimeException ("对不起,您的账号:" + username + " 已停用");}return createLoginUser(user);}public UserDetails createLoginUser(SysUser user) {return new LoginUser(user.getUserId(),user);}
}

1、以上判断用户状态和是否被删除等操作也可以交由spring去做,你只要在LoginUser类中相关方法中返回结果即可。在这里可以做额外的合法性判断。(如这个账号登录次数超限了,并且可以提示相关登录失败信息)
2、实现了根据用户账号查询用户信息方法,最后返回了我们上面定义的LoginUser对象。

LoginUservice登录实现

package com.luo.chengrui.labs.lab04.service;import com.luo.chengrui.labs.lab04.model.LoginUser;
import com.luo.chengrui.labs.lab04.utils.UUID;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;/*** @author* @version 1.0.0* @description* @createTime 2024/01/05*/
@Service
public class LoginService {@Resourceprivate AuthenticationManager authenticationManager;private static final String secret = "abcdefghijklmnopqrstuvwxyz";public String login(String username, String password) {// 用户验证Authentication authentication = null;try {UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);authentication = authenticationManager.authenticate(authenticationToken);} catch (Exception e) {//这里应该按不同异常类型分别捕获,更精确的提示用户登录失败的原因。看业务需要throw new RuntimeException("用户名或密码错误");} finally {}LoginUser loginUser = (LoginUser) authentication.getPrincipal();// 生成tokenreturn createToken(loginUser);}/*** 创建令牌** @param loginUser 用户信息* @return 令牌*/public String createToken(LoginUser loginUser) {String token = UUID.fastUUID().toString();loginUser.setToken(token);Map<String, Object> claims = new HashMap<>();claims.put("LOGIN_USER_KEY", token);return createToken(claims);}private String createToken(Map<String, Object> claims) {String token = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();return token;}/*** 刷新令牌有效期,是指刷新缓存中存储的token信息。我们本示例中暂不做缓存。** @param loginUser 登录信息*/public void refreshToken(LoginUser loginUser) {loginUser.setLoginTime(System.currentTimeMillis());loginUser.setExpireTime(loginUser.getLoginTime() + 30 * 60 * 1000);}
}

SecurityConfig

1、创建鉴权管理器:AuthenticationManager ;
2、设置请求过滤;
3、设置密码加密算法;
4、设置用户获取对象;

package com.luo.chengrui.labs.lab04.config;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
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.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.filter.CorsFilter;/*** spring security配置** @author */
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {/*** 自定义用户认证逻辑*/@Autowiredprivate UserDetailsService userDetailsService;/*** 解决 无法直接注入 AuthenticationManager** @return* @throws Exception*/@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}/*** anyRequest          |   匹配所有请求路径*/@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {httpSecurity// CSRF禁用,因为不使用session.csrf().disable()// 过滤请求.authorizeRequests()// 所有请求均以放行.anyRequest().permitAll().and().headers().frameOptions().disable();}/*** 强散列哈希加密实现*/@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder() {return new BCryptPasswordEncoder();}/*** 设置密码加密算法*/@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());}
}

到此,使用Spring security登录验证的核心代码就写完了。再一个实现UserService类实现从数据库查询用户

UserServiceImpl

package com.luo.chengrui.labs.lab04.service;import com.luo.chengrui.labs.lab04.model.SysUser;
import com.luo.chengrui.labs.lab04.utils.SecurityUtils;
import com.luo.chengrui.labs.lab04.utils.StringUtils;
import org.springframework.stereotype.Service;import java.util.*;
import java.util.stream.Collectors;/*** @author* @version 1.0.0* @description* @createTime 2024/01/31*/
@Service
public class UserService {private static final Map<String, SysUser> userMap = new HashMap<>();static {userMap.put("admin", new SysUser("1", "admin", SecurityUtils.encryptPassword("admin123"), 1));}public List<SysUser> selectUser() {return userMap.entrySet().stream().map(item -> item.getValue()).collect(Collectors.toList());}public SysUser selectUserByUserName(String username) {SysUser user = userMap.get(username);if (user == null) {throw new RuntimeException("用户不存在");}return user;}public SysUser registerUser(SysUser user) {if (user == null) {throw new RuntimeException("用户信息不能为空");}if (StringUtils.isEmpty(user.getUsername())) {throw new RuntimeException("用户名不能为空");}if (StringUtils.isEmpty(user.getPassword())) {throw new RuntimeException("密码不能为空");}if (userMap.get(user.getUsername()) != null) {throw new RuntimeException("用户名已经存在");}user.setUserId(UUID.randomUUID().toString());user.setStatus(1);user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));userMap.put(user.getUserId(), user);return user;}public SysUser selectUser(String username) {return userMap.get(username);}
}

简单模拟了根据账号查询用户的接口,实际是事先在userMap中放了一个admin用户而已。

SysUser数据库对象

package com.luo.chengrui.labs.lab04.model;/*** @author* @version 1.0.0* @description* @createTime 2024/01/31*/
public class SysUser {private String userId;private String username;private String password;private Integer delFlag;private Integer status;public SysUser() {}public SysUser(String userId, String username, String password,  Integer status) {this.userId = userId;this.username = username;this.password = password;this.status = status;}public String getUserId() {return userId;}public void setUserId(String userId) {this.userId = userId;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public Integer getDelFlag() {return delFlag;}public void setDelFlag(Integer delFlag) {this.delFlag = delFlag;}public Integer getStatus() {return status;}public void setStatus(Integer status) {this.status = status;}
}

controller

package com.luo.chengrui.labs.lab04.controller;import com.luo.chengrui.labs.lab04.model.AjaxResult;
import com.luo.chengrui.labs.lab04.model.LoginBody;
import com.luo.chengrui.labs.lab04.service.LoginService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;/*** @author* @version 1.0.0* @description* @createTime 2023/07/17*/
@RestController
@Api(tags = "用户 API 接口")
public class LoginController {@AutowiredLoginService loginService;@ApiOperation(value = "用户登录 ", notes = "目前仅仅是作为测试,所以返回用户全列表")@PostMapping("/login")public AjaxResult login(@RequestBody LoginBody loginBody) {AjaxResult ajax = AjaxResult.success();// 生成令牌String token = loginService.login(loginBody.getUsername(), loginBody.getPassword());ajax.put("token", token);return ajax;}}

swagger配置

package com.luo.chengrui.labs.lab04.config;import com.luo.chengrui.labs.lab04.service.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;import java.util.ArrayList;
import java.util.List;/*** 访问地址:/swagger-ui.html** @author* @version 1.0.0* @description* @createTime 2023/07/17*/
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {@AutowiredTokenService tokenService;@Beanpublic Docket createRestApi() {/* 让swagger页面上的每个接口都添加一个Header参数,用来传递token参数*/ParameterBuilder ticketPar = new ParameterBuilder();List<Parameter> pars = new ArrayList<Parameter>();ticketPar.name(tokenService.getHeader()).description("user ticket")//Token 以及Authorization 为自定义的参数,session保存的名字是哪个就可以写成那个.modelRef(new ModelRef("string")).parameterType("header").required(false).build(); //header中的ticket参数非必填,传空也可以pars.add(ticketPar.build()); //根据每个方法名也知道当前方法在设置什么参数// 创建 Docket 对象return new Docket(DocumentationType.SWAGGER_2) // 文档类型,使用 Swagger2.apiInfo(this.apiInfo()) // 设置 API 信息// 扫描 Controller 包路径,获得 API 接口.select().apis(RequestHandlerSelectors.basePackage("com.luo.chengrui.labs.lab04.controller")).paths(PathSelectors.any())// 构建出 Docket 对象.build().globalOperationParameters(pars);}/*** 创建 API 信息*/private ApiInfo apiInfo() {return new ApiInfoBuilder().title("测试接口文档示例").description("我是一段描述").version("1.0.0") // 版本号.contact(new Contact("XX", "http://localhost", "luodz@gmail.com")) // 联系人.build();}
}

Swaager请求示例:
在这里插入图片描述
响应结果:
在这里插入图片描述

后台方法调用:
1、调用 UserDetailsServiceImpl.LoadUserByUsername方法,获取用户信息;
在这里插入图片描述

2、判断用户各种可用状态和密码合法性。
在这里插入图片描述
小结:本节主要演示了如何使用Spring去实现登录验证
1、创建UserDetails接口实现类,UserDetails是Spring内部定义的登录用户信息,包含账号、密码、删除状态、禁用状态、锁定状态、密码过期状态;
2、创建UserDetailsService接口实现类,实现loadUserByUsername(String username)方法;
3、用户合法性验证,仅一行代码即可完成用户合法性验证:

Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
LoginUser loginUser = (LoginUser) authentication.getPrincipal();

下一节,咱们可以实现对接口访问的拦截了。

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

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

相关文章

Vue3_基础使用_1

这节主要介绍&#xff1a; vue2与vue3的区别&#xff0c;创建响应式的数据&#xff0c;setup语法糖的使用&#xff0c;watch监听&#xff0c;及vue3创建项目。 vue2的选项式与vue3的组合式区别&#xff1a; 选项式&#xff1a;vue2中数据与方法计算属性等等&#xff0c;针对…

单元测试框架深入(一):单元测试框架深入

一、一个简单的例子 1、引入Maven依赖&#xff1a;JUnit框架和Surefire插件 2.在src/test/java目录下新建名字以“Test”结尾的测试类&#xff0c;并用Test注释测试方法 3.运行单元测试用例 或用mvn命令运行单元测试&#xff1a; 二、单元测试基础之单元测试框架&#xff1a;J…

PKG系统安装包及IPSW固件:MacOS 11-14 Sonoma 正式版

MacOS 14 Sonoma&#xff0c;为提高生产力和创造力带来了全新的功能&#xff0c;有了更多使用小部件和令人惊叹的新屏幕保护程序进行个性化设置的方法&#xff0c;对Safari浏览器和视频会议进行了重大更新&#xff0c;以及优化的游戏体验——Mac体验比以往任何时候都更好。 mac…

Docker进阶篇-DockerFile

一、简介 Dockerfile是用来构建Docker镜像的文本文件&#xff0c;是由一条条构建镜像所需的指令和参数构成的脚 本。 构建步骤&#xff1a; 1、编写Dockerfile文件 2、docker build命令构建镜像 3、docker run依镜像运行容器实例 二、Docker构建过程解析 1、Dockerfile…

Spring-mvc、Spring-boot中如何在调用同类方法时触发AOP

1. 问题描述 Spring-mvc和Spring-boot中aop可以实现代理的功能&#xff0c;我们可以借此实现事务和日志记录或者限流等多种操作。但是&#xff0c;如果你在一个方法中调用其同类下的其他方法的时候不会触发AOP。本文主要说明其原因及解决办法和实现原理。 2. 原因 AIOP的本质是…

【百度Apollo】自动驾驶的领航者

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! ⛳️ 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下…

【leetcode题解C++】654.最大二叉树 and 617.合并二叉树

654. 最大二叉树 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&#xff0c;其值为 nums 中的最大值。递归地在最大值 左边 的 子数组前缀上 构建左子树。递归地在最大值 右边 的 子数组后缀上 构建右子树。 返回 nums …

三子棋游戏小课堂

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 今天的主菜是&#xff0c;C语言实现的三子棋小游戏&#xff0c; 所属专栏&#xff1a; C语言知识点 主厨的主页&#xff1a;Chef‘s blog 前言&…

3月之前 不要乱买电车

文 | AUTO芯球 作者 | 刘琪 刘琪3月之前不要急着买车 尤其不要着急买电车 看完 你一定会回来谢我 首先啊 电车的价格战才刚刚开始 你越早买 就越可能成为大冤种 前段时间 极氪007 23万的价格配100度电池 800V快充 舒适性配置更是直接拉满 现在又有品牌搞终身免费充…

康姿百德床垫价格合理功效好,用科技力量守护您的睡眠健康

现代生活中&#xff0c;优质睡眠的观念已深入人心。人们渐渐认识到&#xff0c;一个舒适的床垫不仅仅是睡眠的工具&#xff0c;更是健康的守护者。很多朋友在选购床垫一掷千金&#xff0c;却找不到一款合适的床垫。康姿百德床垫是专为提升睡眠质量研发的床垫&#xff0c;成为了…

Jmeter高级使用

文章目录 JMeter之计数器JMeter之集合点JMeter之断言JMeter之动态关联后置处理器&#xff1a;正则表达式提取器 JMeter之分布式测试JMeter之组件执行顺序元件的作用域元件的执行顺序配置元件Http Cookie管理器 多协议接口的性能测试Debug采样器Http请求中文乱码的解决Post参数设…

AI大模型专题:2024大模型安全流通平台市场厂商评估报告

今天分享的是AI大模型系列深度研究报告&#xff1a;《AI大模型专题&#xff1a;2024大模型安全流通平台市场厂商评估报告》。 &#xff08;报告出品方&#xff1a;揽睿星舟&#xff09; 报告共计&#xff1a;22页 大模型安全流通平台市场分析 企业需要大模型安全流通平台覆盖…