【Java漏洞】Shiro 漏洞:SpringBoot 整合 Shiro+自定义 Realm

news/2024/12/3 7:39:51/文章来源:https://www.cnblogs.com/o-O-oO/p/18583216

在真正的项目中, 我们不会使用Shiro提供的JdbcRealm, 而是使用自定义Realm, 配合我们的MyBatis, 以及自定义表结构进行联合使用.

表结构定义

那么下面我们来定义这些表:


-- 用户信息表
CREATE TABLE `tb_users`(user_id int unsigned primary key auto_increment,username varchar(60) not null unique,password varchar(60) not null,password_salt varchar(60)
);
INSERT INTO `tb_users`(username, password) VALUES('zhangsan', '123456');
INSERT INTO `tb_users`(username, password) VALUES('lisi', '123456');
INSERT INTO `tb_users`(username, password) VALUES('wangwu', '123456');
INSERT INTO `tb_users`(username, password) VALUES('zhaoliu', '123456');
INSERT INTO `tb_users`(username, password) VALUES('chenqi', '123456');-- 角色信息表
CREATE TABLE `tb_roles`(role_id int unsigned primary key auto_increment,role_name varchar(60) not null
);
INSERT INTO `tb_roles`(role_name) VALUES('admin'); -- 系统管理员
INSERT INTO `tb_roles`(role_name) VALUES('cmanager'); -- 仓管
INSERT INTO `tb_roles`(role_name) VALUES('xmanager'); -- 销售
INSERT INTO `tb_roles`(role_name) VALUES('kmanager'); -- 客服
INSERT INTO `tb_roles`(role_name) VALUES('zmanager'); -- 行政-- 权限信息表
CREATE TABLE `tb_permissions`(permission_id int primary key auto_increment,permission_code varchar(60) not null,permission_name varchar(60)
);
INSERT INTO `tb_permissions`(`permission_code`,`permission_name`) VALUES("sys:c:save", "入库");
INSERT INTO `tb_permissions`(`permission_code`,`permission_name`) VALUES("sys:c:delete", "出库");
INSERT INTO `tb_permissions`(`permission_code`,`permission_name`) VALUES("sys:c:update", "修改");
INSERT INTO `tb_permissions`(`permission_code`,`permission_name`) VALUES("sys:c:find", "查询");INSERT INTO `tb_permissions`(`permission_code`,`permission_name`) VALUES("sys:x:save", "新增订单");
INSERT INTO `tb_permissions`(`permission_code`,`permission_name`) VALUES("sys:x:delete", "删除订单");
INSERT INTO `tb_permissions`(`permission_code`,`permission_name`) VALUES("sys:x:update", "修改订单");
INSERT INTO `tb_permissions`(`permission_code`,`permission_name`) VALUES("sys:x:find", "查询订单");INSERT INTO `tb_permissions`(`permission_code`,`permission_name`) VALUES("sys:k:save", "新增客户");
INSERT INTO `tb_permissions`(`permission_code`,`permission_name`) VALUES("sys:k:delete", "删除客户");
INSERT INTO `tb_permissions`(`permission_code`,`permission_name`) VALUES("sys:k:update", "修改客户");
INSERT INTO `tb_permissions`(`permission_code`,`permission_name`) VALUES("sys:k:find", "查询客户");-- 用户角色表
CREATE TABLE `tb_urs`(uid int not null,rid int not null
);
INSERT INTO `tb_urs` VALUES(1,1); -- 第1个用户是第1个角色 (zhangsan 是 admin 角色)
INSERT INTO `tb_urs` VALUES(2,2);
INSERT INTO `tb_urs` VALUES(3,3);
INSERT INTO `tb_urs` VALUES(4,4);
INSERT INTO `tb_urs` VALUES(5,5);-- 角色权限表
CREATE TABLE `tb_rps`(rid int not null,pid int not null
);
INSERT INTO `tb_rps` VALUES(2,1); -- 仓库管理员拥有四个权限
INSERT INTO `tb_rps` VALUES(2,2);
INSERT INTO `tb_rps` VALUES(2,3);
INSERT INTO `tb_rps` VALUES(2,4);
INSERT INTO `tb_rps` VALUES(3,5); -- 销售人员具有九个权限, 包含客服人员的权限, 以及仓库查询权限
INSERT INTO `tb_rps` VALUES(3,4);
INSERT INTO `tb_rps` VALUES(3,6);
INSERT INTO `tb_rps` VALUES(3,7);
INSERT INTO `tb_rps` VALUES(3,8);
INSERT INTO `tb_rps` VALUES(3,9);
INSERT INTO `tb_rps` VALUES(3,10);
INSERT INTO `tb_rps` VALUES(3,11);
INSERT INTO `tb_rps` VALUES(3,12);
INSERT INTO `tb_rps` VALUES(4,11); -- 客服人员具有两个权限, 查询和修改
INSERT INTO `tb_rps` VALUES(4,12);
INSERT INTO `tb_rps` VALUES(5,12); -- 行政人员具备所有查询功能
INSERT INTO `tb_rps` VALUES(5,8);
INSERT INTO `tb_rps` VALUES(5,4);

