单点登录三:添加RBAC权限校验模型功能理解及实现demo

news2024/11/17 15:49:42

1、RBAC权限模型

RBAC(Role-Based Access Control)是一种基于角色的访问控制模型,用于管理系统中用户的权限和访问控制。它将用户、角色和权限之间的关系进行了明确的定义,以实现灵活的权限管理和控制。

1.1、RBAC模型主要包括以下几个核心概念:

1、模型概念:

用户(User):系统中的实际操作者,拥有唯一标识符。

角色(Role):权限的集合,可以被赋予给用户,一个用户可以拥有一个或多个角色。

权限(Permission):系统中的具体操作权限,定义了用户可以执行的操作,可以是功能菜单的接口路径URL,也可以是其他细粒度的操作权限。

2、操作概念:

授权(Authorization):将权限赋予用户或角色的过程,即为用户分配相应的角色或权限。

认证(Authentication):验证用户的身份和权限,确保用户具有访问系统资源的合法权限。

RBAC模型的基本原则是:权限授权应该基于角色,而不是直接授权给特定用户。通过角色的中介,将权限与用户进行解耦,实现了权限的集中管理和灵活分配。

1.2、RBAC模型的优点包括:

  • 简化权限管理:通过角色的授权机制,可以集中管理和维护权限,减少了权限管理的复杂性。
  • 灵活的权限分配:通过给用户分配适当的角色,可以灵活地控制用户的权限,实现细粒度的访问控制。
  • 易于扩展和维护:RBAC模型具有良好的可扩展性和维护性,可以根据业务需求进行权限的增删改操作,而不影响其他部分的权限控制。

在实际应用中,RBAC模型可以结合权限管理框架和安全框架来实现。例如,使用Spring Security框架可以方便地实现RBAC模型的权限控制,通过定义角色、权限和用户的关系,并结合注解和配置来实现权限的校验和访问控制。

总结来说,RBAC模型是一种灵活和可扩展的权限管理模型,通过角色的授权机制实现了权限的集中管理和灵活分配,为系统提供了高效、安全和可维护的访问控制机制。

1.3、RBAC级别

  • RBAC0:用户和角色是多对多,角色和权限是多对多。一个用户拥有的权限,是他所有角色的集合。
  • RBAC1:基于RBAC0,并引入了角色分层的概念,即一个角色分为多个等级,每个等级对应的权限是不一样的。把权限分给用户时,需要分到对应的角色等级。角色等级低时拥有的权限少,角色等级高的权限是所有角色等级低的权限的集合。
  • RBAC2:基于RBAC1,对角色访问进行限制。
    • 如互斥角色限制:同一个用户分配到两个角色,且角色互斥时,那么系统应该提醒只能选择其中一个角色。比如员工拥有商务这个角色,可以创建结算单并提交给财务审核,这时,就不能赋予这个员工财务角色,否则他就自己提交结算自己审核结算单了。
    • 角色数量限制:一个用户拥有的角色数量是有限的;一个角色被分配的用户数量也是有限的。
    • 先决条件限制:用户想获得某个上级角色,必须先获得其下一级的角色。比如想获得产品总监的权限,那就需要从产品助理这一角色开始,再到产品经理角色,最后到产品总监角色。
  • RBAC3:基于RBAC0,对RBAC1和RBAC2进行了整合,是最全面的权限管理。

1.4、RBAC模型复杂情况

当用户非常多的情况,可以设计用户组的概念,用户组代替原先用户的概念,这样能起到批量操作用户角色的作用;

权限组的概念也差不多,是权限过多的情况,一个一个赋权操作太繁琐,可以用权限组批量操作,非常方便。

