【SpringBoot】SpringBoot2.x整合Shiro(一)
一:什么是ACL和RBAC:
ACL: Access Control List 访问控制列表
以前盛行的一种权限设计,它的核心在于用户直接和权限挂钩
优点:简单易用,开发便捷
缺点:用户和权限直接挂钩,导致在授予时的复杂性,比较分散,不便于管理
例子:常见的文件系统权限设计, 直接给用户加权限
RBAC: Role Based Access Control
基于角色的访问控制系统。权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限
优点:简化了用户与权限的管理,通过对用户进行分类,使得角色与权限关联起来
缺点:开发对比ACL相对复杂
例子:基于RBAC模型的权限验证框架与应用 Apache Shiro、spring Security
BAT企业 ACL,一般是对报表系统,阿里的ODPS
总结:不能过于复杂,规则过多,维护性和性能会下降, 更多分类 ABAC、PBAC等
二:Apache Shiro基础知识和架构
Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能
shiro包含四大核心模块:身份认证,授权,会话管理和加密
直达Apache Shiro官网 http://shiro.apache.org/introduction.html
什么是身份认证
Authentication,身份证认证,一般就是登录
什么是授权
Authorization,给用户分配角色或者访问某些资源的权限
什么是会话管理
Session Management, 用户的会话管理员,多数情况下是web session
什么是加密
Cryptography, 数据加解密,比如密码加解密等
shiro架构图
三.用户访问Shiro权限控制运行流程
Subject
我们把用户或者程序称为主体(如用户,第三方服务,cron作业),主体去访问系统或者资源
SecurityManager
安全管理器,Subject的认证和授权都要在安全管理器下进行
Authenticator
认证器,主要负责Subject的认证
Realm
数据域,Shiro和安全数据的连接器,好比jdbc连接数据库; 通过realm获取认证授权相关信息
Authorizer
授权器,主要负责Subject的授权, 控制subject拥有的角色或者权限
Cryptography
加解密,Shiro的包含易于使用和理解的数据加解密方法,简化了很多复杂的api
Cache Manager
缓存管理器,比如认证或授权信息,通过缓存进行管理,提高性能
四.Springboot2.x整合Apache Shiro实战
项目环境:
Maven3.5 + Jdk8 + Springboot 2.X + IDEA
相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!--注释掉-->
<!--<scope>runtime</scope>-->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--阿里巴巴druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<!--spring整合shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
</dependency>
<!-- shiro+redis缓存插件 主要用于CacheManager,以及缓存sessionManage中配置session持久化-->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.1.0</version>
</dependency>
Shiro各个组件说明
Realm:
realm作用:Shiro 从 Realm 获取安全数据
默认自带的realm:idae查看realm继承关系,有默认实现和自定义继承的realm
两个概念
principal : 主体的标示,可以有多个,但是需要具有唯一性,常见的有用户名,手机号,邮箱等
credential:凭证, 一般就是密码
所以一般我们说 principal + credential 就账号 + 密码
开发中,往往是自定义realm , 即继承 AuthorizingRealm,重写doGetAuthenticationInfo(认证方法)和doGetAuthorizationInfo(授权方法)
当用户登陆的时候会调用 doGetAuthenticationInfo
进行权限校验的时候会调用: doGetAuthorizationInfo
UsernamePasswordToken : 对应就是 shiro的token中有Principal和Credential
UsernamePasswordToken-》HostAuthenticationToken-》AuthenticationToken
SimpleAuthorizationInfo:代表用户角色权限信息
SimpleAuthenticationInfo :代表该用户的认证信息
Shiro内置的Filter
核心过滤器类:DefaultFilter, 配置哪个路径对应哪个拦截器进行处理
authc:org.apache.shiro.web.filter.authc.FormAuthenticationFilter
需要认证登录才能访问
user:org.apache.shiro.web.filter.authc.UserFilter
用户拦截器,表示必须存在用户。
anon:org.apache.shiro.web.filter.authc.AnonymousFilter
匿名拦截器,不需要登录即可访问的资源,匿名用户或游客,一般用于过滤静态资源。
roles:org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
角色授权拦截器,验证用户是或否拥有角色。
参数可写多个,表示某些角色才能通过,多个参数时写 roles["admin,user"],当有多个参数时必须每个
参数都通过才算通过
perms:org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
权限授权拦截器,验证用户是否拥有权限
参数可写多个,表示需要某些权限才能通过,多个参数时写 perms["user, admin"],当有多个参数时必
须每个参数都通过才算可以
authcBasic:org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
httpBasic 身份验证拦截器。
logout:org.apache.shiro.web.filter.authc.LogoutFilter
退出拦截器,执行后会直接跳转到shiroFilterFactoryBean.setLoginUrl(); 设置的 url
port:org.apache.shiro.web.filter.authz.PortFilter
端口拦截器, 可通过的端口。
ssl:org.apache.shiro.web.filter.authz.SslFilter
ssl拦截器,只有请求协议是https才能通过。
Filter的配置路径说明:
/admin/video /user /pub
路径通配符支持 ?、*、**,注意通配符匹配不 包括目录分隔符“/”
心 可以匹配所有,不加*可以进行前缀匹配,但多个冒号就需要多个 * 来匹配
URL权限采取第一次匹配优先的方式
? : 匹配一个字符,如 /user? , 匹配 /user3,但不匹配/user/;
* : 匹配零个或多个字符串,如 /add* ,匹配 /addtest,但不匹配 /user/1
** : 匹配路径中的零个或多个路径,如 /user/** 将匹 配 /user/xxx 或 /user/xxx/yyy
例子
/user/**=filter1
/user/add=filter2
请求 /user/add 命中的是filter1拦截器
性能问题:通配符比字符串匹配会复杂点,所以性能也会稍弱,推荐是使用字符串匹配方式
数据加解密器CredentialsMatche
一般会自定义验证规则
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new
HashedCredentialsMatcher();
//散列算法,使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//散列的次数,比如散列两次,相当于 md5(md5("xxx"));
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
Shiro的缓存模块CacheManager
shiro中提供了对认证信息和授权信息的缓存。
默认是关闭认证信息缓存的,对于授权信息的缓存shiro默认开启的(因为授权的数据量大,每次都要查询数据库,性能受到影响)
AuthenticatingRealm 及 AuthorizingRealm 分别提供了对AuthenticationInfo 和 AuthorizationInfo 信息的缓
存。
Shiro中的SessionManager
用户和程序直接的链接,程序可以根据session识别到哪个用户,和javaweb中的session类似
什么是会话管理器SessionManager
会话管理器管理所有subject的所有操作,是shiro的核心组件,前后端分离的应用中session通常用token代替,用于会话管理
RBAC权限控制架构设计
1.数据库设计:老五张表
用户,角色,权限,用户角色表,角色权限表
2.shiro相关配置
-配置ShiroFilterFactoryBean
配置流程和思路
shiroFilterFactoryBean-》
-SecurityManager-》
-CustomSessionManager
-CustomRealm-》hashedCredentialsMatcher
SessionManager
-DefaultSessionManager: 默认实现,常用于javase
-ServletContainerSessionManager: web环境
-DefaultWebSessionManager:常用于自定义实现(一般用这种)
ShiroConfig配置
@Configuration
public class ShiroConfig { /**
* 配置ShiroFilterFactoryBean
*
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { System.out.println("执行 ShiroFilterFactoryBean shiroFilter");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //必须设置securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager); //需要登录的接口:如果访问某个接口,需要登录却没有登录,则调用此接口,如果前端后端不分离,则跳转到html页面
shiroFilterFactoryBean.setLoginUrl("/pub/need_login"); //登录成功 跳转url,如果前后端分离,则没这个调用 --这里设置为首页就行了
shiroFilterFactoryBean.setSuccessUrl("/"); //登录成功,但是没有权限,未授权就会调用这个接口,如果不是前后端分离,则跳转到403页面
shiroFilterFactoryBean.setUnauthorizedUrl("/pub/not_permit"); //设置自定义filter
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("roleOrFilter", new CustomRoleFilter()); //shiroFilterFactoryBean绑定自定义的filter
shiroFilterFactoryBean.setFilters(filterMap); //过滤器链的map
//拦截器(过滤器路径,坑一必须要用LinkedhashMap),部分路径无法进行拦截
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); /*
********************************************
通常是配置这些过滤器,也可以用个数据库的动态加载,这些数据
********************************************/
//退出过滤器
filterChainDefinitionMap.put("/logout", "logout"); //匿名可以访问,也就是游客模式
filterChainDefinitionMap.put("/pub/**", "anon"); //登录用户才可以访问
filterChainDefinitionMap.put("/authc/**", "authc"); //管理员角色才可以访问
filterChainDefinitionMap.put("/admin/**", "roles[admin]"); //有编辑权限才可以访问
filterChainDefinitionMap.put("/video/update", "perms[video_update]"); //坑二:过滤器是顺序执行,从上而下,一般来说/** 放到最下面 //authc: url定义必须通过认证才可以访问
//anno: url可以匿名访问
filterChainDefinitionMap.put("/**", "authc"); //配置过滤器
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean;
} /**
* 数据域
*
* @return
*/
@Bean
public CustomRealm customRealm() { CustomRealm customRealm = new CustomRealm(); //设置加密器--因为数据库中的密码不是明文存储
customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return customRealm;
}
/**
* 自定义seesionManager
* -配置session持久化
* @return
*/
@Bean
public SessionManager sessionManager() {
//自定义CustomSessionManager 继承 DefaultWebSessionManager
CustomSessionManager customSessionManager = new CustomSessionManager(); //配置session持久化
customSessionManager.setSessionDAO(redisSessionDAO()); //超时时间,默认 30分钟,会话超时;方法里面的单位是毫秒
customSessionManager.setGlobalSessionTimeout(20000); return customSessionManager;
} /**
* 密码加解密规则 CredentialMatcher-配置在数据域中,用于数据的加解密
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); //设置散列算法:这里使用MD5算法
hashedCredentialsMatcher.setHashAlgorithmName("md5"); //散列次数,好比散列两次 相当于md5(md5(x))
hashedCredentialsMatcher.setHashIterations(2); return hashedCredentialsMatcher;
}
/**
* 配置redisManager
*/
@Bean
public RedisManager getRedisManager() { RedisManager redisManager = new RedisManager(); //默认就是localhost:6379 不写也行
redisManager.setHost("localhost");
redisManager.setPort(6379);
return redisManager;
}
/**
* 配置具体cache实现类RedisCacheManager
* 为什么要使用缓存:
* 缓存组件位于SecurityManager中,在CustomRealm数据域中,由于授权方法中每次都要查询数据库,性能受影响,因此将数据缓存起来,提高查询效率
* 除了使用Redis缓存,还能使用shiro-ehcache
*
* @return
*/
public RedisCacheManager redisCacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(getRedisManager());
//设置过期时间,单位是秒,20s,
redisCacheManager.setExpire(20);
return redisCacheManager;
}
/**
* 自定义session持久化
*
* @return
*/
public RedisSessionDAO redisSessionDAO() { /*
为啥session也要持久化?
重启应用,用户无感知,可以继续以原先的状态继续访问
注意点:
DO对象需要实现序列化接口 Serializable
logout接口和以前一样调用,请求logout后会删除redis里面的对应的key,即删除对应的token
*/
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(getRedisManager()); //配置自定义sessionId,shiro自动生成色sessionId不满足条件时可以使用
redisSessionDAO.setSessionIdGenerator(new CustomSessionIdGenerator());
return redisSessionDAO;
}
/**
* LifecycleBeanPostProcessor
* 管理shiro一些bean的生命周期 即bean初始化 与销毁
*
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
} /**
* AuthorizationAttributeSourceAdvisor
* 作用:加入shiro注解的使用,不加入这个AOP注解不生效(shiro的注解 例如 @RequiresGuest)
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new
AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
} /**
* DefaultAdvisorAutoProxyCreator
* 作用: 用来扫描上下文寻找所有的Advistor(通知器), 将符合条件的Advisor应用到切入点的Bean中,需
* 要在LifecycleBeanPostProcessor创建后才可以创建
*
* @return
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new
DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setUsePrefix(true);
return defaultAdvisorAutoProxyCreator;
}
}
自定义Filter==>>>CustomRoleFilter
/**
* 自定义Filter
* 问题:为什么要自定义filter:
* 因为配置role[admin,root] ,只有同时满足admin和root才能够访问,显然这是不合理的
* 实际是超级管理员有全部权限
*/
public class CustomRoleFilter extends AuthorizationFilter { @Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
//获取当前访问路径所有角色的集合
Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) mappedValue; //没有角色显示,可以直接访问
if (rolesArray == null || rolesArray.length == 0) {
//no roles specified, so nothing to check - allow access.
return true;
} //当前subject是roles中的任意一个,则有权限访问
Set<String> roles = CollectionUtils.asSet(rolesArray); for (String role : roles) {
if (subject.hasRole(role)) {
return true;
}
}
return subject.hasAllRoles(roles);
}
}
自定义SessionManager:===>>CustomSessionManager
/** @Discription 自定义SessionMananger 为什么要自定义自定义SessionMananger?
* 原因:
* 因为前后端分离的情况下 不是靠session,而是靠token去交互,因此需要自定义这个sessionId的获取
* 即重写父类的方法(父类是从头中拿sessionId)
*
*/
public class CustomSessionManager extends DefaultWebSessionManager { //这个key 放在请求头中,可以自己定义 ,通常是设置为token或者authorization
private static final String AUTHORIZATION = "token"; public CustomSessionManager() {
super();
} @Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
//将ServletRequest转换成HttpServletRequest
String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
if (sessionId != null) {
// request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "cookie");
//
// request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
// //automatically mark it valid here. If it is invalid, the
// //onUnknownSession method below will be invoked and we'll remove the attribute at that time.
// request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId); //automatically mark it valid here. If it is invalid, the
//onUnknownSession method below will be invoked and we'll remove the attribute at that time.
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return sessionId;
} else {
return super.getSessionId(request, response);
}
}
}
自定义数据域 CustomRealm
**
* 自定义的realm 数据域
*/ public class CustomRealm extends AuthorizingRealm { @Autowired
private UserService userService; /**
* 权限校验的时候会调用
*
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("授权 doGetAuthorizationInfo"); //从token中获取用户信息,token代表用户输入
// String username = (String) principals.getPrimaryPrincipal();
User newUser = (User) principals.getPrimaryPrincipal(); // 使用原因?
// 授权的时候每次都去查询数据库,对于频繁访问的接口,性能和响应速度比较慢,所以使用缓存 //提高性能的方法1-使用redis缓存:
// 将信息放到缓存,例如redis,但是要设置缓存失效时间,因为可能更新数据库了,但是缓存没有更新
//提高性能的方法2-使用shiro-redis集成的缓存:
// shiro-redis的缓存配置在SecurityManager中
User user = userService.findAllUserInfoByUsername(newUser.getUsername()); List<String> stringRoleList = new ArrayList<>();
List<String> stringPermissionList = new ArrayList<>(); List<Role> roleList = user.getRoleList(); for (Role role : roleList) {
stringRoleList.add(role.getName()); List<Permission> permissionList = role.getPermissionList(); for (Permission p : permissionList) {
if (p != null) {
stringPermissionList.add(p.getName());
}
}
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //将用户对应的角色和权限信息 放到权限器中
simpleAuthorizationInfo.addStringPermissions(stringPermissionList);
simpleAuthorizationInfo.addRoles(stringRoleList); return simpleAuthorizationInfo;
} /**
* 用户登录的时候会调用
*
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("认证 doGetAuthenticationInfo"); //从token中获取用户信息,token代表用户输入
String username = (String) token.getPrincipal(); User user = userService.findAllUserInfoByUsername(username); //取密码
String password = user.getPassword(); if (password == null || "".equals(password)) {
return null;
} return new SimpleAuthenticationInfo(user, password, this.getClass().getName());
}
} /*
* 原有的问题
class java.lang.String must has getter for field: authCacheKey or id\nWe need a
field to identify this Cache Object in Redis. So you need to defined an id field
which you can get unique id to identify this principal. For example, if you use
UserInfo as Principal class, the id field maybe userId, userName, email, etc. For
example, getUserId(), getUserName(), getEmail(), etc.\nDefault value is
authCacheKey or id, that means your principal object has a method called
\"getAuthCacheKey()\" or \"getId()\""
改造原有的逻辑,修改缓存的唯一key doGetAuthorizationInfo 方法
原有:
String username = (String)principals.getPrimaryPrincipal();
User user = userService.findAllUserInfoByUsername(username);
改为
User newUser = (User)principals.getPrimaryPrincipal();
User user = userService.findAllUserInfoByUsername(newUser.getUsername()); doGetAuthenticationInfo方法
原有:
return new SimpleAuthenticationInfo(username, user.getPassword(),
this.getClass().getName());
改为
return new SimpleAuthenticationInfo(user, user.getPassword(),
this.getClass().getName());
*
*
* */
代码地址:
https://github.com/AlenYang123456/Springboot-Shiro
【SpringBoot】SpringBoot2.x整合Shiro(一)的更多相关文章
- SpringBoot2.x整合Shiro(一)
一:什么是ACL和RBAC: ACL: Access Control List 访问控制列表 以前盛行的一种权限设计,它的核心在于用户直接和权限挂钩 优点:简单易用,开发便捷 缺点:用户和权限直接挂钩 ...
- SpringBoot 优雅的整合 Shiro
Apache Shiro是一个功能强大且易于使用的Java安全框架,可执行身份验证,授权,加密和会话管理.借助Shiro易于理解的API,您可以快速轻松地保护任何应用程序 - 从最小的移动应用程序到最 ...
- 在 springboot 中如何整合 shiro 应用 ?
Shiro是Apache下的一个开源项目,我们称之为Apache Shiro. 它是一个很易用与Java项目的的安全框架,提供了认证.授权.加密.会话管理,与spring Security 一样都是 ...
- SpringBoot2.0 整合 Shiro 框架,实现用户权限管理
本文源码:GitHub·点这里 || GitEE·点这里 一.Shiro简介 1.基础概念 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理.作为一款安全 ...
- SpringBoot2.x整合Shiro出现cors跨域问题(踩坑记录)
1. Springboot如何跨域? 最简单的方法是: 定义一个配置CorsConfig类即可(是不是简单且无耦合到令人发指) @Configuration public class CorsConf ...
- SpringBoot学习:整合shiro(身份认证和权限认证),使用EhCache缓存
项目下载地址:http://download.csdn.NET/detail/aqsunkai/9805821 (一)在pom.xml中添加依赖: <properties> <shi ...
- SpringBoot学习:整合shiro自动登录功能(rememberMe记住我功能)
首先在shiro配置类中注入rememberMe管理器 /** * cookie对象; * rememberMeCookie()方法是设置Cookie的生成模版,比如cookie的name,cooki ...
- SpringBoot学习:整合shiro(rememberMe记住我后自动登录session失效解决办法)
项目下载地址:http://download.csdn.NET/detail/aqsunkai/9805821 定义一个拦截器,判断用户是通过记住我登录时,查询数据库后台自动登录,同时把用户放入ses ...
- SpringBoot学习:整合shiro(验证码功能和登录次数限制功能)
项目下载地址:http://download.csdn.NET/detail/aqsunkai/9805821 (一)验证码 首先login.jsp里增加了获取验证码图片的标签: <body s ...
随机推荐
- 翻译:《实用的Python编程》03_03_Error_checking
目录 | 上一节 (3.2 深入函数) | 下一节 (3.4 模块) 3.3 错误检查 虽然前面已经介绍了异常,但本节补充一些有关错误检查和异常处理的其它细节. 程序是如何运行失败的 Python 不 ...
- LeetCode-二叉树的深度
二叉树的深度 二叉树的深度 使用递归求解二叉树的深度. 需要注意使用的临界条件. /** * 任意一个二叉树的最大深度 **/ #include<iostream> #include< ...
- 网络好不好,ping一下就知道
摘要:在测试和部署网络通信应用时,我们经常会遇到网络不通的问题,一般都会想到ping一下.本文将带您了解ping命令的作用和原理~ 在测试和部署网络通信应用时,我们经常会遇到网络不通的问题.一般都会想 ...
- 什么是内存对齐,go中内存对齐分析
内存对齐 什么是内存对齐 为什么需要内存对齐 减少次数 保障原子性 对齐系数 对齐规则 总结 参考 内存对齐 什么是内存对齐 弄明白什么是内存对齐的时候,先来看一个demo type s struct ...
- C语言之结构体内存的对齐
C语言之结构体内存的对齐 大纲: 零.引例 一.结构体内存对齐规则 二.怎样计算结构体的大小 三.设计结构体时要注意的方面 四.为什么存在内存对齐 五.修改默认对齐数 在前面的章节中,我们谈到了C ...
- Tomcat后台爆破指南
0x00 实验环境 攻击机:Win 10 0x01 爆破指南 针对某Tomcat默认管理页面: (1)这里主要是介绍一种比较好用的burp爆破方法: 点击Tomcat后台管理链接 Tomc ...
- 测试工程师Docker进阶
学习整理来源 B站 狂神说Java https://space.bilibili.com/95256449/ 四.docker镜像 1.镜像是什么 镜像是一种轻量级.可执行的独立软件包,用来打包软件运 ...
- java网络通信不止UDP,TCP
预备知识 多线程 实现多线程 线程池 IO流 核心功能就是读和写 扩展功能对什么读写,怎么读写,如何优化读写 网络基础 IP IP规定网络上所有的设备都必须有一个独一无二的IP地址,就好比是邮件上都必 ...
- PTE 准备之 Describe Image
25s 准备时间:决定用什么模板,用模板cover那些信息点 Content: 数字和文字哪个多,就多说哪个,均匀覆盖 Fluency : 保持流利度 不要纠结时态,单复数,人称代词等 时间要求: 尽 ...
- java面试记很多次还是记不住的问题
1.java底层如何实现多态 https://blog.csdn.net/fan2012huan/article/details/51007517 (1)在常量池中找到方法调用的符号引用 (2)查看P ...