spring security 认证授权详解

news2024/11/25 4:54:04

spring security简介

Spring Security 是 Spring家族中的一个安全管理框架,它提供了更丰富的功能做认证授权

  • 认证:当前用户有没有权限登录,是否为本系统用户
  • 授权:当前登录的用户有没有操作功能的权限

spring security的搭建

引入依赖

<dependencies>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.5.2</version>
        </dependency>

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.10.3</version>
        </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>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.75</version>
        </dependency>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

 创建数据库表

create table user
(
    password varchar(100) null,
    username varchar(50)  null,
    id       varchar(50)  not null
        primary key
)
    comment '用户表';

项目配置如下

server:
  port: 9000
spring:
  datasource:
    url: jdbc:mysql://xxxx:3306/security_db?useSSL=true&serverTimezone=Asia/Shanghai
    username: root
    password: root123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  redis:
    cluster:
      nodes:
        - 39.106.53.30:6379
        - 39.106.53.30:6380
        - 39.106.53.30:6381
        - 39.106.53.30:6382
        - 39.106.53.30:6383
        - 39.106.53.30:6384
mybatis-plus:
  mapper-locations:  classpath:/mapper/*.xml
  type-aliases-package: com.tech.security.securityservice.model

直接访问接口

没有经过认证的接口都会进入spring security的默认登录界面

spring security的原理

SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器

主要的过滤器如下

  • UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要有它负责。
  • ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException。
  • FilterSecurityInterceptor:负责权限校验的过滤器

 可以看到,在spring 容器中就有spring security的过滤器链,包括熟悉的过滤器UsernamePasswordAuthenticationFilter、FilterSecurityInterceptor等过滤器

spring security认证

认证流程

Authentication接口: 它的实现类,表示当前访问系统的用户,封装了用户相关信息。
AuthenticationManager接口:定义了认证Authentication的方法
UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法
UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回,然后将这些信息封装到Authentication对象中

UsernamePasswordAuthenticationFilter实现类:实现了我们最常用的基于用户名和密码的认证逻辑,封装Authentication对象
DaoAuthenticationProvider实现类:是AuthenticationManager中管理的其中一个Provider,因为是要访问数据库

定义jwt认证工具类

我这里采用的是java-jwt,可能和jj-jwt写法和解析方式有所不同

/**
 * @author sl
 */
@Slf4j
public class JwtTokenUtils {


    /**
     * 额外的数据,越复杂越安全
     */
    private static final String SING_VALUE = "dsdadfsghkjlsdfnmkjsd";

    /**
     * 过期时间
     */
    private static ReactiveRedisOperations<Object, Object> redisTemplate;


    public static String getJwtToken(String id){

        String JwtToken = JWT.create()
                .withClaim("userId",id)
                .withExpiresAt(DateUtil.offsetHour(new Date(),1))
                .sign(Algorithm.HMAC256(SING_VALUE));

        return JwtToken;
    }

    public static boolean CheckToken(String token){
        try {
            JWT.require(Algorithm.HMAC256(SING_VALUE)).build().verify(token);
            return true;
        } catch (Exception exception) {
            return false;
        }
    }

    public static String parseTokenInfo(String token){
        DecodedJWT verify = JWT.require(Algorithm.HMAC256(SING_VALUE)).build().verify(token);

        Claim claim = verify.getClaim("userId");

        return claim.asString();
    }
}

定义userDetails实现类

@Data
public class LoginUser implements UserDetails {

    private String id;

    private String username;

    private String password;

    private String token;

    private String authority;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {return null;}

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

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

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

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

 定义UserDetailsService实现类

目的是去数据库中查询用户信息,并返回UserDetails对象

/**
 * @author sl
 */
@Service
public class SecurityUserDetailServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("username",username);
        User user = userMapper.selectOne(userQueryWrapper);;

        if(Objects.isNull(user)){
            throw new RuntimeException("用户名或密码错误");
        }

        //TODO 根据用户查询权限信息 添加到LoginUser中

        LoginUser loginUser = new LoginUser();