1.5、springSecruity中实际运用简单的步骤

  1. 用户表、角色表、权限表。然后两张关联表将用户和角色,角色和权限做关联。
  2. 添加用户的时候,给user特定的用户角色。
  3. 登录时,将查询用户信息,将角色信息放进token,或缓存(比如redis,外部缓存好处是能临时控制权限)中。
  4. 登录后的操作,都会经过自定义filter,验证通过的话,可以将用户信息放进springSecruity上下文,和ThreadLocal(这里是因为登录以及验证角色权限是单线程操作,使用本地线程速度最快,比map以及外部缓存都快)。
  5. 我们想使用权限,而且无侵入的去控制的话,可以弄个自定义注解,参数为角色名,验证ThreadLocal中保存的用户信息中的角色中是否包含此角色名,返回boolean类型。

2、demo实现

结合前面我写的两篇帖子,这里整理了一个demo,能够实现一个简单的无侵入的登录及权限控制方案。这里用到springSecruity、jwt、redis、mysql、threadLocal等技术

0、springboot配置

数据源自己配,就是要提前有redis和mysql

server:
  port: 8083

spring:
  application:
    name: player
  redis:
    host: xxxxxxx
    port: xxxxxxx

  datasource:
    url: jdbc:mysql://xxxxxxx:xxxx/xxxxxx
    username: root
    password: Aa@123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: none

mybatis:
  mapper-locations: classpath:mappers/*.xml
  type-aliases-package: com.loong.nba.player.pojo

1、依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

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

    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.11.2</version>
    </dependency>

    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.11.2</version>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>
        <version>0.11.2</version>
        <scope>runtime</scope>
    </dependency>

    <!-- MyBatis -->
    <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>8.0.31</version>
    </dependency>
</dependencies>

2、util

package com.loong.nba.player.util;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * @author jilong
 * @date 2023/5/18
 */
@Component
public class JwtUtil {

    private final Key secretKey;
    private final long expirationTime;

    @Autowired
    private StringRedisTemplate redisTemplate;

    public JwtUtil() {
        this.secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
        this.expirationTime = 120000;

    }

    /**
     * 生成jwt令牌
     *
     * @param username 用户名
     * @return token
     */
    public String generateToken(String username) {
        Date now = new Date();
        Date expirationDate = new Date(System.currentTimeMillis() + expirationTime);
        String token = Jwts.builder()
                .setSubject(username)
                .setIssuedAt(now)
                .setExpiration(expirationDate)
                .signWith(secretKey, SignatureAlgorithm.HS256)
                .compact();
        redisTemplate.opsForValue().set(username, token, expirationTime, TimeUnit.MILLISECONDS);

        return token;
    }

    /**
     * 解析令牌
     *
     * @param token token
     * @return 内容
     */
    public String getUsernameFormToken(String token) {
        return extractClaims(token).getBody().getSubject();
    }

    public boolean validateToken(String token) {
        try {
            Jws<Claims> claims = extractClaims(token);
            return !claims.getBody().getExpiration().before(new Date());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    private Jws<Claims> extractClaims(String token) {
        JwtParser parser = Jwts.parserBuilder().setSigningKey(secretKey).build();
        return parser.parseClaimsJws(token);
    }
}
package com.loong.nba.player.util;

import com.loong.nba.player.pojo.LoginUserBO;

public class SessionUtils {
    static ThreadLocal<LoginUserBO> loginUser = new ThreadLocal<>();

    public static LoginUserBO getLoginUser() {
        return loginUser.get();
    }

    public static void setLoginUser(LoginUserBO loginUserBO) {
        loginUser.set(loginUserBO);
    }
}

3、pojo

package com.loong.nba.player.pojo;

public class UserPO {

    private Integer id;
    private String username;
    private String password;
    private String salt;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    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 String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }
}
package com.loong.nba.player.pojo;

import java.util.List;

public class LoginUserBO {
    private String username;
    private Integer userId;
    private String password;
    private List<Role> roleList;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public List<Role> getRoleList() {
        return roleList;
    }

    public void setRoleList(List<Role> roleList) {
        this.roleList = roleList;
    }
}
package com.loong.nba.player.pojo;

public class Role {
    private Integer id;
    private String roleName;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }
}
package com.loong.nba.player.pojo;

/**
 * @author jilong
 * @date 2023/5/19
 */