由于是自定义Realm, 所以查询数据的操作应该由我们自己手动完成, 所以这里我们应该配合我们的MyBatis进行查询数据信息.

DAO 设计

因为我们需要从数据库中拿数据, 那么我们这里可以参考一下JdbcRealm做了什么:

public class JdbcRealm extends AuthorizingRealm {protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?";protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ?";// ↑ 根据用户名查询用户信息protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?";// ↑ 查询具体用户名的角色名称protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?";// ↑ 根据角色名查询权限列表
}

当然如果我们想要自定义Realm, 我们也需要制定这些业务场景的查询语句. 为了使用我们的 MyBatis 联动 Realm, 这里我们重新建立一个干净的项目.

引入依赖:


<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId> <!-- 引入 parent --><version>2.5.3</version>
</parent>
<dependencies><dependency> <!-- 导入 shiro-spring, 会自动引入 shiro-core, shiro-web --><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.4.1</version></dependency><dependency> <!-- springboot 没有提供对 shiro 的自动配置, shiro 的自动配置需手动完成 --><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency> <!-- 引入 thymeleaf 模板引擎 --><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency> <!-- 引入 lombok --><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency> <!-- 引入 druid-spring-boot-starter, 自动配置 Druid --><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.17</version></dependency><dependency> <!-- 引入 shiro 标签依赖 --><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.1.0</version></dependency><dependency> <!-- 会自动引入 mybatis, mybatis-spring, spring-boot-starter-jdbc --><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version></dependency><dependency> <!-- 引入 mysql 扩展 --><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version></dependency><dependency> <!-- 引入 SpringBoot 测试依赖 --><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency>
</dependencies>

application.yml:

server:port: 80mybatis:type-aliases-package: com.heihu577.beanmapper-locations: classpath:mappers/*.xmlspring:datasource:druid:url: jdbc:mysql://localhost:3306/shiro2?useSSL=true&useUnicode=true&characterEncoding=utf-8username: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver

定义Bean:

package com.heihu577.bean;@Data
public class User {private Integer userId;private String username;private String password;private String passwordSalt;
}

MainApp:


@SpringBootApplication
@MapperScan("com.heihu577.mapper")
public class MainApp {public static void main(String[] args) {ConfigurableApplicationContext ioc = SpringApplication.run(MainApp.class, args);}
}

UserMapper 根据用户名查询用户信息

定义Mapper接口:

public interface UserMapper {// 根据用户名, 查询用户信息public User queryUserByUserName(@Param("username") String username);
}

随后我们创建/resources/mappers/UserMapper.xml:


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.heihu577.mapper.UserMapper"><resultMap id="user" type="User"><id property="userId" column="user_id"/><result property="username" column="username"/><result property="password" column="password"/><result property="passwordSalt" column="password_salt"/></resultMap><select id="queryUserByUserName" resultMap="user" parameterType="String">SELECT * FROM `tb_users` WHERE `username` = #{username}</select>
</mapper>

RoleMapper 根据用户名查询角色信息


public interface RoleMapper {// 根据用户名, 查询出角色名称public Set<String> queryRoleByUserName(@RequestParam("username") String username);/** 涉及到联表查询* SELECT * FROM tb_users INNER JOIN tb_urs ON tb_users.user_id = tb_urs.uid INNER JOIN tb_roles ON tb_urs.rid = tb_roles.role_id;* +---------+----------+----------+---------------+-----+-----+---------+-----------+* | user_id | username | password | password_salt | uid | rid | role_id | role_name |* +---------+----------+----------+---------------+-----+-----+---------+-----------+* |       1 | zhangsan | 123456   | NULL          |   1 |   1 |       1 | admin     |* |       2 | lisi     | 123456   | NULL          |   2 |   2 |       2 | cmanager  |* |       3 | wangwu   | 123456   | NULL          |   3 |   3 |       3 | xmanager  |* |       4 | zhaoliu  | 123456   | NULL          |   4 |   4 |       4 | kmanager  |* |       5 | chenqi   | 123456   | NULL          |   5 |   5 |       5 | zmanager  |* +---------+----------+----------+---------------+-----+-----+---------+-----------+**/
}

