SpringBoot-Shiro安全权限框架

news2024/12/26 22:18:13

Apache Shiro是一个强大而灵活的开源安全框架,它干净利落地处理身份认证,授权,企业会话管理和加密。
官网:

http://shiro.apache.org/

源码:

https://github.com/apache/shiro
在这里插入图片描述

Subject:代表当前用户或者当前程序,在Shiro中Subject是一个接口,他定义了很多认证授权的方法。
认证就是判断你这个用户是不是合法用户,授权其实就是你认证成功之后,你的权限能访问系统的那些资源。

SecurityManage:安全管理器,Subject去认证的时候,需要通过SecurityManage安全管理器来负责认证和授权
安全管理器又要通过Authenticator认证器进行认证,通过Authorizer授权器进行授权,通过SessionManag会话管理器进行会话管理,有没有发现他就相当于一个中介,他来接收这些事情,而干这些事情的不是他来做的。

Authenticator:认证器,Realm从数据库中去获取到用户信息,然后认证器来做身份认证来进行身份认证。
Authorizer:授权器,通过认证器认证权限之后,得通过授权器来判断这个用户身份有什么权限,他可以访问那些资源

Realm:相当于数据源,从Realm中获取到用户的数据,比如用户的数据在MYSQL数据库,那么Realm就需要从MYSQL数据库中去获取到用户的信息,然后来做身份认证。
在Realm中也有一些认证授权相关的操作。

SessionManager:会话管理器,不依赖web容器的session,所以shiro可以使用在非web 应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。

SessionDAO:会话,比如要将Session存储到数据库,那么可以通过jdbc来存储到数据库。
在这里插入图片描述

一、引入依赖


<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-core</artifactId>
  <version>1.5.3</version>
</dependency>

二、shiro配置文件shiro.ini(用户名或者密码)

[users]
relaysec=123456

三、测试代码

public class ShiroDemo{
	
	public static void main(String[] args){
		
		//1.创建安全管理器对象
		DefaultSecurityManager securityManager = new DefaultSecurityManager();

		//2.给安全管理器设置realm
		securityManager.setRealm(new IniRealm("classpath:shiro.ini"));

		//3.SecurityUtils给全局安全工具类设置安全管理器
		SecurityUtils.setSecurityManager(securityManager);

		//4.关键对象subject主体
		Subject subject = SecurityUtils.getSubject();

		//5.创建令牌
		UsernamePasswordToken token = new UsernamePasswordToken("relaysec","123456");
	
		try{
			subject.login(token);//用户认证
            System.out.println("登录成功");
		}catch(UnknownAccountException e){
			e.printStackTrace();
            System.out.println("认证失败: 用户名不存在~");
		}catch(IncorrectCredentialsException e){
			e.printStackTrace();
            System.out.println("认证失败: 密码错误~");
		}
	}
}

SpringBoot整合shrio

一、创建一个war项目

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-juli</artifactId>
            <version>8.5.23</version>
        </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>

        <!--引入JSP解析依赖-->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

        <!--引入shiro整合Springboot依赖
			shiro-spring-boot-web-starter
		-->
        <!--CVE-2020-1957 Shiro <= 1.5.1-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.4.2</version>
        </dependency>
<!--        <dependency>-->
<!--            <groupId>org.apache.shiro</groupId>-->
<!--            <artifactId>shiro-spring</artifactId>-->
<!--            <version>1.4.2</version>-->
<!--        </dependency>-->
        <!--CVE-2020-11989 shiro < 1.5.3-->
<!--        <dependency>-->
<!--            <groupId>org.apache.shiro</groupId>-->
<!--            <artifactId>shiro-web</artifactId>-->
<!--            <version>1.4.2</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>org.apache.shiro</groupId>-->
<!--            <artifactId>shiro-spring</artifactId>-->
<!--            <version>1.4.2</version>-->
<!--        </dependency>-->