        loginUser.setId(user.getId());
        loginUser.setUsername(user.getUsername());
        loginUser.setPassword(new BCryptPasswordEncoder().encode(user.getPassword()));
        loginUser.setAuthority("");
        return loginUser;
    }
}

登录以及退出登录

通过认证器进入登录认证流程,退出认证直接删除redis缓存即可

@Service
public class UserServiceImpl implements UserService {


    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 认证器
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public LoginUser login(LoginUser user) {

        // 使用security认证
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
        Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        LoginUser principal = (LoginUser) authenticate.getPrincipal();
        if (authenticate.isAuthenticated()) {
            String jwtToken = JwtTokenUtils.getJwtToken(principal.getId());
            redisTemplate.opsForValue().set("login_"+principal.getId(), JSON.toJSONString(principal) ,1,TimeUnit.DAYS);
            principal.setToken(jwtToken);
        }

        return principal;
    }

    @Override
    public void logout() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        redisTemplate.delete("login:"+loginUser.getId());
    }

}

配置自定义认证过滤器进行jwt认证

主要是解析请求头中包含的token信息,并存入SecurityContextHolder上下文中

/**
 * @author Administrator
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取token
        String token = request.getHeader("token");
        String loginUserString = null;
        try {
            if (!StringUtils.hasText(token)) {
                //放行
                filterChain.doFilter(request, response);
                return;
            }

            if(token !=null){
                String userId= JwtTokenUtils.parseTokenInfo(token);

                loginUserString= redisTemplate.opsForValue().get("login_" + userId);
            }
        }catch (Exception e) {
            throw new RuntimeException("用户未登录");
        }

        LoginUser loginUser = JSON.parseObject(loginUserString, LoginUser.class);
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        filterChain.doFilter(request, response);
    }
}

 将jwt认证过滤器加入过滤器链中

/**
 * @author sl
 */
@EnableWebSecurity
@Configuration
public class SecurityConfig {

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }


    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf().disable();
        httpSecurity.authorizeRequests()
                .antMatchers("/user/login").anonymous()
                .anyRequest().authenticated()
                .and()
                .formLogin();
        // 在UsernamePasswordAuthenticationFilter之前添加自定义token前置过滤器
        httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        return httpSecurity.build();
    }

}

测试认证

直接访问登录接口,传递用户名密码进入认证流程, 返回token

携带token访问,认证成功

spring security授权 

授权的概念:当前登录的用户有没有操作功能的权限

授权的模型:rbac(Role-Based Access Control)模型

授权方式:注解,配置

rbac授权模型

基于角色的权限控制。这是目前最常被开发者使用也是相对易用、通用权限模型

数据库脚本 

共5张表结构,用户表,角色表,权限表,用户角色表,用户权限表

create table perm
(
    id          varchar(60)                not null
        primary key,
    func_name   varchar(64) default 'NULL' not null comment '功能名称',
    path        varchar(200)               null comment '路由地址',
    status      char        default '0'    null comment '功能状态(0正常 1停用)',
    perms       varchar(100)               null comment '权限标识',
    create_time datetime                   null,
    update_time datetime                   null,
    del_flag    int         default 0      null comment '是否删除(0未删除 1已删除)',
    remark      varchar(500)               null comment '备注'
)
    comment '权限表';

INSERT INTO perm (id, func_name, path, status, perms, create_time, update_time, del_flag, remark) VALUES ('dfasdfgfdg', '审批功能', '/audit', '0', 'audit', '2023-10-11 15:20:16', '2023-10-11 15:20:18', 0, '审批功能');
INSERT INTO perm (id, func_name, path, status, perms, create_time, update_time, del_flag, remark) VALUES ('fdsfdsfhgfsjy', '删除功能', '/delete', '0', 'delete', '2023-10-11 11:26:58', '2023-10-11 11:27:02', 0, '删除权限');


create table user
(
    id       varchar(50)  not null
        primary key,
    username varchar(50)  null,
    password varchar(100) null
)
    comment '用户表';

INSERT INTO user (id, username, password) VALUES ('qwertreyt', 'ceshi', 'ceshi');
INSERT INTO user (id, username, password) VALUES ('sdafasf214', 'admin', 'admin');