public class UserDO {
    private String name;
    private String password;

    public UserDO() {
    }

    public UserDO(String name, String password) {
        this.name = name;
        this.password = password;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

4、config

package com.loong.nba.player.config;

import com.loong.nba.player.filter.JwtAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * @author jilong
 * @date 2023/5/18
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .antMatchers("/login","/user").permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

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

}

5、dao层

package com.loong.nba.player.dao;

import com.loong.nba.player.pojo.Role;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface RoleMapper {

    List<Role> findByUserId(Integer id);

}
package com.loong.nba.player.dao;

import com.loong.nba.player.pojo.UserPO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface UserMapper {

    UserPO findById(Integer id);

    UserPO findByName(@Param("name") String name);

    Integer addUser(@Param("userPO") UserPO userPO);

}

6、service

package com.loong.nba.player.service;

import com.loong.nba.player.pojo.UserDO;
import com.loong.nba.player.pojo.UserPO;

public interface UserService {

    /**
     * 添加用户
     * @return 用户id
     */
     UserPO addUser(UserDO userDO);

     boolean comparePassword(UserDO userDO);


}
package com.loong.nba.player.service;

import com.loong.nba.player.dao.UserMapper;
import com.loong.nba.player.pojo.UserDO;
import com.loong.nba.player.pojo.UserPO;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;

/**
 * @author jilong
 * @date 2023/5/19
 */
@Service
public class UserServiceImpl implements UserService {

    private final UserMapper userMapper;

    public UserServiceImpl(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public UserPO addUser(UserDO userDO) {

        // 定义盐的长度(字节数)
        int saltLength = 6;

        // 创建一个安全的随机数生成器
        SecureRandom secureRandom = new SecureRandom();

        // 生成盐
        byte[] salt = new byte[saltLength];
        secureRandom.nextBytes(salt);

        // 将盐转换为字符串或字节数组
        String saltString = encodeSalt(salt);

        // 计算密码摘要
        String password = encryptPassword(userDO.getPassword(), saltString);

        UserPO userPO = new UserPO();
        userPO.setUsername(userDO.getName());
        userPO.setPassword(password);
        userPO.setSalt(saltString);

        userMapper.addUser(userPO);

        System.out.println("Salt (Base64 string): " + saltString);
        return userPO;
    }

    String encodeSalt(byte[] salt) {
        return Base64.getEncoder().encodeToString(salt);
    }

    byte[] decodeSaltString(String saltString) {
        return Base64.getDecoder().decode(saltString);
    }

    String encryptPassword(String password, String salt) {

        String plaintext = password + salt;

        try {
            // 创建SHA-256算法的MessageDigest实例
            MessageDigest digest = MessageDigest.getInstance("SHA-256");

            // 计算中文字符串的摘要
            byte[] hash = digest.digest(plaintext.getBytes(StandardCharsets.UTF_8));

            // 将摘要字节数组转换为十六进制字符串表示
            String digestHex = bytesToHex(hash);
            System.out.println("SHA-256摘要:" + digestHex);
            return digestHex;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

    // 将摘要字节数组转换为十六进制字符串表示
    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexStringBuilder = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexStringBuilder.append('0');
            }
            hexStringBuilder.append(hex);
        }
        return hexStringBuilder.toString();
    }


    @Override
    public boolean comparePassword(UserDO userDO) {

        UserPO user = userMapper.findByName(userDO.getName());
        String salt = user.getSalt();
        String password = encryptPassword(userDO.getPassword(), salt);
        return password.equals(user.getPassword());
    }

}

7、filter

package com.loong.nba.player.filter;

import com.loong.nba.player.dao.RoleMapper;
import com.loong.nba.player.dao.UserMapper;
import com.loong.nba.player.pojo.LoginUserBO;
import com.loong.nba.player.pojo.Role;
import com.loong.nba.player.pojo.UserPO;
import com.loong.nba.player.util.JwtUtil;
import com.loong.nba.player.util.SessionUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
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.Collections;
import java.util.List;

/**
 * @author jilong
 * @date 2023/5/18
 */
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final String tokenHeader = "Authorization";
    private final String tokenPrefix = "Bearer ";