<!--        <dependency>-->
<!--            <groupId>org.apache.shiro</groupId>-->
<!--            <artifactId>shiro-spring</artifactId>-->
<!--            <version>1.5.3</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>org.apache.shiro</groupId>-->
<!--            <artifactId>shiro-web</artifactId>-->
<!--            <version>1.5.3</version>-->
<!--        </dependency>-->

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.7.0</version>
        </dependency>

二、创建ShiroConfig(@Configuration修饰)

配置3个bean,ShiroFilterFactory、DefaultWebSecurityManager、Realm

@Configuration
public class ShiroConfig implements EnvironmentAware{

	private Environment env;
}

①、创建ShiroFilter

ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//给filter设置安全管理器
shiroFilterFactoryBean setSecrityManager(defaultWebSecurityManager);

//配置系统受限资源和系统公共资源
/**
	map的key值代表的是我们的资源,map的value值代表的是我们的权限
	authc代表我们是需要认证和授权的,anon代表我们不需要认证和授权
	其实代码审计去审的就是shiroConfig文件,看他的jar包,以及ShiroConfig配置文件
*/
Map<String,String> map = new HashMap<>();
//authc 请求这个资源需要认证和授权
map.put("/admin/**","anon");
map.put("/admin/users","authc");
map.put("/demo/**","anon");
map.put("/index.jsp","authc");
map.put("/hello/*", "authc");
map.put("/toJsonList/*","authc");

shiroFilterFactoryBean.setLoginUrl("/login.jsp");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;

还有一种方式:

@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager sessionManager) {
//构建ShiroFilterFactoryBean对象,负责创建过滤器工厂
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置登录路径
    shiroFilterFactoryBean.setLoginUrl("/login");    
//注意:必须设置SecuritManager
shiroFilterFactoryBean.setSecurityManager(sessionManager);
//设置访问未授权的需要跳转到的路径
    shiroFilterFactoryBean.setUnauthorizedUrl("/403");
//设置登录成功访问路径
    shiroFilterFactoryBean.setSuccessUrl("/");
//自定义的过滤设置注入到shiroFilter中
    shiroFilterFactoryBean.getFilters().put("apikey", new ApiKeyFilter());
    shiroFilterFactoryBean.getFilters().put("csrf", new CsrfFilter());
    shiroFilterFactoryBean.getFilters().put("user", new UserAuthcFilter());
//定义map指定请求过滤规则
    Map<String, String> filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();
    ShiroUtils.loadBaseFilterChain(filterChainDefinitionMap);
    ShiroUtils.ignoreCsrfFilter(filterChainDefinitionMap);
    filterChainDefinitionMap.put("/**", "apikey, csrf, authc");
    return shiroFilterFactoryBean;
}

Shiro有两种方式可进行精度控制,一种是过滤器方式,根据访问的URL进行控制,该种方式允许使用*匹配URL,可以进行粗粒度控制;另一种是注解的方式,实现细粒度控制,但只能是在方法上控制,无法控制类级别访问。
过滤器的类型有很多,本文代码只用到anon和authc两种类型

定义一个Map类型的filterChainDefinitionMap,使用ShiroFilterChainDefinition来控制请求路径的鉴权与授权。

创建ShiroUtils类,自定义静态方法loadBaseFilterChain()和ignoreCsrfFilter()方法,判断哪些请求路径需要用户登录才能访问,哪些不需要登录就能访问,实现粗粒度控制。

②、创建安全管理器

将用户认证信息源设置到安全管理器

@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
	
	DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
	defaultWebSecurityManager.setRealm(realm);
	return defaultWebSecurityManager;
}

还有一种方式:
管理内部组件实例,并通过它来提供安全管理的各种服务。
modularRealmAuthenticator是shiro提供的realm管理器,用来设置realm生效, 通过setAuthenticationStrategy来设置多个realm存在时的生效规则

@Bean(name="securityManager")
public DefaultWebSecurityManager securityManager(SessionManager sessionManager, MemoryConstrainedCacheManager memoryConstrainedCacheManager){
	
	DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
    dwsm.setSessionManager(sessionManager);
    dwsm.setCacheManager(memoryConstrainedCacheManager);
    dwsm.setAuthenticator(modularRealmAuthenticator());
    return dwsm;
}