定义Mapper文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.heihu577.mapper.RoleMapper"><select id="queryRoleByUserName" resultType="String" parameterType="String">SELECT role_name FROM tb_usersINNER JOIN tb_urs ON tb_users.user_id = tb_urs.uidINNER JOIN tb_roles ON tb_urs.rid = tb_roles.role_idWHERE username = #{username};</select>
</mapper>

PermissionMapper 根据用户名查询权限信息

public interface PermissionMapper {public Set<String> queryPermissionByUserName(@RequestParam("username") String username);
}

定义Mapper文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.heihu577.mapper.PermissionMapper"><select id="queryPermissionByUserName" resultType="String" parameterType="String">SELECT permission_code FROM tb_usersINNER JOIN tb_urs ON tb_users.user_id = tb_urs.uidINNER JOIN tb_roles ON tb_urs.rid = tb_roles.role_idINNER JOIN tb_rps ON tb_rps.rid = tb_roles.role_idINNER JOIN tb_permissions ON tb_permissions.permission_id = tb_rps.pidWHERE username = #{username};</select>
</mapper>

自定义 Realm 设计

定义如下Realm:


public class MyRealm extends AuthorizingRealm { // 自定义 Realm 通常继承 AuthorizingRealm@Resourceprivate UserMapper userMapper;@Resourceprivate RoleMapper roleMapper;@Resourceprivate PermissionMapper permissionMapper;/*** @return 当前 Realm 名称, 可自定义名称*/@Overridepublic String getName() {return "MyRealm";}/*** 准备好授权数据, 授权数据就是当前用户的角色, 当前用户的权限信息, 所以我们只需要准备这些数据, 返回给 SecurityManager 即可.*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {String username = (String) principals.iterator().next(); // 得到已经登录成功的用户名, 实际上获取到的内容是 doGetAuthenticationInfo 方法中 new SimpleAuthenticationInfo(用户名, 用户密码, 当前Realm名称) 中的第一个参数Set<String> roles = roleMapper.queryRoleByUserName(username); // 通过用户名得到角色名称Set<String> permissions = permissionMapper.queryPermissionByUserName(username); // 通过用户名得到权限信息SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.setRoles(roles); // 将数据库查询出来的信息封装到 AuthorizationInfo 中info.setStringPermissions(permissions);return info;}/*** 准备好认证数据, 我们无需操心比对, 比对最终交给 SecurityManager, 我们只需要提供数据就可以了.* 而认证数据, 我们只需要提供 账号,密码 即可.*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// subject.login(token) 会调用到这里UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token; // 认证时, 强制转换String username = usernamePasswordToken.getUsername(); // 得到用户名User user = userMapper.queryUserByUserName(username); // 从数据库中查询该用户名, 得到该用户信息if (user != null) {// 成功从数据库中查询到用户, 我们就将用户的信息封装到 AuthenticationInfo 中, SimpleAuthenticationInfo 是 AuthenticationInfo 的子类return new SimpleAuthenticationInfo(username, user.getPassword(), this.getName());// new SimpleAuthenticationInfo(用户名, 用户密码, 当前Realm名称)}return null;}
}

我们只需要重写doGetAuthenticationInfo方法, 并且返回AuthenticationInfo类型的数据即可,AuthenticationInfo类型的数据中封装的就是用户的账号与密码信息.

重写doGetAuthorizationInfo方法, 并且返回AuthorizationInfo类型的信息,AuthorizationInfo中包含了用户的角色, 权限信息.

重写getName方法, 为我们的自定义Realm增加一个名称.

从上面可以看到的是, 我们自定义Realm成功参与了自己的数据库查询逻辑在里面, 我们使用了MyBatis从数据库中取数据, 将数据放入到返回对象中. 因为比对工作是由SecurityManager完成的, 所以我们这里只需提供数据即可, 无需加入自己的业务逻辑判断. 当然, 为了验证方便, 我们依然使用之前的login.html, index.html, UserServiceImpl, PageController, UserController进行做测试即可.

最终运行效果:

不同的用户, 不同的角色, 具有不同的权限.

Layui 优化界面

https://www.layuicdn.com/docs/v2/demo/admin.html 中拷贝代码, 并且下载 Layui 所需要的 css 与 js.

并且将 Layui 中的 CSS 与 JS 放入到/resource/static目录下, 定义/resources/templates/index.html文件内容如下:

<!DOCTYPE html>
<html xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head><base href="/"> <!-- 一定要加入这一行代码, 避免 CSS, JS 引用出错. --><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>layout 管理系统大布局 - Layui</title><link rel="stylesheet" href="./layui/css/layui.css">
</head>
<body>
<div class="layui-layout layui-layout-admin"><div class="layui-header"><div class="layui-logo layui-hide-xs layui-bg-black">layout demo</div><!-- 头部区域(可配合layui 已有的水平导航) --><ul class="layui-nav layui-layout-left"><!-- 移动端显示 --><li class="layui-nav-item layui-show-xs-inline-block layui-hide-sm" lay-header-event="menuLeft"><i class="layui-icon layui-icon-spread-left"></i></li><li class="layui-nav-item layui-hide-xs"><a href="">nav 1</a></li><li class="layui-nav-item layui-hide-xs"><a href="">nav 2</a></li><li class="layui-nav-item layui-hide-xs"><a href="">nav 3</a></li><li class="layui-nav-item"><a href="javascript:;">nav groups</a><dl class="layui-nav-child"><dd><a href="">menu 11</a></dd><dd><a href="">menu 22</a></dd><dd><a href="">menu 33</a></dd></dl></li></ul><shiro:user><ul class="layui-nav layui-layout-right"><li class="layui-nav-item layui-hide layui-show-md-inline-block"><a href="javascript:;"><img src="x" alt="图片显示错误" class="layui-nav-img"><shiro:principal/></a><dl class="layui-nav-child"><dd><a href="">Your Profile</a></dd><dd><a href="">Settings</a></dd><dd><a href="">Sign out</a></dd></dl></li><li class="layui-nav-item" lay-header-event="menuRight" lay-unselect><a href="javascript:;"><i class="layui-icon layui-icon-more-vertical"></i></a></li></ul><shiro:user></div><div class="layui-side layui-bg-black"><div class="layui-side-scroll"><!-- 左侧导航区域(可配合layui已有的垂直导航) --><ul class="layui-nav layui-nav-tree" lay-filter="test"><li class="layui-nav-item layui-nav-itemed"><a class="" href="javascript:;">仓库管理</a><dl class="layui-nav-child"><shiro:hasPermission name="sys:c:save"><dd><a href="javascript:;">入库</a></dd></shiro:hasPermission><shiro:hasPermission name="sys:c:delete"><dd><a href="javascript:;">出库</a></dd></shiro:hasPermission><shiro:hasPermission name="sys:c:delete"><dd><a href="javascript:;">更新仓库</a></dd></shiro:hasPermission><shiro:hasPermission name="sys:c:delete"><dd><a href="">查找仓库</a></dd></shiro:hasPermission></dl></li><li class="layui-nav-item layui-nav-itemed"><a class="" href="javascript:;">销售管理</a><dl class="layui-nav-child"><shiro:hasPermission name="sys:x:save"><dd><a href="javascript:;">保存订单</a></dd></shiro:hasPermission><shiro:hasPermission name="sys:x:delete"><dd><a href="javascript:;">删除订单</a></dd></shiro:hasPermission><shiro:hasPermission name="sys:x:delete"><dd><a href="javascript:;">更新订单</a></dd></shiro:hasPermission><shiro:hasPermission name="sys:x:delete"><dd><a href="">查询订单</a></dd></shiro:hasPermission></dl></li><li class="layui-nav-item layui-nav-itemed"><a class="" href="javascript:;">客户管理</a><dl class="layui-nav-child"><shiro:hasPermission name="sys:k:save"><dd><a href="javascript:;">新增客户</a></dd></shiro:hasPermission><shiro:hasPermission name="sys:k:delete"><dd><a href="javascript:;">删除客户</a></dd></shiro:hasPermission><shiro:hasPermission name="sys:k:update"><dd><a href="javascript:;">修改客户</a></dd></shiro:hasPermission><shiro:hasPermission name="sys:k:find"><dd><a href="">查询客户</a></dd></shiro:hasPermission></dl></li></ul></div></div><div class="layui-body"><!-- 内容主体区域 --><div style="padding: 15px;">内容主体区域。记得修改 layui.css 和 js 的路径</div></div><div class="layui-footer"><!-- 底部固定区域 -->底部固定区域</div>
</div>
<script src="./layui/layui.js"></script>
<script>//JSlayui.use(['element', 'layer', 'util'], function () {var element = layui.element, layer = layui.layer, util = layui.util, $ = layui.$;//头部事件util.event('lay-header-event', {//左侧菜单事件menuLeft: function (othis) {layer.msg('展开左侧菜单的操作', {icon: 0});}, menuRight: function () {layer.open({type: 1, content: '<div style="padding: 15px;">处理右侧面板的操作</div>', area: ['260px', '100%'], offset: 'rt' //右上角, anim: 5, shadeClose: true});}});});
</script>
</body>
</html>