    private final JwtUtil jwtUtil;
    private final StringRedisTemplate redisTemplate;
    private final UserMapper userMapper;
    private final RoleMapper roleMapper;

    public JwtAuthenticationFilter(JwtUtil jwtUtil, StringRedisTemplate redisTemplate, UserMapper userMapper, RoleMapper roleMapper) {

        this.jwtUtil = jwtUtil;
        this.redisTemplate = redisTemplate;
        this.userMapper = userMapper;
        this.roleMapper = roleMapper;
    }


    @Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String header = request.getHeader(tokenHeader);
        if (!ObjectUtils.isEmpty(header) && header.startsWith(tokenPrefix)) {
            String token = header.replace(tokenPrefix, "");
            if (!ObjectUtils.isEmpty(token) && jwtUtil.validateToken(token)) {
                String username = jwtUtil.getUsernameFormToken(token);
                UserPO userPO = userMapper.findByName(username);
                List<Role> roleList = roleMapper.findByUserId(userPO.getId());
                LoginUserBO loginUserBO = new LoginUserBO();
                loginUserBO.setUserId(userPO.getId());
                loginUserBO.setUsername(username);
                loginUserBO.setPassword(userPO.getPassword());
                loginUserBO.setRoleList(roleList);

                if (redisTemplate.opsForValue().get(username) != null) {
                    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                            username, null, Collections.emptyList());
                    SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                    SessionUtils.setLoginUser(loginUserBO);
                }
            }
        }
        chain.doFilter(request, response);
    }
}

8、controller

package com.loong.nba.player.controller;

import com.loong.nba.player.pojo.UserDO;
import com.loong.nba.player.pojo.UserPO;
import com.loong.nba.player.service.UserServiceImpl;
import com.loong.nba.player.util.JwtUtil;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;

/**
 * @author jilong
 * @date 2023/5/15
 */
@RestController
public class LoginController {

    private final JwtUtil jwtUtil;

    private final UserServiceImpl userService;

    public LoginController(JwtUtil jwtUtil, UserServiceImpl userService) {
        this.jwtUtil = jwtUtil;
        this.userService = userService;
    }


    @PostMapping("/login")
    public ResponseEntity<UserDO> postLogin(@RequestBody UserDO userDO, HttpServletResponse response) {

        if (!userService.comparePassword(userDO)) {
           return ResponseEntity.ok(new UserDO());
        }
        String token = jwtUtil.generateToken(userDO.getName());
        response.addHeader("Authorization", "Bearer " + token);
        return ResponseEntity.ok(userDO);
    }

    @PostMapping("/user")
    public UserPO addUser(@RequestBody UserDO userDO) {
        return userService.addUser(userDO);
    }

    @GetMapping("/test")
    @PreAuthorize("@permission.hasRole("+"'管理员'"+")")
    public String test() {
        return "hello test";
    }
}

9、mapper.xml

