Spring Security入门教程:实现自定义用户配置

在上一篇文章中:Spring Security入门教程:利用Spring Security实现安全控制
我们学会了使用Spring Security实现安全控制,学会了他的基础使用,这节课我们来学习一下它的自定义的功能,更深入的了解和使用Spring Security。

登录成功失败和注销

在Spring Security中我们可以自己实现登录成功跟登录失败之后我们系统做的事情。首先我们定义一个登陆失败的类。我们需要实现一个AuthenticationFailureHandler 接口,并且重写他的onAuthenticationFailure方法。

package com.masiyi.springsecuritydemo.handler;import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {Map<String, Object> result = new HashMap<String, Object>();result.put("msg", "登录失败: "+exception.getMessage());result.put("status", 500);response.setContentType("application/json;charset=UTF-8");String s = new ObjectMapper().writeValueAsString(result);response.getWriter().println(s);}
}

例如在我们这个上面,我们给要用方返回一个登录失败的提示信息。
之后我们在自定义一个登录成功的处理类。他跟上面的登陆失败的。写法类似,也是实现一个AuthenticationSuccessHandler 接口,在这个方法里面。我们给调用端返回一个登录成功的信息。

package com.masiyi.springsecuritydemo.handler;import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {Map<String, Object> result = new HashMap<String, Object>();result.put("msg", "登录成功");result.put("status", 200);response.setContentType("application/json;charset=UTF-8");String s = new ObjectMapper().writeValueAsString(result);response.getWriter().println(s);}
}

当然类似的还有退出成功的处理类

package com.masiyi.springsecuritydemo.handler;import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;public class MyLogoutSuccessHandler implements LogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {Map<String, Object> result = new HashMap<String, Object>();result.put("msg", "注销成功");result.put("status", 200);response.setContentType("application/json;charset=UTF-8");String s = new ObjectMapper().writeValueAsString(result);response.getWriter().println(s);}
}

订完这三个自定义的类之后,我们需要给它配置。这样的话,spring security就知道我们需要他做一些什么事情。我们写一个配置类如下。我们刚刚写好的自定义的处理类给他set值进去。这样的话我们再进行一个登录成功,登录失败跟退出操作。他就会执行我们自定义类里面的逻辑。

package com.masiyi.springsecuritydemo.config;import com.masiyi.springsecuritydemo.handler.MyAuthenticationFailureHandler;
import com.masiyi.springsecuritydemo.handler.MyAuthenticationSuccessHandler;
import com.masiyi.springsecuritydemo.handler.MyLogoutSuccessHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeHttpRequests().mvcMatchers("/user").permitAll().anyRequest().authenticated().and().formLogin().successHandler(new MyAuthenticationSuccessHandler()).failureHandler(new MyAuthenticationFailureHandler()).and().logout().logoutSuccessHandler(new MyLogoutSuccessHandler());}
}

获取登录成功的用户信息


@RestController
public class BasicController {@RequestMapping("/auth")public String hello() {//org.springframework.security.web.context.HttpRequestResponseHolder 关键方法Authentication authentication = SecurityContextHolder.getContext().getAuthentication();org.springframework.security.core.userdetails.User principal = (org.springframework.security.core.userdetails.User) authentication.getPrincipal();System.out.println("身份 :"+principal.getUsername());System.out.println("凭证 :"+authentication.getCredentials());System.out.println("权限 :"+authentication.getAuthorities());return "hello security";}}

SecurityContextHolder.getContext() 获取Spring Security的上下文,上下文中存储着登录的用户信息Authentication ,利用SecurityContextHolder.getContext().getAuthentication()即可获取

自定义内存用户

我们在WebSecurityConfigurer里面添加如下代码,之前我们都是在yml文件中配置用户和密码,这样的话我们可以在代码中使用内存中的用户信息

 @Beanpublic UserDetailsService userDetailsService() {InMemoryUserDetailsManager inMemoryUserDetailsManager= new InMemoryUserDetailsManager();UserDetails u1 = User.withUsername("masiyi").password("{noop}111").roles("USER").build();inMemoryUserDetailsManager.createUser(u1);return inMemoryUserDetailsManager;}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService());}

自定义数据源用户

在实际工作和项目中我们肯定不会和上门一样把用户和密码写死,大部分我们是把用户信息存储在数据库中的,所以我们就需要学习在Spring Security中自定义数据源用户,这里以mysql数据库为例

首先在pom文件中引入以下jar包:

      <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.0</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.38</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.7</version></dependency>

接着在配置文件中加入你的数据库连接信息:

# datasource
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springsecurity?characterEncoding=UTF-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=root# mybatis
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.masiyi.springsecuritydemo.entity