重写ModularRealmAuthenticator,只要有一个Realm验证成功即可,只返回第一个Realm身份验证成功的认证信息

@Bean
public ModularRealmAuthenticator modularRealmAuthenticator() {
    UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator();
    modularRealmAuthenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
    return modularRealmAuthenticator;
}

securityManager不用直接注入Realm可能导致事务失效
可以定义一个handleContextRefresh方法,利用监听去初始化,等到ApplicationContext加载完成之后,完成shiroReaml

@EventListener
public void handleContextRefresh(ContextRefreshedEvent event){
	
	ApplicationContext context = event.getApplicationContext();
	List<Realm> realmList = new ArrayList<>();
	
	LocalRealm localRealm = context.getBean(LocalRealm.class);
	LdapRealm ldapRealm = context.getBean(LdapRealm.class);
	
	realmList.add(LocalRealm);
	realmList.add(ldapRealm);
	context.getBean(DefaultWebSecurityManager.class).setRealms(realmList);
}

③、自定义Realm

@Bean
public Realm getRealm(){
	
	CustomerRealm customerRealm = new CustomerRealm();
	return customerRealm;
}

/**
	自定义Realm一般继承AuthorizingRealm,然后实现getAuthenticationInfo()和getAuthorizationInfo()方法,来完成身份认证和权限获取。
*/
public class CustomerRealm extends AuthorizingRealm{
	
	/**
		用于授权
		PrincipalCollection 是一个身份集合
		首先通过getPrimaryPrincipal()得到传入的用户名,然后调用getAuthorizationInfo()方法。
		再根据用户名调用 UserService接口获取角色及权限信息,并将得到的用户roles放到authorizationInfo中,并返回。
	*/
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals){
		String userId = (String) principals.getPrimaryPrincipal();
		return getAuthorizationInfo(userId,usserService);
	}
	public static AuthorizationInfo getAuthorizationInfo(String userId,UserService userService){
		
		SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
		UserDTO userDTO = userService.getUserDTO(userId);
		Set<String> roles = userDTO.getRoles().stream().map(Role::getId).collect(Collectors.toSet());
		authorizationInfo.setRoles(roles);
		return authorizationInfo();
	}

	//用于验证账户和密码
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		
		/**
			//直接从参数中获取用户名和密码
			String username = token.getUsername();
			String password = String.valueOf(token.getPassword())
		*/
		 System.out.println("=============");
		
		//从传过来的token获取用户名
		String principal = (String) token.getPrincipal();
		System.out.println("用户名"+principal);

		//假设从数据库中获得用户名,密码
		String password_db="123";
        String username_db="zhangsan";
        if (username_db.equals(principal)){
//            SimpleAuthenticationInfo simpleAuthenticationInfo =
            return new SimpleAuthenticationInfo(principal,"123", this.getName());
        }

		return null;
	}
}

还有方式:
展示一个LdapReam Bean,注解@DependsOn表示组件依赖,下图中表示依赖lifecycleBeanPostProcessor
LifecycleBeanPostProcessor用来管理shiro Bean的生命周期,在LdapReam创建之前先创建lifecycleBeanPostProcessor

@Bean
@DependsOn(lifecycleBeanPostProcessor)
public LdapRealm ldapRealm(){
	
	return new LdapRealm();
}

@Bean(name="lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
	return new LifecycleBeanPostProcessor();
}

三、Controller进行访问,登录成功之后转发到index.jsp,否则直接转发到login.jsp文件。

@RequestMapping("login")
public String login(String username,String password){

	Subject subject = Security.getSubject();
	try{
		//认证成功
		UsernamePasswordToken token = new UsernamePasswordToken(uername,password);
		subject.login(token);
		return "redirect:/index.jsp";
	}catch(UnknownAccountException e){
		e.printStackTrace();
		System.out.println("用户名错误");
	}catch(IncorrectCredentialsException e){
		e.printStackTrace();
		System.out.println("密码错误");
	}catch(Exception e){
		e.printStackTrace();
		System.out.println(e.getMessage());
	}

	return "redirect:/login.jsp";
}