create table role
(
    id          varchar(60)      not null
        primary key,
    name        varchar(128)     null,
    role_key    varchar(100)     null comment '角色权限字符串',
    status      char default '0' null comment '角色状态(0正常 1停用)',
    del_flag    int  default 0   null comment 'del_flag',
    create_time datetime         null,
    update_time datetime         null,
    remark      varchar(500)     null comment '备注'
)
    comment '角色表';
	
INSERT INTO role (id, name, role_key, status, del_flag, create_time, update_time, remark) VALUES ('qwerqwerer', '管理员', 'amdin', '0', 0, '2023-10-11 11:22:00', '2023-10-11 11:22:09', '管理员角色');
INSERT INTO role (id, name, role_key, status, del_flag, create_time, update_time, remark) VALUES ('uiplpptry', '普通用户', 'ceshi1', '0', 0, '2023-10-11 15:31:39', '2023-10-11 15:31:42', '普通用户');

create table role_perm
(
    role_id varchar(60) not null comment '角色ID',
    perm_id varchar(60) not null comment '菜单id',
    primary key (role_id, perm_id)
);

INSERT INTO role_perm (role_id, perm_id) VALUES ('qwerqwerer', 'dfasdfgfdg');
INSERT INTO role_perm (role_id, perm_id) VALUES ('qwerqwerer', 'fdsfdsfhgfsjy');
INSERT INTO role_perm (role_id, perm_id) VALUES ('uiplpptry', 'fdsfdsfhgfsjy');

create table user_role
(
    user_id varchar(60) not null comment '用户id',
    role_id varchar(60) not null comment '角色id',
    primary key (user_id, role_id)
);

INSERT INTO user_role (user_id, role_id) VALUES ('sdafasf214', 'qwerqwerer');
INSERT INTO user_role (user_id, role_id) VALUES ('qwertreyt', 'uiplpptry');

开启权限配置

@EnableGlobalMethodSecurity(prePostEnabled = true)

/**
 * @author sl
 */
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class SecurityConfig {

    ....

}

建立mapper通过用户id查询权限

<?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.tech.security.securityservice.mapper.PermMapper">
    <select id="findPermByUserId" resultType="perm">
        select p.*
        from
            user u left join user_role ur
                             on ur.user_id = u.id
                   left join  role_perm rm
                              on ur.role_id = rm.role_id
                   left join perm p
                             on rm.perm_id = p.id
        where user_id  = #{userId}
    </select>
</mapper>

将用户权限封装到userDetails对象中 

LoginUser加入权限

package com.tech.security.securityservice.model;

import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

@Data
public class LoginUser implements UserDetails {

    private String id;

    private String username;

    private String password;

    private String token;

    /**
     *存储权限信息
     */
    private List<String> permissions;

    /**
     *存储SpringSecurity所需要的权限信息的集合
     */
    @JSONField(serialize = false)
    private List<SimpleGrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if(authorities!=null){
            return authorities;
        }
        //把permissions中字符串类型的权限信息转换成GrantedAuthority对象存入authorities中
        authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
        return authorities;
    }

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

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

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

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

封装权限到userDetails对象中 

/**
 * @author sl
 */
@Service
public class SecurityUserDetailServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private PermMapper permMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("username",username);
        User user = userMapper.selectOne(userQueryWrapper);;

        if(Objects.isNull(user)){
            throw new RuntimeException("用户名或密码错误");
        }

        LoginUser loginUser = new LoginUser();
        loginUser.setId(user.getId());
        loginUser.setUsername(user.getUsername());
        loginUser.setPassword(new BCryptPasswordEncoder().encode(user.getPassword()));
        //根据用户查询权限信息 添加到LoginUser中
        List<Perm> perms= permMapper.findPermByUserId(user.getId());
        List<String> permsList = perms.stream().map(s -> s.getPerms()).collect(Collectors.toList());
        loginUser.setPermissions(permsList);
        return loginUser;
    }
}

别忘记在自定义认证器中加入权限进入上下文中

UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);

编写controller加权限注解测试

hasAuthority代表拥有该权限可以访问,还有hasRole等,默认会拼接上ROLE_,在这里不做过多介绍,@PreAuthorize("@myAccess.hasAuthority('audit')"),myAccess为自定义权限校验

/**
 * @author sl
 */
@RestController
public class TestSecurityAccessController {

    /**
     * 测试数据访问接口
     * @return
     */
    @GetMapping("/access")
    @PreAuthorize("hasAuthority('access')")
    public String access(){
        return "access success!!";
    }

    /**
     *  @PreAuthorize("@myAccess.hasAuthority('audit')")
     *  使用自定义权限校验
     * @return
     */
    @GetMapping("/audit")
    @PreAuthorize("@myAccess.hasAuthority('audit')")
    public String audit(){
        return "audit access!!";
    }

    /**
     * 使用spring security权限校验
     * @return
     */
    @GetMapping("/delete")
    @PreAuthorize("hasAuthority('delete')")
    public String delete(){
        return "delete access!!";
    }
}

基于配置的权限配置

        httpSecurity.authorizeRequests()
                .antMatchers("/user/login").anonymous()
                // 基于配置的权限处理
                .antMatchers("/audit").hasAuthority("audit")
                .anyRequest().authenticated()
                .and()
                .formLogin();

自定义权限校验

在controller中@PreAuthorize("@myAccess.hasAuthority('audit')") 就使用了自定义的权限校验

/**
 * @author sl
 * 自定义权限校验
 */
@Component("myAccess")
public class MyAccessHandler {

    public boolean hasAuthority(String authority){
        //获取当前用户的权限
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        List<String> permissions = loginUser.getPermissions();
        //判断用户权限集合中是否存在authority
        return permissions.contains(authority);
    }
}

启动项目开始权限测试 

登录测试账户

当前登录的账户ceshi登录成功,当前测试账户具有删除权限

访问无权限的审计功能

当前访问为403,无授权的http状态,无法访问

访问删除功能 

访问成功,代表用户具有的删除权限可以访问,授权成功

认证授权优化问题 

统一返回结果,自定义异常处理器

当前的认证授权,如果失败了返回的结果不是很友好,我们希望在认证失败或者是授权失败的情况下也能和我们的接口一样返回相同结构的json,方便统一结构

spring security的异常处理

  • 如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。
  • ​ 如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。
  • ​所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置给SpringSecurity即可。

工具类WebUtils

public class WebUtils {
    /**
     * 将字符串渲染到客户端
     *
     * @param response 渲染对象
     * @param string   待渲染的字符串
     */
    public static void renderString(HttpServletResponse response, String string) {
        try {
            response.setStatus(200);
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            response.getWriter().print(string);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

认证失败处理器

/**
 * @author sl
 * 认证失败返回处理
 */
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {

        Result result = new Result(HttpStatus.UNAUTHORIZED.value(), "用户认证失败,请重新登录");
        String json = JSON.toJSONString(result);
        // 处理异常,调用工具类
        WebUtils.renderString(response, json);
    }
    
}

授权失败处理器

/**
 * @author sl
 * 权限校验失败处理
 */
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        Result result = new Result(HttpStatus.FORBIDDEN.value(), "权限不足");
        String json = JSON.toJSONString(result);
        WebUtils.renderString(response,json);
    }
}

将授权以及认证处理器配置给SpringSecurity

/**
 * @author sl
 */
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class SecurityConfig {

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

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


    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf().disable();
        httpSecurity.authorizeRequests()
                .antMatchers("/user/login").anonymous()
                // 基于配置的权限处理
//                .antMatchers("/audit").hasAuthority("audit")
                .anyRequest().authenticated()
                .and()
                .formLogin();
        // 在UsernamePasswordAuthenticationFilter之前添加自定义token前置过滤器
        httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 自定义认证失败和权限处理失败处理器
        httpSecurity.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler);
        // 允许跨越
        httpSecurity.cors();
        return httpSecurity.build();
    }

}

测试认证失败返回和授权失败返回 

未登录显示认证失败