在数据库中执行以下sql以创建对应的数据表

/*Navicat Premium Data TransferSource Server         : localhostSource Server Type    : MySQLSource Server Version : 50738Source Host           : localhost:3306Source Schema         : springsecurityTarget Server Type    : MySQLTarget Server Version : 50738File Encoding         : 65001Date: 20/12/2023 19:47:32
*/SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,`name_zh` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'ROLE_product', '商品管理员');
INSERT INTO `role` VALUES (2, 'ROLE_admin', '系统管理员');
INSERT INTO `role` VALUES (3, 'ROLE_user', '用户管理员');-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,`enabled` tinyint(1) DEFAULT NULL,`accountNonExpired` tinyint(1) DEFAULT NULL,`accountNonLocked` tinyint(1) DEFAULT NULL,`credentialsNonExpired` tinyint(1) DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'root', '{noop}123', 1, 1, 1, 1);
INSERT INTO `user` VALUES (2, 'admin', '{noop}123', 1, 1, 1, 1);
INSERT INTO `user` VALUES (3, 'blr', '{noop}123', 1, 1, 1, 1);-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role`  (`id` int(11) NOT NULL AUTO_INCREMENT,`uid` int(11) DEFAULT NULL,`rid` int(11) DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE,INDEX `uid`(`uid`) USING BTREE,INDEX `rid`(`rid`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1, 1);
INSERT INTO `user_role` VALUES (2, 1, 2);
INSERT INTO `user_role` VALUES (3, 2, 2);
INSERT INTO `user_role` VALUES (4, 3, 3);SET FOREIGN_KEY_CHECKS = 1;

根据user表新建一个对应的实体类,但是这个类要实现UserDetails 接口

package com.masiyi.springsecuritydemo.entity;import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;import java.util.ArrayList;
import java.util.Collection;
import java.util.List;public class User implements UserDetails {private Integer id;private String username;private String password;private Boolean enabled;private Boolean accountNonExpired;private Boolean accountNonLocked;private Boolean credentialsNonExpired;private List<Role> roles = new ArrayList<>();@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {List<GrantedAuthority> grantedAuthorities = new ArrayList<>();roles.forEach(role -> grantedAuthorities.add(new SimpleGrantedAuthority(role.getName())));return grantedAuthorities;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}@Overridepublic String getUsername() {return username;}@Overridepublic String getPassword() {return password;}@Overridepublic boolean isAccountNonExpired() {return accountNonExpired;}@Overridepublic boolean isAccountNonLocked() {return accountNonLocked;}@Overridepublic boolean isCredentialsNonExpired() {return credentialsNonExpired;}@Overridepublic boolean isEnabled() {return enabled;}public void setUsername(String username) {this.username = username;}public void setPassword(String password) {this.password = password;}public Boolean getEnabled() {return enabled;}public void setEnabled(Boolean enabled) {this.enabled = enabled;}public Boolean getAccountNonExpired() {return accountNonExpired;}public void setAccountNonExpired(Boolean accountNonExpired) {this.accountNonExpired = accountNonExpired;}public Boolean getAccountNonLocked() {return accountNonLocked;}public void setAccountNonLocked(Boolean accountNonLocked) {this.accountNonLocked = accountNonLocked;}public Boolean getCredentialsNonExpired() {return credentialsNonExpired;}public void setCredentialsNonExpired(Boolean credentialsNonExpired) {this.credentialsNonExpired = credentialsNonExpired;}public List<Role> getRoles() {return roles;}public void setRoles(List<Role> roles) {this.roles = roles;}
}

Role 角色类

package com.masiyi.springsecuritydemo.entity;public class Role {private Integer id;private String name;private String nameZh;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getNameZh() {return nameZh;}public void setNameZh(String nameZh) {this.nameZh = nameZh;}
}

新建一个xml文件对应user

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.masiyi.springsecuritydemo.dao.UserDao"><!--查询单个--><select id="loadUserByUsername" resultType="User">select id,username,password,enabled,accountNonExpired,accountNonLocked,credentialsNonExpiredfrom userwhere username = #{username}</select><!--查询指定行数据--><select id="getRolesByUid" resultType="Role">select r.id,r.name,r.name_zh nameZhfrom role r,user_role urwhere r.id = ur.ridand ur.uid = #{uid}</select>
</mapper>

UserDao 接口

package com.masiyi.springsecuritydemo.dao;import com.masiyi.springsecuritydemo.entity.Role;
import com.masiyi.springsecuritydemo.entity.User;
import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapper
public interface UserDao {//根据用户名查询用户User loadUserByUsername(String username);//根据用户id查询角色List<Role> getRolesByUid(Integer uid);
}

MyUserDetailService类,实现org.springframework.security.core.userdetails.UserDetailsService接口,重写loadUserByUsername方法。

package com.masiyi.springsecuritydemo.service;import com.masiyi.springsecuritydemo.dao.UserDao;
import com.masiyi.springsecuritydemo.entity.User;
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.Component;
import org.springframework.util.ObjectUtils;@Component
public class MyUserDetailService implements UserDetailsService {private final UserDao userDao;@Autowiredpublic MyUserDetailService(UserDao userDao) {this.userDao = userDao;}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userDao.loadUserByUsername(username);if (ObjectUtils.isEmpty(user)) {throw new RuntimeException("用户不存在");}user.setRoles(userDao.getRolesByUid(user.getId()));return user;}
}

在WebSecurityConfigurer类中把

    @Beanpublic UserDetailsService userDetailsService() {InMemoryUserDetailsManager inMemoryUserDetailsManager= new InMemoryUserDetailsManager();UserDetails u1 = User.withUsername("masiyi").password("{noop}111").roles("USER").build();inMemoryUserDetailsManager.createUser(u1);return inMemoryUserDetailsManager;}

注释掉,用下面的配置

    @Autowiredprivate MyUserDetailService myUserDetailService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(myUserDetailService);}

这样,就可以读取自定义的用户了,即自定义数据源用户。

注册,加密密码,登录解密密码

在上面我们是直接初始化用户的,我们并没有添加用户的功能,但是在实际项目当中,肯定要有注册的功能,所以我们给前端写一个注册的方法

package com.masiyi.springsecuritydemo.controller;import com.masiyi.springsecuritydemo.service.MyUserDetailService;
import com.masiyi.springsecuritydemo.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** @Author masiyi* @Date 2023/12/20* @PackageName:com.masiyi.springsecuritydemo.controller* @ClassName: UserController* @Description: TODO* @Version 1.0*/
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate MyUserDetailService myUserDetailService;@PostMapping("/register")public Boolean register(@RequestBody User userInfo){return myUserDetailService.insertUser(userInfo);}}

MyUserDetailService 类中我们的insertUser方法如下


import com.masiyi.springsecuritydemo.dao.UserDao;
import com.masiyi.springsecuritydemo.entity.User;
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.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;@Component
public class MyUserDetailService implements UserDetailsService {@Autowiredprivate  UserDao userDao;@Autowiredprivate PasswordEncoder passwordEncoder;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userDao.loadUserByUsername(username);if (ObjectUtils.isEmpty(user)) {throw new RuntimeException("用户不存在");}user.setRoles(userDao.getRolesByUid(user.getId()));return user;}public Boolean insertUser(User userInfo) {//登录的时候在 UsernamePasswordAuthenticationFilter里面解密userInfo.setPassword(passwordEncoder.encode(userInfo.getPassword()));return userDao.insertUser(userInfo);}
}

对应的xml文件的sql如下:

    <insert id="insertUser">insert into user(username,password) values (#{userInfo.username},#{userInfo.password})</insert>

配置类要改的内容如下

在这里插入图片描述

package com.masiyi.springsecuritydemo.config;import com.masiyi.springsecuritydemo.handler.MyAuthenticationFailureHandler;
import com.masiyi.springsecuritydemo.handler.MyAuthenticationSuccessHandler;
import com.masiyi.springsecuritydemo.handler.MyLogoutSuccessHandler;
import com.masiyi.springsecuritydemo.service.MyUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {/*** 这里有两种方式 authorizeHttpRequests 和 authorizeRequests* @param http the {@link HttpSecurity} to modify* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeHttpRequests().mvcMatchers("/user/register").permitAll().anyRequest().authenticated().and().formLogin().successHandler(new MyAuthenticationSuccessHandler()).failureHandler(new MyAuthenticationFailureHandler()).and().logout().logoutSuccessHandler(new MyLogoutSuccessHandler()).and().csrf().disable();}
//
//    @Bean
//    public UserDetailsService userDetailsService() {
//        InMemoryUserDetailsManager inMemoryUserDetailsManager
//                = new InMemoryUserDetailsManager();
//        UserDetails u1 = User.withUsername("masiyi")
//                .password("{noop}111").roles("USER").build();
//        inMemoryUserDetailsManager.createUser(u1);
//        return inMemoryUserDetailsManager;
//    }@Beanpublic UserDetailsService userDetailsService() {return new MyUserDetailService();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());}/*** 指定加密方式*/@Beanpublic PasswordEncoder passwordEncoder() {// 使用BCrypt加密密码return new BCryptPasswordEncoder();}
}

至此spring security实现自定义用户配置,我们就讲到这里。下面我会给大家讲一下spring security的授权的功能。

项目的地址就在
https://gitee.com/WangFuGui-Ma/spring-security-admin
如果大家对这篇文章或者专栏有兴趣或者对大家有所帮助的话,欢迎关注点赞。加评论。
我们spring security的进阶专栏见。

在这里插入图片描述

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

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

相关文章

时序分解 | Matlab实现LMD局域均值分解

时序分解 | Matlab实现LMD局域均值分解 目录 时序分解 | Matlab实现LMD局域均值分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 时序分解 | Matlab实现LMD局域均值分解 Matlab语言 1.算法新颖小众&#xff0c;用的人很少&#xff0c;包含分解图 2.直接替换数据即可用…

Leecode热题100---3:无重复字符的最长子串

题目&#xff1a;给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串的长度。 C&#xff1a; 指针法&#xff0c;使用at读取字符串中的值&#xff1b; #include <iostream> #include <string> #include <vector> #include <windows.…

nodejs复习笔记

最近在复习nodejs&#xff0c;整理了一些笔记来记录和分享。 非常惭愧&#xff0c;我之前关于nodejs学习的一篇文章《nodejs全栈开发学习笔记》已经是2019年6月份的时候了&#xff0c;大概浏览了一下&#xff0c;发现当时很多不明白的地方&#xff0c;现在通过复习&#xff0c…

想让普通金额数字显示为逗号分隔的数字?

使用vueelement 后台传的数据 1.编写方法 放在method当中 /** 数字转换显示格式 */priceFormat (num, n) {n n || 2;let symbol ",";if (num null) return num;if (typeof num ! number) throw new TypeError(num参数应该是一个number类型);if (n < 0) thro…

angular13 自定义组件全项目都可用 自存

1.定义自定义组件 使用命令创建一个组件 但删除它在你的module里的声明&#xff0c;因为会报错只能引用一次 在本组件中创建一个module文件&#xff0c;引入刚才的组件component.ts import { NgModule } from angular/core; import { CommonModule } from angular/common; im…

粒子系统技术在AI去衣应用中的创新探索

引言&#xff1a; 随着计算机视觉和人工智能技术的飞速发展&#xff0c;AI去衣技术逐渐走进公众视野。这一技术以其独特的应用前景和技术挑战引起了广泛的关注。在实现衣物去除的同时保持图像质量的关键技术之一&#xff0c;便是粒子系统技术。本文将深入探讨粒子系统技术在AI去…

网络审计:为什么定期检查您的网络很重要

在数字化时代&#xff0c;网络安全成为组织和个人必须面对的重要挑战。网络审计是一种关键的安全措施&#xff0c;通过定期检查和评估网络系统的安全性&#xff0c;帮助发现潜在的安全漏洞和弱点&#xff0c;从而防止数据泄露和其他安全威胁。本文将介绍网络审计的重要性&#…

工单系统有哪些?

市面上的工单系统真的非常多&#xff0c;一个个列举肯定说不完&#xff0c;我大致给它们按照不同的依据&#xff0c;进行了一下分类&#xff1a; 1、按部署方式分类&#xff1a; 本地化部署工单系统&#xff1a;适用于对数据安全性要求较高的企业&#xff0c;需要企业在本地服…

华为正式放弃高通芯片 | 百能云芯

5月15日&#xff0c;据外媒最新报道&#xff0c;高通公司正式确认&#xff0c;华为已无需依赖其处理器供应。 在出口许可被正式吊销前&#xff0c;高通的首席财务官已公开表示&#xff0c;预计明年与华为之间的芯片销售将为零&#xff0c;因为华为决定不再从高通购买4G芯片。 报…

探索Playwright:Python下的Web自动化测试革命

在如今这个互联网技术迅速发展的时代&#xff0c;web应用的质量直接关系着企业的声誉和用户的体验。因此&#xff0c;自动化测试成为了保障软件质量的重要手段之一。今天&#xff0c;我将带大家详细了解一款在测试领域大放异彩的神器——Playwright&#xff0c;并通过Python语言…

实名举报:施先燕婚内出轨,身份证:湖北省麻城市

联系方式 施先燕&#xff1a;18664572582 施先燕&#xff1a;13510873532

如何管理测试用例?测试用例有什么管理工具?YesDev

3.1 测试用例 测试用例(Test Case) 是指对一项特定的软件产品进行测试任务的描述&#xff0c;体现测试方案、方法、技术和策略。其内容包括测试目标、测试环境、输入数据、测试步骤、预期结果等。简单地认为&#xff0c;测试用例是为某个特殊目标而编制的一组测试输入、执行条…