漏洞复现

Shiro层面绕过之后,SpringBoot也需要解析路径的,所以如果Springboot版本过高的话,可能是复现不成功的。并且不能使用Springboot集成的shiro吗,那样子也有可能导致复现不成功

<groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>1.5.0</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.5.0</version>
</dependency>

ShiroConfig配置


LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
        map.put("/login","anon");//anon 设置为公共资源  放行资源放在下面
//        map.put("/user/register","anon");//anon 设置为公共资源  放行资源放在下面
//        map.put("/register.jsp","anon");//anon 设置为公共资源  放行资源放在下面
//        map.put("/user/getImage","anon");
        map.put("/doLogin", "anon");
        map.put("/demo/**","anon");
        map.put("/unauth", "user");
        map.put("/admin/*","authc");

        //默认认证界面路径---当认证不通过时跳转
        shiroFilterFactoryBean.setLoginUrl("/login.jsp");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        return shiroFilterFactoryBean;

Controller:

绕过方式: /demo/…;/admin/index
在这里插入图片描述
在这里插入图片描述

漏洞分析:
定位到PathMatchingFilterChainResolver类的getchain方法,这个方法是处理过滤的
首先调用getPathWithinApplication方法获取路径,跟进去。
来到getPathWithinApplication方法,继续跟进WebUtils的getPathWithinApplication方法
首先getContextPath方法获取工程路径,调用getRequestUri获取访问路径,跟进去getRequestUri方法
来到getRequestUri方法,首先从域中获取,获取不到的话,调用getRequestURI方法获取路径,获取的就是我们访问的//demo/…;/admin/users 这个路径,然后调用decodeAndCleanUriString方法进行处理。

来到decodeAndCleanUriString方法,通过indexOf方法,因为我们的路径中存在分号,所以他获取到的位置是第9个,

然后判断如果不等于-1的话,调用substring方法进行字符串截取,从0到9 包前不包后 ,也就是说分号不需要截取,截取出来的字符串就是//demo/…。然后返回上一个方法。
来到normalize方法,这里进行了字符的替换,

替换反斜线

替换 // 为 /

替换 /./ 为 /

替换 /…/ 为 /

然后返回。

回到getChain方法,首先判断如果url不等于null并且他的最后一位是 / 的话,进行字符串截取然后赋值,我们拿到的字符串路径是/demo/… 所以往下走。