<?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.loong.nba.player.dao.RoleMapper">
    <select id="findByUserId" resultType="Role">
        SELECT role.id , role.rolename FROM role where id
                  IN (SELECT role_id FROM user_role WHERE user_id = #{id})
    </select>

</mapper>
<?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.loong.nba.player.dao.UserMapper">
    <select id="findById" resultType="UserPO">
        SELECT * FROM user WHERE id = #{id}
    </select>

    <select id="findByName" resultType="UserPO">
        SELECT * FROM user WHERE username = #{name}
    </select>

    <insert id="addUser" parameterType="UserPO">
        INSERT INTO user (username, user_password, salt)
        VALUES (#{userPO.username}, #{userPO.password}, #{userPO.salt})
    </insert>
</mapper>

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

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

相关文章

这才是CSDN最系统的网络安全学习路线(建议收藏)

01 什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防两面…

异步复位同步释放

(1条消息) 芯片设计全流程知识点总结_GGbao_的博客-CSDN博客 异步复位同步释放在always语句块里为&#xff1a; 当异步复位信号到来时&#xff0c;寄存器 reg_1 会被直接拉低&#xff0c;这是不会产生问题的&#xff0c;当rstn下降沿和clk上升沿同时到来时&#xff0c;如果rstn…

EduSoho 网校系统部署

目录 一、初始化环境二、安装 Nginx三、安装 MySQL四、安装 PHP五、上线 EduSoho六、验证FAQ 一、初始化环境 1、安装wget 如果系统已安装wget,请跳过此步骤 yum install wget2、关闭防火墙/Selinux systemctl stop firewalld.service systemctl disable firewalld.service…

Win32通用控件,加载进程(PE查看器)项目初步

在本专栏上一篇文章中带领大家学习了对话框的创建&#xff0c;并且在项目中创建出了对话框。在这一篇文章中&#xff0c;我将带领大家学习Win32通用控件&#xff0c;了解_WM_NOTIFY消息&#xff0c;并且带领大家初步写出课程中加载Windows所有进程的应用程序的雏形&#xff0c;…

因为懒,我用了“低代码”打下手

目录 一、前言 二、工具介绍 三、平台特点 四、如何使用JNPF&#xff1f; 五、低代码平台的选择参考 六、总结 一、前言 「一个优秀的开发者&#xff0c;一定是会利用各种工具来提升自己的开发效率。」 前段时间&#xff0c;体验了很多国内前沿的低代码平台&#xff0c;在…

Fiddler抓包工具之Fiddler界面主菜单功能介绍

Fiddler界面主菜单功能介绍 File菜单 File菜单中的命令主要支持完成通过Fiddler来启动和停止web流量的捕获&#xff08;capture&#xff09;,也可以加载或存储捕获的流量 &#xff08;1&#xff09;Capture Traffic&#xff1a;默认勾选&#xff0c;勾选此项才可抓包&#xff…

Linux ALSA音频工具

参考&#xff1a; ALSA 音频工具 amixer、aplay、arecord Linux Alsa ALSA的配置文件 音频录制——arecord 音频播放——aplay 音频配置——amixer alsamixer与amixer的区别 alsamixer是Linux音频框架ALSA工具之一&#xff0c;用于配置音频各个参数; alsamixer是基于文本图形…

APP中有html5页面的时候,怎么进行元素定位

测试app的时候&#xff0c;我们知道可以通过UI Automator Viewer进行元素定位 但是很多app中都会内嵌h5页面&#xff0c;这个时候定位就会变成下图这样&#xff1a; 第一步&#xff1a; 在手机中打开当前app的h5界面&#xff0c;使用usb连接电脑后&#xff0c; 第二步&#x…

聚焦金融行业网络安全|安全狗亮相知虎科技生态大会

5月18日&#xff0c;知虎科技生态大会在上海顺利举办。作为国内云原生安全领导厂商&#xff0c;安全狗也受邀出席此次活动。 据悉&#xff0c;此次大会邀请了来自国内顶级安全厂商的安全专家介绍从网络安全、应用安全、业务安全、数据安全、资金安全、通信安全等六个方面全方位…

抖音账号矩阵系统源码/技术开发搭建私有化部署开源

抖音SEO矩阵系统是基于抖音平台的搜索引擎优化技术的一种系统&#xff0c;其主要作用是通过一系列的技术手段&#xff0c;提高抖音视频的曝光和排名&#xff0c;使其获得更多的流量和粉丝。在本文中&#xff0c;我们将介绍抖音SEO矩阵系统的开发技术&#xff0c;包括系统设计、…

ElasticSearch集成SpringBoot实践及数据同步

一 前言 ES 全称 Elasticsearch 是一款分布式的全文搜索引擎&#xff0c;在互联网公司中&#xff0c;这款搜索引擎一直被程序员们所推崇。常见的使用场景如ELK日志分析&#xff0c;电商APP的商品推荐&#xff0c;社交APP的同城用户推荐等等。今天结合自己平时的一些学习对它与…

分布式事务解决方案Seata-1.6.0版本Docker安装

1. 简介 为什么要安装1.6.0 版本&#xff1f; 因为低版本Seata只支持单表的DDL语句&#xff0c;只有在在1.6.0版本及以上才支持 UPDATE JOIN。 注&#xff1a;1.6.0版本与 低版本的主要区别在于配置文件格式的修改&#xff0c;一站式解决方案&#xff1a;分布式事务解决方案Sea…

有哪些好用的App云测试平台?

一、国内外6种好用app云测平台推荐&#xff08;章节末附pk图&#xff09; 1、国内云测平台 1&#xff09;Testin云测 网址&#xff1a;https://www.testin.cn/ Testin云测平台是一款基于云端的移动应用测试平台&#xff0c;为移动应用开发者和测试人员提供一站式的移动应用质…

DNS风险分析及安全防护研究(一):DNS自身风险分析(中科三方)

作为互联网上的一项基础服务&#xff0c;DNS在网站运行中起到了至关重要的作用&#xff0c;然而其安全性在很长一段时间内都没有得到足够的重视。DNS采用不可靠的UDP协议&#xff0c;安全性具有较大的漏洞&#xff0c;攻击者很容易利用这些漏洞发动攻击&#xff0c;从而引起一些…

第三章 向量与线性方程组

引言 题型总结中推荐例题有蓝皮书的题型较为重要&#xff0c;只有吉米多维奇的题型次之。码字不易&#xff0c;如果这篇文章对您有帮助的话&#xff0c;希望您能点赞、评论、收藏&#xff0c;投币、转发、关注。您的鼓励就是我前进的动力&#xff01; 知识点思维导图 补充&…

CodeForces.1786A2.发牌.[中等][flg标识][数学规律][双色牌]

题目描述&#xff1a; 题目解读&#xff1a; 发牌问题&#xff0c;给两人发双色牌&#xff0c;同样还是 给a发1张&#xff0c;然后给b发2&#xff0c;3张&#xff1b; 给a发4&#xff0c;5张&#xff0c;给b发6&#xff0c;7张&#xff1b; 给a发8&#xff0c;9张&#xff…

软件测试基础知识整理(七)- 因果图法、正交法、场景法、错误推测法

目录 一、因果图法 1.1 因果图中的基本符号 1.2 操作步骤 二、正交法 2.1 正交表概念 2.2 举例说明 三、场景法 3.1 操作步骤 3.2 举例说明 四、错误推测法&#xff08;了解&#xff09; 一、因果图法 因果图法用于识别系统中可能存在的输入和输出的关系&#xff0c;…

《高性能MySQL》——创建高性能的索引(笔记)

文章目录 五、创建高性能的索引5.1 索引基础5.1.1 索引的类型B-Tree索引哈希索引空间数据索引(R-Tree)全文索引其他索引类别 5.2 索引的优点索引是最好的解决方案吗? 5.3 高性能的索引策略5.3.1 独立的列5.3.2 前缀索引和索引选择性5.3.3 多列索引5.3.4 选择合适的索引列顺序5…

了解和使用Docker

前提 本文对 Docker 进行全面阐述&#xff0c;详细介绍 Docker 的作用、其基本使用&#xff0c;如常用命令、Dockerfile 的作用及使用、Docker Compose 的作用及使用。常用的基本上都会涉及&#xff0c;其他可以在 Docker 官网进行查漏补缺。 下面还有投票&#xff0c;一起参…

CRM客户管理系统开发 获客管理营销全搞定

企业经营管理是有很大学问的&#xff0c;无论是生产经营、销售、服务还是客户管理、维护、营销都是需要付出一定的人力物力来管理的。传统的企业管理多是通过人工方式来完成&#xff0c;个中细节繁琐复杂&#xff0c;耗时耗力还很容易出现纰漏。所以随着科技的发展&#xff0c;…