最终运行结果:

项目打包

pom.xml文件中增加如下内容:

<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins>
</build>

随后用maven打包即可:

如果在别的机器进行部署, 这里数据库链接等信息一定要配置好, 否则项目启动不来.

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java漏洞】Shiro 漏洞:SpringBoot 整合 Shiro+IniRealm

IniRealm 如果我们想在SpringBoot中进行使用Shiro, 那么我们肯定是需要围绕如下环节进行研究. 创建 pom.xml: <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.3&l…

【Java漏洞】Shiro 漏洞:SpringBoot 整合 Shiro+JdbcRealm

JdbcRealm 其中JdbcRealm需要创建如下表结构: CREATE TABLE `users`(id int primary key auto_increment,username varchar(60) not null unique,password varchar(60) not null,password_salt varchar(20) ); -- 创建五个用户如下 INSERT INTO `users`(username, password) VA…

【Java漏洞】Shiro 漏洞:基于 Java SE 基本使用

在pom.xml文件中进行引入依赖: <dependencies><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.4.1</version></dependency> </dependencies>因为本次的Realm从文…

【Java漏洞】Shiro 漏洞:Shiro 核心组件

Shiro 的运行流程为如下:这里 Subject 的创建是由 SecurityUtils 进行创建的, 后面我们代码会给出案例, 官方给出的图如下:

【Java漏洞】Shiro 漏洞:权限管理

Shiro 的漏洞已爆出很多年, 我们只关心到了它如何触发, 有时并没有想过这个框架是干嘛的, 甚至没有分析过该框架的底层运行逻辑, 那么本篇文章, 让大家从开发者的角度, 来观察。 概念为了实现不同身份登录系统, 出现的功能模块不一样。这个需求叫做权限管理。 学生登录后, 出现…

读数据保护:工作负载的可恢复性02收集需求

收集需求1. 要点 1.1. 数据保护并不是IT里面最出彩的部分1.1.1. 让这个组织知道自己可能遭受哪些风险1.1.2. 与该组织内具有核心竞争力的IT产品通常没有什么联系1.2. 做数据保护所需的资源通常很昂贵,而且这些资源并不会体现在该组织卖给客户的最终产品里1.2.1. 没人会情愿为这…

怎么去除img之间存在的间隔缝隙?

在前端开发中,img 元素之间出现间隔缝隙通常是由几个原因造成的,以下列出常见原因及解决方法: 1. 默认的 inline-block 行为:原因: img 元素默认是 inline-block 元素。inline-block 元素会受到空格和换行符的影响,这些空格和换行符会被渲染成一个空格,从而导致元素之间出…

window10安装子系统wsl2

启用linux子系统 打开控制面板 点击程序点击 启用或关闭 Windows 功能勾选 适用于 Linux 的 Windows 子系统 然后点击确定[Haima的博客] http://www.cnblogs.com/haima/

免费实时翻译软件-MTtranslator

MTtranslator 基于win11的实时字幕(Live Captions),利用本地大模型(Helsinki-NLP/opus-mt-en-zh)实现实时翻译功能。功能特点仅支持英文到简体中文翻译该应用专为实时字幕翻译设计,支持从英文到简体中文的转换。离线操作翻译完全离线进行,保证隐私安全。但翻译质量仅供参…

Educational Codeforces Round 172 (Rated for Div. 2)

A. Greedy Monocarp题目大意:给你n个箱子,每个箱子有ai枚硬币,现在有一个人会进行若干次操作:每次拿走硬币最多的箱子,直到他的硬币总和大于等于k。 你可以在一些箱子内增加一些硬币,使得这个人拿走的硬币数量最小,问你最少需要加多少枚硬币。思路: 看数据范围,ai<…

HCIP-15 BGP路由反射器

为解决IBGP水平分割问题可以采用全互联的IBGP连接,但是该方式需要维护大量的IBGP对等体关系,为此可以部署RR来减少IBGP对等体关系的数量。 RR的设定打破了IBGP水平分割规则,为了防止路由环路产生,BGP增加了Originator_ID、Cluster_ID两个路径属性。目录中转AS中的IBGP问题路…

财务知识-期末常用会计分录

财务知识-期末常用会计分录