然后循环遍历我们的map中的内容,就是我们在Shiroconfig中写的那些过滤的内容,然后进行一一匹配,最后匹配到/demo/**的时候,然后调用proxy方法,我们跟进去。

来到proxy方法,首先调用getChain方法获取到请求路径对应的过滤器,然后调用过滤器的proxy方法,来到proxy方法
来到proxy方法,首先创建了一个ProxiedFilterChain对象,这个对象是一个代理对象。

基本上到这里我们的原始请求就会进入到 springboot中. springboot对于每一个进入的request请求也会有自己的处理方式,找到自己所对应的controller。

我们定位到Spring处理请求的地方。我们跟进去getPathWithinApplication方法
到getPathWithinApplication方法,调用getContextPath方法获取到工程路径,调用getRequestUri获取访问路径,我们跟进getRequestUri方法
来到getRequestUri方法,首先从域中获取,获取不到的话然后通过getRequestURI方法获取到url,然后调用decodeAndCleanUriString方法,我们跟进去。
来到decodeAndCleanUriString方法,跟进removeSemicolonContent方法。
首先获取到分号的位置,然后while循环如果不等于-1的话,然后进行字符串截取,将我们的分号截取掉 然后返回的路径就是//demo…

回到decodeAndCleanUriString方法,调用decodeRequestString进行decode解码,然后调用getSanitizedPath方法进行过滤 //

然后返回。
回到getPathWithinApplication方法,可以发现我们的分号已经被去掉了。
到这里基本上的流程就结束了,可以发现在Spring中会过滤分号,而在Shiro中不会。导致权限绕过。

===================================================

应用案例登录认证

在这里插入图片描述

  1. 客户端提交用户账号和密码,在Controller中拿到账号和密码封装到token对象.
  2. 然后借助subject的login方法,把数据提交给SecurityManager
  3. 使用Authenticator处理token,Authenticator从Realm列表中获取LdapRealm
  4. LdapRealm从token中获取数据,交给authenticate进行比对,对比通过返回AuthenticationInfo

一、创建maven工程,并导入相关依赖

shiro-core commons-logging

<dependency>
  <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.2.5</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.2.5</version>
</dependency>
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>1.2.1</version>
</dependency>

 <dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-quartz</artifactId>
 </dependency>

登录控制器Controller

@PostMapping(value="/signin")
public ResultHolder login(@RequestBody LoginRequest request){
	
	SessionUser sessionUser = SessionUtils.getUser();
	if(sessionUser!=null){
		if(!StringUtils.equals(sessionUser.getId(), request.getUsername())){

			return ResultHolder.error(Translator.get("please_logout_current_user"));
		}
		SecurityUtils.getSubject().getSession().setAttribute("authenticate", UserSource.LOCAL.name());
	}
	 return userService.login(request);
}

在login方法中,把用户名和密码封装为UsernamePasswordToken对象token,然后通过SecurityUtils.getSubject()获取Subject对象,并将前面获取token对象作为参数。若调用subject.login(token)时不抛出任何异常,说明认证通过,调用subject.isAuthenticated()返回true表示当前的用户已经登录。后续可以根据subject实例获取用户信息。

public ResultHolder login(LoginRequest request) {
        String login = (String) SecurityUtils.getSubject().getSession().getAttribute("authenticate");
        String username = StringUtils.trim(request.getUsername());
        String password = "";
        if (!StringUtils.equals(login, UserSource.LDAP.name())) {
            password = StringUtils.trim(request.getPassword());
            ……
        }
        UsernamePasswordToken token = new UsernamePasswordToken (username, password, login);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
            if (subject.isAuthenticated()) {
                UserDTO user = (UserDTO) subject.getSession().getAttribute(ATTR_USER);
               ……
                                return ResultHolder.success(subject.getSession().getAttribute("user"));
} else {
        return ResultHolder.error(Translator.get("login_fail"));
    }
} catch (ExcessiveAttemptsException e) {
    throw new ExcessiveAttemptsException(Translator.get("excessive_attempts"));
}
……
}

=============================================================

案例二:

@RestController
@CrossOrigin
@RequestMapping("/")
public class LoginController{
	
	private static final Logger logger = LoggerFactory.getLogger(LoginController.class);
	
	@Reference //Dubbo远程调用的服务
	private UserService userService;

	@RequestMapping(value="/login",method=RequestMethod.POST)
	public ResponseEntity login(){
		//获取存储在系统的用户
		ShiroUser user = (ShiroUser)SecurityUtils.getSubject().getPrincipal();

		//为获取的用户添加token
		user.setToken(SecurityUtils.getSubpect().getSession().getId().toString());
		return ResponseEntity.ok(user);
	}

	/**
		获取当前登陆人的信息(包括角色权限)
	*/
	@GetMapping("/logininfo")
	public ResponsseEntity loginInfo(){
		ShiroUser shiroUser = (ShiroUser) SecurityUtils.getSubject().getPrincipal();
		
		Map<String,Object> map = new HashMap<>();
		Set<String> permissions = Sets.newHashSet();

		//将获取的角色和权限存入指定的map
		User user = userService.getById(shiroUser.getUserId().intValue());
		map.put("roleList",user.getRoles());
		map.put("permissionList",permissions);
		map.put("userId",shiroUser.getUserId());
		map.put("username",shiroUser.getLoginName());

		return ResponseEntity.ok(map);
	}
}

======================================================

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

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

相关文章

【问题证明】矩阵方程化为特征值方程求得的特征值为什么是全部特征值?不会丢解吗?