登录之后返回权限不足 

项目gitee地址 

https://gitee.com/watcherman/security-service.git

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

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

相关文章

SqlServer安装教程

百度网盘地址: 链接&#xff1a;https://pan.baidu.com/s/1ntqoK9uVc6fBVTm7twh8kw 提取码&#xff1a;grdt 安装: 双击:SQLEXPRADV_x64_CHS.exe ,等待;点击计划,系统配置检查器,根据要求修改(我被要求重启了)点击安装,全新SQL Server独立安装或向现有安装添加功能,接受功能选…

颜色特征和sift算法结合的指示类交通标志识别

目录 摘 要...................................................................................... 3 第一章 绪论........................................................................ 6 1.1 研究课题背景...................................................…

fatal error C1083: 无法打开包括文件: “ta_libc.h”: No such file or directory

用python做交易数据分析时&#xff0c;可以用talib库计算各类指标&#xff0c;这个库通过以下命令安装&#xff1a; pip install TA-Lib -i https://pypi.tuna.tsinghua.edu.cn/simple windows安装时可能出现本文标题所示的错误&#xff0c;可按如下步骤解决&#xff1a; 1、去…

【Docker 内核详解】namespace 资源隔离(四):Mount namespace Network namespace

namespace 资源隔离&#xff08;四&#xff09;&#xff1a;Mount namespace & Network namespace 1.Mount namespace mount namespace 通过隔离文件系统挂载点对隔离文件系统提供支持&#xff0c;它是历史上第一个 Linux namespace&#xff0c;所以标识位比较特殊&#x…

云开发校园宿舍/企业/部门/物业故障报修小程序源码

微信小程序云开发校园宿舍企业单位部门物业报修小程序源码&#xff0c;这是一款云开发校园宿舍报修助手工具系统微信小程序源码&#xff0c;适用于学校机房、公司设备、物业管理以及其他团队后勤部&#xff0c;系统为简单云开发&#xff0c;不需要服务器域名即可部署&#xff0…

[HNCTF 2022 WEEK2]ez_ssrf题目解析

这题主要是引入ssrf这个漏洞攻击&#xff0c;本质上没有更深入的考察 本题是需要我们去伪造一个ssrf的请求头去绕过 题目开始给了我们信息让我们去访问index.php fsockopen函数触发ssrf fsockopen() 函数建立与指定主机和端口的 socket 连接。然后&#xff0c;它将传入的 bas…

nginx的location的优先级和匹配方式

nginx的location的优先级和匹配方式 在http模块中有server&#xff0c;server模块中有location&#xff0c;location匹配的是uri 在一个server中&#xff0c;会有多个location&#xff0c;如何来确定匹配哪个location niginx的正则表达式 ^ 字符串的起始位置 $ 字符串的…

Mybatis用Byte[]存图片,前端显示图片

前端页面 static下 也就是说byte[] 转成JSON字符串后,和用BASE64编码后是一摸一样的,那么SpringBoot会自动将实体类转JSON字符串,也就是说根本不需要Base64编码 注意:两个值并非一摸一样,一个多了个双引号 byte[]的值前后有个双引号 有一点点区别 一个有双引号,一个没有…

7.定时器

定时器资源 CC2530有四个定时器TIM1~TIM4和休眠定时器 TIM1 定时器1 是一个独立的16 位定时器&#xff0c;支持典型的定时/计数功能&#xff0c;比如输入捕获&#xff0c;输出比较和PWM 功能。定时器有五个独立的捕获/比较通道。每个通道定时器使用一个I/O 引脚。定时器用于…

NewStarCTF2023week2-Unserialize?

代码审计&#xff1a; 定义了一个eval类&#xff0c;该类下有一个私有变量cmd和公有成员函数destruct()&#xff0c;该函数在对象的所有引用都被删除或类被销毁时会自动调用&#xff1b; 调用该函数则会执行一个正则表达式进行正则匹配&#xff0c;过滤掉了一些常用命令和bas…

机器人技术研究现状

