spring-boot+mybatisPlus+shiro的集成demo 我用了5天
spring-boot + mybatis-plus + shiro 的集成demo我用了五天
关于shiro框架,我还是从飞机哪里听来的,就连小贱都知道,可我母鸡啊。简单百度了下,结论很好上手,比spring的security要简单许多...于是我就是开始了我的shiro学习之路 。正巧这几天在研究spring-boot集成mybatis-plus 于是乎我就把shiro也揉了进去,但是效果并不像我预期想象的那样。
以下是我这几天血泪换来的成果-->
基本概念
shiro :隶属Apache 简单易用的java安全框架 三大件 :Subject, SecurityManager 和 Realm
Subject:即当前操作用户
SecurityManager:安全管理器
Realm:用户数据的概念,域
过程:
1)建表 数据库一般至少有五张表
1-user:用户账号密码
2-role:角色ID,一个账户可以有很多角色
3-permission权限ID,一个角色可以有很多权限
4-user_role关系对照表:记录每个userID有的角色
5-role_permission关系对照表,记录每个role有的permission
-- 用户表
DROP TABLE IF EXISTS `user_info`;
CREATE TABLE `user_info` (
`uid` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`pass_word` varchar(255) DEFAULT NULL COMMENT '密码',
`salt` varchar(255) DEFAULT NULL COMMENT '加密',
`state` tinyint(4) NOT NULL COMMENT '状态',
`username` varchar(255) DEFAULT NULL COMMENT '用户名',
`email` varchar(64) DEFAULT NULL COMMENT '邮箱',
`crtime` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`uid`),
UNIQUE KEY(`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 角色表
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`available` bit(1) DEFAULT NULL,
`description` varchar(255) DEFAULT NULL,
`role` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; -- 用户角色表
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`uid` int(11) NOT NULL,
`role_id` int(11) NOT NULL,
PRIMARY KEY(`uid`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 权限表
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
`id` int(11) NOT NULL COMMENT 'PMK',
`available` bit(1) DEFAULT NULL COMMENT '是否激活',
`name` varchar(255) DEFAULT NULL,
`parent_id` bigint(20) DEFAULT NULL,
`parent_ids` varchar(255) DEFAULT NULL,
`permission` varchar(255) DEFAULT NULL COMMENT '权限',
`resource_type` enum('menu','button') DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- 角色权限表
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission` (
`permission_id` int(11) NOT NULL,
`role_id` int(11) NOT NULL,
PRIMARY KEY(`role_id`,`permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2)项目搭建
spring-boot mybatis-plus shiro
1.pom.xml 贴出部分
<!-- mybits-plus -starter-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatisplus-spring-boot-starter</artifactId>
<version>1.0.5</version>
</dependency>
<!-- MP 核心库 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>2.1.8</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- 模板引擎 代码生成 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
</dependency>
<!-- mybits-plus -end-->
<!--shiro 登录认证-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>5.1.35</scope>
</dependency>
<!--druid 数据库连接池监控-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.0</version>
</dependency>
<!--jasypt 数据库加解密-->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>1.8</version>
</dependency>
2.先集成mybatis-plus 利用自动生成方法生成 新建5张表的POJO 和 Mapper Service文件
3.编写ShiroConfig配置类
package com.zxt.ms.configs; import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties; /**
* @ClassName ShiroConfig
* @Description ms 梦想家
* @Author Zhai XiaoTao https://www.cnblogs.com/zhaiyt
* @Date 2019/1/26 17:27
* @Version 1.0
*/
@Slf4j
@Configuration
public class ShiroConfig { @Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
} /**
* shiro自带过滤器,无需再另外设置filter
*
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { log.info("ShiroConfig.shirFilter() start ..."); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//配置过滤器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); //没登录的页面
shiroFilterFactoryBean.setLoginUrl("/notLogin");
// 设置无权限时跳转的 url;
shiroFilterFactoryBean.setUnauthorizedUrl("/notRole"); /*
* shiro 内置枚举
* anon 表示可以匿名使用
* authc 表示需要认证(登录)才能使用,没有参数
*/
//静态资源允许访问
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/layer/**", "anon"); //游客,开发权限
filterChainDefinitionMap.put("/guest/**", "anon");
//用户,需要角色权限 “user”
filterChainDefinitionMap.put("/user/**", "roles[user]");
//管理员,需要角色权限 “admin”
filterChainDefinitionMap.put("/admin/**", "roles[admin]");
//开放登陆接口
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/loginUser", "anon");
//其余接口一律拦截
//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截
filterChainDefinitionMap.put("/**", "authc"); //过滤器注入工厂类
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); log.info("ShiroConfig.shirFilter() end ...");
return shiroFilterFactoryBean;
} /**
* @return org.apache.shiro.mgt.SecurityManager
* @Description <安全管理器Bean>
* @Author Zhaiyt
* @Date 14:35 2019/1/28
* @Param
**/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//注入realm
securityManager.setRealm(myShiroRealm());
return securityManager;
} /**
* @return com.zxt.ms.configs.ShiroRealm
* @Description <域 的概念 Shiro 从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,
* 那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;
* 可以把Realm看成DataSource , 即安全数据源>
* @Author Zhaiyt
* @Date 14:38 2019/1/28
* @Param
**/
@Bean
public ShiroRealm myShiroRealm() {
ShiroRealm myShiroRealm = new ShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
} /**
* @return org.springframework.web.servlet.handler.SimpleMappingExceptionResolver
* @Description <异常处理>
* @Author Zhaiyt
* @Date 15:20 2019/1/28
* @Param
**/
@Bean(name = "simpleMappingExceptionResolver")
public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() {
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理
mappings.setProperty("UnauthorizedException", "403");
exceptionResolver.setExceptionMappings(mappings); // None by default
exceptionResolver.setDefaultErrorView("error"); // No default
exceptionResolver.setExceptionAttribute("ex"); // Default is "exception"
return exceptionResolver;
} /**
* 因为我们的密码是加过密的,所以,如果要Shiro验证用户身份的话,需要告诉它我们用的是md5加密的,并且是加密了两次。
* @Description <加密>
* @Author Zhaiyt
* @Date 16:18 2019/1/29
* @Param
* @return org.apache.shiro.authc.credential.HashedCredentialsMatcher
**/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
} /**
* @Description <因为只有开启了AOP才执行doGetAuthorizationInfo(),也就权限拦截>
* @Author Zhaiyt
* @Date 16:18 2019/1/29
* @Param
* @return org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor
**/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
shiro配置类编写主要需要注意以下几点:
1.Shiro 过滤器
2.SecurityManager 安全管理器
3.加密方式
4.域需要注入到SecurityManager中
4.自定义ShiroRealm编写:
package com.zxt.ms.configs; import com.zxt.ms.entity.UserInfo;
import com.zxt.ms.service.IUserInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component; import java.util.Set; /**
* @ClassName ShiroRealm
* @Description ms 梦想家
* @Author Zhai XiaoTao https://www.cnblogs.com/zhaiyt
* @Date 2019/1/26 16:54
* @Version 1.0
*/
@Slf4j
@Component
public class ShiroRealm extends AuthorizingRealm { @Autowired
private IUserInfoService userInfoServiceImpl; /**
* @Description <权限验证>
* @Author Zhaiyt
* @Date 14:57 2019/1/28
* @Param
* @return org.apache.shiro.authz.AuthorizationInfo
**/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //因为非正常退出,即没有显式调用 SecurityUtils.getSubject().logout()
if (!SecurityUtils.getSubject().isAuthenticated()) {
log.info("非正常退出,清除缓存");
doClearCache(principalCollection);
SecurityUtils.getSubject().logout();
return null;
} UserInfo userInfo = (UserInfo)principalCollection.getPrimaryPrincipal();
String username = userInfo.getUsername();
//用户存在 授权
if(StringUtils.isNotBlank(username)){
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Set<String> roles=userInfoServiceImpl.findRoleByUser(userInfo.getUsername());
Set<String> permissions=userInfoServiceImpl.findPermissionByUser(userInfo.getUsername());
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
return null;
} /**
* @Description <身份验证>
* @Author Zhaiyt
* @Date 14:58 2019/1/28
* @Param
* @return org.apache.shiro.authc.AuthenticationInfo
**/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("shiroRealm.doGetAuthenticationInfo() start ...");
//获取用户的输入的账号.
String username = (String)authenticationToken.getPrincipal(); //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
if(StringUtils.isNotBlank(username)){ UserInfo userInfo = userInfoServiceImpl.findByUsername(username); if(userInfo == null){
log.error("用户不存在");
throw new UnknownAccountException("用户名或密码错误!");
} SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userInfo, //用户名
userInfo.getPassword(), //密码
ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt
getName() //realm name
);
return authenticationInfo;
}
throw new UnknownAccountException("用户名或密码错误!");
}
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
} /**
* 清除权限缓存
* 使用方法:在需要清除用户权限的地方注入 ShiroRealm,
* 然后调用其clearCache方法。
*/
public void clearCache() {
PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
super.clearCache(principals);
} public static void main(String[] args) {
String hashAlgorithmName = "MD5";
String credentials = "123456";
int hashIterations = 2;
ByteSource credentialsSalt = ByteSource.Util.bytes("zhaizhai");
Object obj = new SimpleHash(hashAlgorithmName, credentials, credentialsSalt, hashIterations);
System.out.println(obj);
}
}
ShiroRealm 需要集成 AuthorizingRealm 这个里面要实现两个方法,一个认证 doGetAuthenticationInfo,一个授权 doGetAuthorizationInfo
5.测试controller编写
package com.zxt.ms.controller; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map; /**
* @ClassName TestController
* @Description ms 梦想家
* @Author Zhai XiaoTao https://www.cnblogs.com/zhaiyt
* @Date 2019/1/28 16:16
* @Version 1.0
*/
@Slf4j
@Controller
public class HomeController { @RequiresPermissions(value = "admin")
@RequestMapping(value = "/index")
public String test(){
return "index";
} @GetMapping(value = "/login")
public String login(){
return "login";
} @RequestMapping("/loginUser")
@ResponseBody
public Map<String,Object> login(HttpServletRequest request, HttpServletResponse response) throws Exception{
log.info("HomeController.login()");
String username = request.getParameter("username");
String password = request.getParameter("password");
String rememberMe = request.getParameter("rememberMe");
Map<String,Object> map = new HashMap<>();
String ret="";
Subject currentUser = SecurityUtils.getSubject();
if (!currentUser.isAuthenticated()) {
UsernamePasswordToken token = new UsernamePasswordToken(username,
password);
token.setRememberMe(rememberMe=="true");
map.put("code","FAILD");
try {
currentUser.login(token);
map.put("code","SUCCESS");
map.put("msg","登陆成功");
} catch (UnknownAccountException ex) {
map.put("msg","账号错误");
log.error(MapUtils.getString(map,"msg"));
} catch (IncorrectCredentialsException ex) {
map.put("msg","密码错误");
log.error(MapUtils.getString(map,"msg"));
} catch (LockedAccountException ex) {
map.put("msg","账号已被锁定,请与管理员联系");
log.error(MapUtils.getString(map,"msg"));
} catch (AuthenticationException ex) {
map.put("msg","您没有授权");
log.error(MapUtils.getString(map,"msg"));
}
}
return map;
}
}
代码基本上就上面那些,但是我深知,仅仅只有上面那些东西根本就跑不起来,我不知道我为什么要写这样的东西,可能某个小哥在看我写的博客时候也会骂我吧,写的什么鬼鸡仔。。。 我是没有办法把所有东西都整到这来,没有任何意义,有些坑必须自己去踩,只有踩过了,也许才会记得更清楚。
下面我要说坑了:
1.编写shiroFilter的时候我没有将静态数据过滤,导致页面展示格式错乱
2.登陆使用的是ajax,而shiro当做是表单的提交,所以一开始的 login controller 写的是有问题,导致一直报 302
3.认证无法通过,在shiro配置类中我指定了加密方式,为MD5 2次离散 ,在Realm中我指定了需要加 salt 的加密方式,因此密码的加密方式为 MD5 + salt 我使用main 跑出来加密后的数据,添加至数据库,可是一直无法认证成功,后发现在SecurityManager注入的ShiroRealm实体没有set加密方法...
4.认证成功后无法回调授权方法,原因,需要权限校验的方法上添加 @RequiresPermissions(value = "admin") OK
spring-boot+mybatisPlus+shiro的集成demo 我用了5天的更多相关文章
- spring boot 和shiro的代码实战demo
spring boot和shiro的代码实战 首先说明一下,这里不是基础教程,需要有一定的shiro知识,随便百度一下,都能找到很多的博客叫你基础,所以这里我只给出代码. 官方文档:http://sh ...
- Spring Boot微服务如何集成fescar解决分布式事务问题?
什么是fescar? 关于fescar的详细介绍,请参阅fescar wiki. 传统的2PC提交协议,会持有一个全局性的锁,所有局部事务预提交成功后一起提交,或有一个局部事务预提交失败后一起回滚,最 ...
- Spring Boot 2.0 快速集成整合消息中间件 Kafka
欢迎关注个人微信公众号: 小哈学Java, 每日推送 Java 领域干货文章,关注即免费无套路附送 100G 海量学习.面试资源哟!! 个人网站: https://www.exception.site ...
- Spring Boot 微服务应用集成Prometheus + Grafana 实现监控告警
Spring Boot 微服务应用集成Prometheus + Grafana 实现监控告警 一.添加依赖 1.1 Actuator 的 /prometheus端点 二.Prometheus 配置 部 ...
- 基于Spring Boot和Shiro的后台管理系统FEBS
FEBS是一个简单高效的后台权限管理系统.项目基础框架采用全新的Java Web开发框架 —— Spring Boot 2.0.3,消除了繁杂的XML配置,使得二次开发更为简单:数据访问层采用Myba ...
- Spring Boot 整合 Shiro ,两种方式全总结!
在 Spring Boot 中做权限管理,一般来说,主流的方案是 Spring Security ,但是,仅仅从技术角度来说,也可以使用 Shiro. 今天松哥就来和大家聊聊 Spring Boot ...
- Spring Boot与ActiveMQ的集成
Spring Boot对JMS(Java Message Service,Java消息服务)也提供了自动配置的支持,其主要支持的JMS实现有ActiveMQ.Artemis等.本节中,将以Active ...
- spring boot与ElasticSearch的集成
本文主要介绍Spring boot与ElasticSearch的集成,因为Spring boot的教程以及ElasticSearch的学习其他博客可能更优秀,所以建议再看这篇文章前先学习学习一下Spr ...
- Spring Boot2 系列教程(三十二)Spring Boot 整合 Shiro
在 Spring Boot 中做权限管理,一般来说,主流的方案是 Spring Security ,但是,仅仅从技术角度来说,也可以使用 Shiro. 今天松哥就来和大家聊聊 Spring Boot ...
随机推荐
- [再寄小读者之数学篇](2014-11-02 Herglotz' trick)
设 $f$ 是 $\bbR$ 上周期为 $1$ 的连续可微函数, 满足 $$\bee\label{141102_f} f(x)+f\sex{x+\frac{1}{2}}=f(2x),\quad\for ...
- /etc/profile文件被改坏导致命令不可用
这几天在装一个软件,设置环境变量的时候,不小心把/etc/profile文件改坏了(就是没配置对),在source /etc/profile后导致所有命令都不可用了.出现如下报错: -bash: xx ...
- gitlab升级和迁移
由于近期公司gitlab服务器老是卡顿和出现其他问题,然后也很久没有升级过了,现在版本还是8.10.5,而官网最新版本已经是11.2了.另一个原因是gitlab所在的这台服务器快到期了,想换一台配置更 ...
- [简洁]JavaScript中添加、移除、移动、复制、创建和查找节点元素
查找: document.getElementsByTagName通过标签名获取元素,不论有多少个都返回元素集合. document.getElementsByClassName通过类名获取元素,同上 ...
- The container 'Maven Dependencies' references non existing library '
解决办法 uncheck the option "resolve dependencies from workspace projects" from the maven tab ...
- mysql之concat concat_ws group_concat
concat.concat_ws.group_concat都可以用来连接字符串. concat和concat_ws用来连接同一行中不同列的数据,group_ws用来连接同一列的数据. 格式如下: co ...
- mysql-8.0.11安装步骤
1.下载好安装包:mysql-8.0.11-winx64.zip 2.解压到合适的目录,例如:C:\XQ\Soft\mysql-8.0.11-winx64 3.在目录下创建my.ini文件,配置bas ...
- SQL Server - CLUSTERED
CREATE TABLE dbo.t_MetricBook ( MetricSetID smallint NOT NULL, BookID smallint NOT NULL, ReportingCc ...
- Scrapy-redis 分布式
分布式:架构方式 多台真实机器+爬虫(如requests,scrapy等)+任务共享中心 多台虚拟机器(或者部分虚拟部分真实)+爬虫(如requests,scrapy等)+任务共享中心 多台容器级虚拟 ...
- 新建的亚马逊云服务器EC2 ping 不通 解决方案
在EC2配置安全组里面新加一条规则