问题 这个问题困扰了我好久&#xff0c;一直感觉如果有其他的特征值没法证伪&#xff0c;不过一直存在思想的层面&#xff0c;没有实际解决&#xff0c;今天突然想到动笔来解决&#xff0c;遂得解&#xff0c;证明如下。 证明 总结 这个证明看似证明过后很直观&#xff0c;但…

10.4 小任务

目录 QT实现TCP服务器客户端搭建的代码&#xff0c;现象 TCP服务器 .h文件 .cpp文件 现象 TCP客户端 .h文件 .cpp文件 现象 QT实现TCP服务器客户端搭建的代码&#xff0c;现象 TCP服务器 .h文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #includ…

智能合约漏洞,BEVO 代币损失 4.5 万美元攻击事件分析

智能合约漏洞&#xff0c;BEVO 代币损失 4.5 万美元攻击事件分析 一、事件背景 北京时间 2023 年 1 月 31 日&#xff0c;在 twitter 上看到这样一条消息&#xff1a; BEVO 代币被攻击&#xff0c;总共损失 45000 美元&#xff0c;导致 BEVO 代币的价格下跌了 99%。 有趣的是…

编程新手?跟着这个教程,用Python画出小猪佩奇

小猪佩奇是许多小朋友们的心头好&#xff0c;它的形象可爱、颜色鲜艳。你知道吗&#xff0c;只需要Python中的一个简单模块&#xff0c;我们就可以自己绘制出这个可爱的形象&#xff01;本文将教你如何使用Python的turtle模块&#xff0c;一步步画出小猪佩奇。 1. 准备工作&a…

当我们做后仿时我们究竟在仿些什么(四)

就像人类容易接受自然数&#xff0c;但对于负数缺乏某种直觉上的认识一样&#xff1b;后仿过程中经常出现的 Negative Delay 和 Negative Timing Check 也非常容易使人困惑。 Warning-[SDFCOM_NICD] Negative INTERCONNECT Delay encountered今天这篇首先简要分析这些 Negativ…

创建线程池

如何创建线程池及处理相应任务 目录 如何创建线程池及处理相应任务线程池定义解决的问题(需求)工作原理实现线程池创建示意图重要构造器创建线程池(ExecutorService)线程池任务处理常用API处理Runnable任务处理Callable任务 使用工具类(Executors)创建线程池常用API应用案例 拓…

桌面自动化工具总结

引言:产品经理提出桌面程序需要自动化的测试,避免繁琐的人肉点击。说干就干。 现有自动化工具是五花八门,我找了两个框架。 这两个框架都是基于微软的UIA 框架,链接地址 https://learn.microsoft.com/en-us/windows/win32/winauto/uiauto-providerportal?source=recommen…

以太网的MAC层

以太网的MAC层 一、硬件地址 ​ 局域网中&#xff0c;硬件地址又称物理地址或MAC地址&#xff08;因为用在MAC帧&#xff09;&#xff0c;它是局域网上每一台计算机中固化在适配器的ROM中的地址。 ​ 关于地址问题&#xff0c;有这样的定义&#xff1a;“名字指出我们所要寻…

【Spring】Bean作用域和生命周期

Bean作用域和生命周期 一. Bean 的作用域1. Bean 的 6 种作⽤域&#xff1a;①. singleton②. prototype③. request④. session⑤. application⑥. websocket单例作用域(singleton) VS 全局作⽤域(application) 2. 设置作用域 二. Spring 执行流程和 Bean 的生命周期1. Spring…

MySQL优化、锁、总结常见问题

慢 SQL 如何定位呢&#xff1f; 慢 SQL 的监控主要通过两个途径&#xff1a; 慢查询日志&#xff1a;开启 MySQL 的慢查询日志&#xff0c;再通过一些工具比如 mysqldumpslow 去分析对应的慢查询日志&#xff0c;当然现在一般的云厂商都提供了可视化的平台。服务监控&#xf…

如何实现torch.arange的tensor版本