随着科技的不断进步&#xff0c;机器人技术在制造业中的应用越来越广泛。本文将综述机器人技术的研究现状&#xff0c;主要包括机器人控制技术、机器人感知技术、机器人智能化技术、柔性机器人技术、协作机器人技术以及云机器人技术六个方面。 一、机器人控制技术 机器人控制技…

2023年中国云计算软件市场规模、市场结构及市场份额情况分析[图]

云计算是分布式计算的一种&#xff0c;指的是通过网络“云”将巨大的数据计算处理程序分解成无数个小程序&#xff0c;然后&#xff0c;通过多部服务器组成的系统进行处理和分析这些小程序得到结果并返回给用户。云计算软件类型分为三类&#xff0c;即基础设施即服务、平台即服…

调试工具:应用程序验证器Application Verifier(配合WinDbg)

1.应用程序验证器&#xff08;Application Verifier&#xff09;简介 说明&#xff1a;Application Verifier是来自微软官方的一款应用程序验证工具&#xff0c;主要用于帮助用户检测和调试内存损坏、危险的安全漏洞、Run-time检测等&#xff1b;是一款辅助开发工具&#xff0…

NIO教程

一&#xff0c;概述 原本的java是基于同步阻塞式的i/o通信&#xff08;bio) 性能低下&#xff0c;所以出现了nio这种非阻塞式的 二&#xff0c;Java 的I/O演进之路 2.1 i/o模型基本说明 i/o模型&#xff1a;就是用什么样的通道或者说通信模式和架构进行数据的传输和接收&am…

GPT实战系列-ChatGLM2部署Ubuntu+Cuda11+显存24G实战方案

GPT实战系列-ChatGLM2部署UbuntuCuda11显存24G实战方案 自从chatGPT掀起的AI大模型热潮以来&#xff0c;国内大模型研究和开源活动&#xff0c;进展也如火如荼。模型越来越大&#xff0c;如何在小显存部署和使用大模型&#xff1f; 本实战专栏将评估一系列的开源模型&#xf…

MSVC编译dcmtk库

官网 https://www.dcmtk.org/en/dcmtk/ 下载源码和支持包 支持包在support文件夹下,选择适合你的MSVC版本 到官网下载cmake,官网cmake.org 解压源码 支持库 打开cmake-gui,填写源码目录(dcmtk解压的源码目录)和编译目录(自定义的目录) 点下面的configure,弹出选…

Maika 与越南童模们受邀请参加中国上海时装周 hanakimi 品牌开幕

金风送爽&#xff0c;秋高气和。2024中国上海时装周以“活力互链”为主题&#xff0c;于10月8日正式启幕。 魅力四射的越南童模身着著名时尚品牌MLB、Hana Kami、Jacadi的精美设计&#xff0c;迈着有力、专业但又不失优雅的步伐走上时尚舞台上海大型现场。无论是拍摄造型照还是…

机器学习的原理是什么?

训过小狗没? 没训过的话总见过吧? 你要能理解怎么训狗&#xff0c;就能非常轻易的理解机器学习的原理. 比如你想教小狗学习动作“坐下”一开始小狗根本不知道你在说什么。但是如果你每次都说坐下”然后帮助它坐下&#xff0c;并给它一块小零食作为奖励&#xff0c;经过多次…

2020-2021 ACM-ICPC, Asia Nanjing Regional Contest (XXI Open Cup, Grand Prix

Problem - K - Codeforces 首先第一个位置放1&#xff0c;第二个位置放2&#xff0c;...第n个位置放n 任意两个相邻的数都是互质的&#xff0c;我们只要交换相邻的两个数就可以产生两个数满足gcd&#xff08;pi&#xff0c;i&#xff09;1 其中第一个位置为1比较特殊&#x…

Ae 效果:CC Blobbylize

扭曲/CC Blobbylize Distort/CC Blobbylize CC Blobbylize&#xff08;CC 团化&#xff09;与 CC Glass 效果非常相似&#xff0c;可将源图像扭曲变形成一些不可名状的团块&#xff0c;从而创建液态金属等效果。 CC Blobbylize 效果使用源图像以及 Blob layer 图层的纹理&#…