文章目录 背景实现方案不可行的情况 背景 import torch我们都知道&#xff0c;torch.arange只支持数字&#xff0c;不支持tensor&#xff0c;如下&#xff1a; torch.arange(0,5,1)tensor([0, 1, 2, 3, 4]) 但是如果使用tensor&#xff0c;就会报错&#xff1a; torch.arang…

SpringBoot结合Redisson实现分布式锁

&#x1f9d1;‍&#x1f4bb;作者名称&#xff1a;DaenCode &#x1f3a4;作者简介&#xff1a;啥技术都喜欢捣鼓捣鼓&#xff0c;喜欢分享技术、经验、生活。 &#x1f60e;人生感悟&#xff1a;尝尽人生百味&#xff0c;方知世间冷暖。 &#x1f4d6;所属专栏&#xff1a;Sp…

力扣 -- 474. 一和零(二维费用的背包问题)

解题步骤&#xff1a; 参考代码&#xff1a; 未优化代码&#xff1a; class Solution { public:int findMaxForm(vector<string>& strs, int m, int n) {//开一个三维的dp表vector<vector<vector<int>>> dp(strs.size()1,vector<vector<in…

DevEco Studio如何安装中文插件

首先 官网下载中文插件 由于DevEco是基于IntelliJ IDEA Community的&#xff0c;所有Compatibility选择“IntelliJ IDEA Community”&#xff0c;然后下载一个对应最新的就ok了。 最后打开Plugins页面&#xff0c;点击右上角齿轮 -> Install Plugin from Disk…。选择下载的…

AcWing算法提高课-5.6.1同余方程

宣传一下 算法提高课整理 CSDN个人主页&#xff1a;更好的阅读体验 原题链接 题目描述 求关于 x x x 的同余方程 a x ≡ 1 ( m o d b ) ax ≡ 1 \pmod b ax≡1(modb) 的最小正整数解。 输入格式 输入只有一行&#xff0c;包含两个正整数 a , b a,b a,b&#xff0c;用一…

python模拟表格任意输入位置

在表格里输入数值&#xff0c;要任意位置&#xff0c;我找到了好方法&#xff1a; input输入 1. 行 2. 列输入&#xff1a;1 excel每行输入文字input输入位置 3.2 表示输入位置在&#xff1a;3行个列是要实现一个类似于 Excel 表格的输入功能&#xff0c;并且希望能够指定输入…

怎么通过docker/portainer部署vue项目

这篇文章分享一下今天通过docker打包vue项目&#xff0c;并使用打包的镜像在portainer上部署运行&#xff0c;参考了vue-cli和docker的官方文档。 首先&#xff0c;阅读vue-cli关于docker部署的说明 vue-cli关于docker部署的说明https://cli.vuejs.org/guide/deployment.html#…

智慧工地源代码 SaaS模式云平台

伴随着技术的不断发展&#xff0c;信息化手段、移动技术、智能穿戴及工具在工程施工阶段的应用不断提升&#xff0c;智慧工地概念应运而生&#xff0c;庞大的建设规模催生着智慧工地的探索和研发。 什么是智慧工地&#xff1f; 伴随着技术的不断发展&#xff0c;信息化手段、移…

八、动态规划(Dynamic Programming)

文章目录 一、理论基础二、题目分类&#xff08;一&#xff09;基础题目2.[70.爬楼梯](https://leetcode.cn/problems/climbing-stairs/)&#xff08;1&#xff09;思路&#xff08;2&#xff09;代码&#xff08;3&#xff09;复杂度分析 3.[746. 使用最小花费爬楼梯](https:/…

正点原子嵌入式linux驱动开发——U-boot顶层Makefile详解

在学习uboot源码之前&#xff0c;要先看一下顶层Makefile&#xff0c;分析gcc版本代码的时候一定是先从顶层Makefile开始的&#xff0c;然后再是子Makefile&#xff0c;这样通过层层分析Makefile即可了解整个工程的组织结构。顶层Makefile也就是uboot根目录下的Makefile文件&am…