升级到 Asp.Net Core 2.0 (2017/08/29 更新)

为什么使用 Jwt

最近,移动开发的劲头越来越足,学校搞的各种比赛都需要用手机 APP 来撑场面,所以,作为写后端的,很有必要改进一下以往的基于 Session 的身份认证方式了,理由如下:

  1. 移动端经常要保持长时间(1 到 2 星期)在线,但是 Session 却不好在服务端保存这么久,虽然可以持久化到数据库,但是还是挺费资源
  2. 移动端往往不是使用的网页技术,所以藏在 Cookie 里面的 SessionId 不是很方便的传递给服务端
  3. 服务端暴露给客户端的接口往往是 RESTful 风格的,这是一种无状态的 API 风格,所以身份认证的方式最好也是无状态的才好

所以我选择了使用 Jwt (Json Web Token) 这个技术。Jwt 是一种无状态的分布式的身份验证方式,与 Session 相反,Jwt 将用户信息存放在 Token 的 payload 字段保存在客户端,通过 RSA 加密的方式,保证数据不会被篡改,验证数据有效性。

下面是一个使用 Jwt 的系统的身份验证流程:

可以看出,用户的信息保存在 Token 中,而 Token 分布在用户的设备中,所以服务端不再需要在内存中保存用户信息了

身份认证的 Token 传递时以一种相当简单的格式保存在 header 中,方便客户端对其进行操作

Jwt 简介

Jwt 形式的 token 一般分为 3 个部分,分别是 Header,Payload,Signature,这三个部分使用 . 分隔。其中前两部分使用 Base64 编码,未经加密处理,第三个部分使用 RSA 加密。

所以一个 Jwt 看起来大概是这个样子:

header.payload.signature

下面是一个真实的 Jwt:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InplZWtvIiwicm9sZSI6IiIsIm5hbWVpZCI6MSwianRpIjoiNjNjN2Q3OWY2N2VhMDhjYjRiYzNjMmNkOTJiY2JkNTgiLCJuYmYiOjE0OTQ0MDMwMjQsImV4cCI6MTQ5NTAwNzgyMywiaWF0IjoxNDk0NDAzMDI0LCJpc3MiOiJUZXN0SXNzdWVyIiwiYXVkIjoiVGVzdEF1ZGllbmNlIn0.V7Mfi3FGOTLYV0O5DmOWju7LkDJwZNO6HZN19CHb3ekYxcoVbP51YjYAr0fUHc3RPIp3gxITzziHY-07xZ2swCaV0K-hiF5IbwpDuvyxsnlgaRxS94wKDGKSJkArC82KukCtm7IuFBxnNr6kxe7tGcebVhqtaqgnxEUg5lKtDtVI85kd17YtzBp9Vxnc3Ie0r-6KPgUa2HacCf2Pc3hkvY7tZdWZ6ininZlZ-EbcyZI2KTx-vOqdK63MS2JYSw7W2qwf89tsRsORwbB2P4dOBBFK8YSXJpeyGeJWFEMjAMkiH3AeMmW2w_H7r_6Pn-jh5gozzBei4JoHTU6RVDUg1A

Header

Header 部分一般用来记录加密算法跟 Token 类型

举个例子:

{
"alg": "HS256",
"typ": "JWT"
}

Payload

Payload 存放的是一些不敏感的用户数据,因为这一部分仅仅只是使用 Base64 加密,所以不应该用来保存用户的密码之类的信息。

一个例子:

{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}

Signature

这一部分是 Jwt 最重要的部分,使用 header 中记录的算法进行了加密,加密方式如下:

HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

所以这个部分可以用来保证用户信息不被篡改,起到验证用户身份的作用

在开发过程中,可以访问 https://jwt.io 来调试 Token

当然,为了更快的访问速度,还可以使用 这个网站

在 ASP.NET Core 中使用 Jwt

因为 Jwt 本身的特点,所以用来签发 Token 的服务器可以跟应用服务器不是同一台,这样就可以搞微服务之类的东西(反正我不懂。。。)

因此,在这篇博客中,将会创建两个 Web 应用:

  • JwtIssuer // 用来签发 Token
  • JwtAudience // 应用服务器,提供 API 的

首先来搭建我们的 Token 签发服务器吧!

签发第一个 Token

由于要使用到 RSA 加密,所以先创建一个辅助类来帮助简化调用:

RSAUtils.cs

using System.IO;
using System.Security.Cryptography;
using Newtonsoft.Json; namespace JwtUtils
{
public static class RSAUtils
{
/// <summary>
/// 从本地文件中读取用来签发 Token 的 RSA Key
/// </summary>
/// <param name="filePath">存放密钥的文件夹路径</param>
/// <param name="withPrivate"></param>
/// <param name="keyParameters"></param>
/// <returns></returns>
public static bool TryGetKeyParameters(string filePath, bool withPrivate, out RSAParameters keyParameters)
{
string filename = withPrivate ? "key.json" : "key.public.json";
keyParameters = default(RSAParameters);
if (Directory.Exists(filePath) == false) return false;
keyParameters = JsonConvert.DeserializeObject<RSAParameters>(File.ReadAllText(Path.Combine(filePath, filename)));
return true;
} /// <summary>
/// 生成并保存 RSA 公钥与私钥
/// </summary>
/// <param name="filePath">存放密钥的文件夹路径</param>
/// <returns></returns>
public static RSAParameters GenerateAndSaveKey(string filePath)
{
RSAParameters publicKeys, privateKeys;
using (var rsa = new RSACryptoServiceProvider(2048))
{
try
{
privateKeys = rsa.ExportParameters(true);
publicKeys = rsa.ExportParameters(false);
}
finally
{
rsa.PersistKeyInCsp = false;
}
}
File.WriteAllText(Path.Combine(filePath, "key.json"), JsonConvert.SerializeObject(privateKeys));
File.WriteAllText(Path.Combine(filePath, "key.public.json"), JsonConvert.SerializeObject(publicKeys));
return privateKeys;
}
}
}

这个工具类能够帮助我们生成 RSA 密钥,并把生成的私钥跟公钥保存在两个文件中,还能从文件中读取密钥。

然后定义一个数据类,用来帮助我们在应用的各个地方获取加密相关的信息:

JWTTokenOptions.cs

using Microsoft.IdentityModel.Tokens;

namespace JwtUtils
{
public class JWTTokenOptions
{
public string Audience { get; set; }
public RsaSecurityKey Key { get; set; }
public SigningCredentials Credentials { get; set; }
public string Issuer { get; set; }
}
}

接下来在 Startup.cs 中配置 Jwt 的加密选项:

public void ConfigureServices(IServiceCollection services)
{
// 省略了其他的东西 // 从文件读取密钥
string keyDir = PlatformServices.Default.Application.ApplicationBasePath;
if (RSAUtils.TryGetKeyParameters(keyDir, true, out RSAParameters keyParams) == false)
{
keyParams = RSAUtils.GenerateAndSaveKey(keyDir);
}
_tokenOptions.Key = new RsaSecurityKey(keyParams);
_tokenOptions.Issuer = "TestIssuer"; // 签发者名称
_tokenOptions.Credentials = new SigningCredentials(_tokenOptions.Key, SecurityAlgorithms.RsaSha256Signature);
// 添加到 IoC 容器
services.AddSingleton(_tokenOptions); services.AddMvc();
}

接下来创建一个控制器,用来提供签发 Token 的 API

TokenController.cs

namespace JwtIssuer.Controllers
{
[Route("api/[controller]")]
public class TokenController : Controller
{
private readonly JWTTokenOptions _tokenOptions;
private readonly AuthDbContext _dbContext; public TokenController(JWTTokenOptions tokenOptions, AuthDbContext dbContext)
{
_tokenOptions = tokenOptions;
_dbContext = dbContext;
} /// <summary>
/// 生成一个新的 Token
/// </summary>
/// <param name="user">用户信息实体</param>
/// <param name="expire">token 过期时间</param>
/// <param name="audience">Token 接收者</param>
/// <returns></returns>
private string CreateToken(User user, DateTime expire, string audience)
{
var handler = new JwtSecurityTokenHandler();
string jti = audience + user.Username + expire.GetMilliseconds();
jti = jti.GetMd5(); // Jwt 的一个参数,用来标识 Token
var claims = new[]
{
new Claim(ClaimTypes.Role, user.Role ?? string.Empty), // 添加角色信息
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(), // 用户 Id ClaimValueTypes.Integer32),
new Claim("jti",jti,ClaimValueTypes.String) // jti,用来标识 token
};
ClaimsIdentity identity = new ClaimsIdentity(new GenericIdentity(user.Username, "TokenAuth"), claims);
var token = handler.CreateEncodedJwt(new SecurityTokenDescriptor
{
Issuer = "TestIssuer", // 指定 Token 签发者,也就是这个签发服务器的名称
Audience = audience, // 指定 Token 接收者
SigningCredentials = _tokenOptions.Credentials,
Subject = identity,
Expires = expire
});
return token;
} /// <summary>
/// 用户登录
/// </summary>
/// <param name="user">用户登录信息</param>
/// <param name="audience">要访问的网站</param>
/// <returns></returns>
[HttpPost("{audience}")]
public IActionResult Post([FromBody]User user, string audience)
{
DateTime expire = DateTime.Now.AddDays(7); // 在这里来验证用户的用户名、密码
var result = _dbContext.Users.First(u => u.Username == user.Username && u.Password == user.Password);
if (result == null)
{
return Json(new { Error = "用户名或密码错误" });
}
return Json(new { Token = CreateToken(result, expire, audience) });
}
}
}

现在,访问这个 API(http://localhost:port/api/token/TestAudience) 就可以获取用户的 Token 了

在应用服务器验证 Token

在 Startup.cs 中注册 Jwt 相关的服务:

public void ConfigureServices(IServiceCollection services)
{
// 省略了其他的内容 // 从文件读取密钥
string keyDir = PlatformServices.Default.Application.ApplicationBasePath;
if (RSAUtils.TryGetKeyParameters(keyDir, false, out RSAParameters keyparams) == false)
{
_tokenOptions.Key = default(RsaSecurityKey);
}
else
{
_tokenOptions.Key = new RsaSecurityKey(keyparams);
}
_tokenOptions.Issuer = "TestIssuer"; // 设置签发者
_tokenOptions.Audience = "TestAudience"; // 设置签收者,也就是这个应用服务器的名称
_tokenOptions.Credentials = new SigningCredentials(_tokenOptions.Key, SecurityAlgorithms.RsaSha256Signature); services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build());
}); // Add framework services.
services.AddMvc();
}

然后在 Startup.cs 添加 Jwt 认证中间件:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// 省略了其他的内容 app.UseJwtBearerAuthentication(new JwtBearerOptions
{
TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = _tokenOptions.Key,
ValidAudience = _tokenOptions.Audience, // 设置接收者必须是 TestAudience
ValidIssuer = _tokenOptions.Issuer, // 设置签发者必须是 TestIssuer
ValidateLifetime = true
}
}); }

接着随便创建一个 API 控制器

namespace JwtAudience.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
[Authorize]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
} }
}

首先编译一下应用服务器,但是不要急着运行。因为应用服务器验证 Token 是需要公钥的,所以现在去之前的签发服务器的 build 目录

可以看到生成了两个json文件,将其中的 key.public.json 拷贝到应用服务器的对应的目录下面,然后运行应用服务器。

如果我们直接访问应用服务器的 API,就会被挡在外面:

所以现在去把之前拿到的 token 复制出来,然后给这个请求加个请求头——Authorization

值是 Bearer 你的Token



这样,基本的身份验证就完成了~

有兴趣的话还可以把这个 Token 放在前面提到的用来调试 Jwt 网站上,我的 Token 的解析结果是:

这里面的 iss 指的就是签发者,aud 指的是接收者,对于我们的应用服务器来说,这两个参数错了任意一个都将无法通过验证(这里就不演示了,等会儿会有测试代码~)

真的足够安全?

至此,我们已经把 Jwt 的身份认证基本实现了,但是仔细想想,却发现存在一个很严重的问题————用户的 Token 在过期时间之内根本无法手动设置失效,随之而来的还有重放攻击等等问题!

Jwt官方也没有提供很好的应对方法,现在就只有一条路可以走,就是把失效的 Token 加入黑名单。只要能够让 Token 失效,之后应对这些安全问题就只是策略上的选择。

在 Jwt 的官方说明中,jti 这个参数就是用来标识 Token 的。所以,让一个 Token 失效只需要把这个 Token 中的 jti 加入应用服务器的数据库的黑名单就好了。

得益于微软对 Identity 良好的设计,我们可以很容易的拓展默认的 Jwt 认证规则

首先创建一个 ValidJtiRequirement 类

public class ValidJtiRequirement : IAuthorizationRequirement
{
}

嗯,他的结构就是这么简单。。。

然后创建一个用来验证这个 Requirement 的 ValidJtiHandler

public class ValidJtiHandler : AuthorizationHandler<ValidJtiRequirement>
{
private readonly AudienceDbContext _dbContext; public ValidJtiHandler(AudienceDbContext dbContext)
{
_dbContext = dbContext;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ValidJtiRequirement requirement)
{
// 检查 Jti 是否存在
var jti = context.User.FindFirst("jti")?.Value;
if (jti == null)
{
context.Fail(); // 显式的声明验证失败
return Task.CompletedTask;
} // 检查 jti 是否在黑名单
var tokenExists = _dbContext.BlackRecords.Any(r => r.Jti == jti);
if (tokenExists)
{
context.Fail();
}
else
{
context.Succeed(requirement); // 显式的声明验证成功
}
return Task.CompletedTask; }
}

最后,稍微的修改一下注册服务时的代码

services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.AddRequirements(new ValidJtiRequirement()) // 添加上面的验证要求
.Build());
});
// 注册验证要求的处理器,可通过这种方式对同一种要求添加多种验证
services.AddSingleton<IAuthorizationHandler, ValidJtiHandler>();

最后再来提供一个使 Token 失效的 API

namespace JwtAudience.Controllers
{
[Route("api/[controller]")]
public class TokenController : Controller
{
private readonly AudienceDbContext _dbContext; public TokenController(AudienceDbContext dbContext)
{
_dbContext = dbContext;
} [HttpGet]
public IActionResult Get() => Json(_dbContext.BlackRecords); /// <summary>
/// 使用户的 Token 失效
/// </summary>
/// <returns></returns>
[Authorize("Bearer")]
[HttpDelete]
public IActionResult Delete()
{
// 从 payload 中提取 jti 字段
var jti = User.FindFirst("jti")?.Value;
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (jti == null)
{
HttpContext.Response.StatusCode = 400;
return Json(new { Result = false });
}
// 把这个 jti 加入数据库
_dbContext.BlackRecords.Add(new BlackRecord { Jti = jti, UserId = userId });
_dbContext.SaveChanges();
return Json(new {Result = true});
}
}
}

这里需要注意的是,因为拓展了默认的验证策略,所以需要在 Authorize 这个特性钦定使用 Bearer 策略:

[Authorize("Bearer")]

但是这样就容易在编码的时候出现拼写错误,所以来创建一个继承自这个特性的BearerAuthorize类。

namespace JwtAudience
{
/// <summary>
/// Jwt 验证
/// </summary>
public class BearerAuthorizeAttribute : AuthorizeAttribute
{
public BearerAuthorizeAttribute() : base("Bearer") { }
}
}

现在我们就可以使用[BearerAuthorize]来替代[Authorize]

至此,使 token 失效的能力就具备了。

然后附带一份测试代码,用来检验认证过程是否符合我们的预期:

https://coding.net/u/zeeko/p/JwtApplication/git/blob/master/Test/Test.cs


升级到 Asp.Net Core 2.0 (2017/08/29)

花了一天时间来把项目升级到 2.0,并不是因为 API 变化很大,而是之前的 bug 有些多,修起来有些慢。

首先要升级 Program.cs 里面的 Main 函数:

public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
} public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}

看起来更简短了一些。

接下来升级认证配置,按照官方的说明,所有的 app.UseXxxAuthentication 方法都变成了 service.AddAuthentication(XxxSchema).AddXxx(),所以改动不是很大:

JwtIssuer/Startup.cs/ConfigureServices

services.AddAuthentication().AddJwtBearer(jwtOptions =>
{
jwtOptions.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = _tokenOptions.Key,
ValidAudience = _tokenOptions.Audience,
ValidIssuer = _tokenOptions.Issuer,
ValidateLifetime = true
};
});

JwtAudience/Startup.cs/ConfigureServices

services.AddAuthentication().AddJwtBearer(jwtOptions =>
{
jwtOptions.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = _tokenOptions.Key,
ValidAudience = _tokenOptions.Audience,
ValidIssuer = _tokenOptions.Issuer,
ValidateLifetime = true
};
});

至此,需要升级的地方就修改好了,但是到目前为止还是无法运行,因为有些在 1.0 里面没有严格检验的地方开始报错了。

第一个地方是 ValidJtiHandler,之前在注册的时候,生命周期选的是单例并没有报错,但是因为这个类依赖了一个生命周期是 Scoped 的对象—— AudienceDbContext,这会引发一个异常,解决方法是把 ValidJtiHandler 也改成 Scoped:

services.AddScoped<IAuthorizationHandler, ValidJtiHandler>();

第二个地方是 RSAParameters 在 2.x 里面,它的私钥属性不能被 Json.Net 序列化,解决方法也很简单,加一个对应的类似 DTO 的类:

class RsaParameterStorage
{
public byte[] D { get; set; }
public byte[] DP { get; set; }
public byte[] DQ { get; set; }
public byte[] Exponent { get; set; }
public byte[] InverseQ { get; set; }
public byte[] Modulus { get; set; }
public byte[] P { get; set; }
public byte[] Q { get; set; }
}

然后在导出私钥前将 RSAParameters 映射成一个 RsaParameterStorage对象,然后使用 Json.Net 来序列化,映射使用的是我自己写的一个 Mapper(所以升级项目只花了几十分钟,调教 Mapper 花了一天),代码更改如下:

// 转换成 json 字符串
static string ToJsonString(this RSAParameters parameters)
{
var content = parameters.Map().To<RsaParameterStorage>();
return JsonConvert.SerializeObject(content);
} // 从文件中读取
keyParameters = JsonConvert.DeserializeObject<RsaParameterStorage>(File.ReadAllText(filePath)).Map().To<RSAParameters>();

ASP.NET Core 使用 JWT 搭建分布式无状态身份验证系统的更多相关文章

  1. 教你如何实现微信小程序与.net core应用服务端的无状态身份验证

    随着.net core2的发布,越来越多人使用.net core2开发各种应用服务端,下面我就结合自己最近开发的一款小程序,给大家分享下,怎么使用小程序登录后,小程序与服务端交互的权限控制. .net ...

  2. 在ASP.NET Core 2.0中使用Facebook进行身份验证

    已经很久没有更新自己的技术博客了,自从上个月末来到天津之后把家安顿好,这个月月初开始找工作,由于以前是做.NET开发的,所以找的还是.NET工作,但是天津这边大多还是针对to B(企业)进行定制开发的 ...

  3. ASP.NET Core的无状态身份认证框架IdentityServer4

    Identity Server 4是IdentityServer的最新版本,它是流行的OpenID Connect和OAuth Framework for .NET,为ASP.NET Core和.NE ...

  4. ASP.NET Core 基于JWT的认证(一)

    ASP.NET Core 基于JWT的认证(一) Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计 ...

  5. asp.net core 集成JWT(一)

    [什么是JWT] JSON Web Token(JWT)是目前最流行的跨域身份验证解决方案. JWT的官网地址:https://jwt.io/ 通俗地来讲,JWT是能代表用户身份的令牌,可以使用JWT ...

  6. asp.net core 集成JWT(二)token的强制失效,基于策略模式细化api权限

    [前言] 上一篇我们介绍了什么是JWT,以及如何在asp.net core api项目中集成JWT权限认证.传送门:https://www.cnblogs.com/7tiny/p/11012035.h ...

  7. ASP.NET Core 使用 Redis 实现分布式缓存:Docker、IDistributedCache、StackExchangeRedis

    ASP.NET Core 使用 Redis 实现分布式缓存:Docker.IDistributedCache.StackExchangeRedis 前提:一台 Linux 服务器.已安装 Docker ...

  8. ASP.NET Core 基于JWT的认证(二)

    ASP.NET Core 基于JWT的认证(二) 上一节我们对 Jwt 的一些基础知识进行了一个简单的介绍,这一节我们将详细的讲解,本次我们将详细的介绍一下 Jwt在 .Net Core 上的实际运用 ...

  9. Asp.Net Core基于JWT认证的数据接口网关Demo

    近日,应一位朋友的邀请写了个Asp.Net Core基于JWT认证的数据接口网关Demo.朋友自己开了个公司,接到的一个升级项目,客户要求用Aps.Net Core做数据网关服务且基于JWT认证实现对 ...

随机推荐

  1. MySQL表-----查询------

    ``模糊查询4.2.1[使用like进行模糊查询]注意:like运算副只用于字符串,所以仅与char和varchar数据类型联合使用例:select * from a where name like ...

  2. 基于Intranet的零件库管理信息系统设计--part01

    好吧,临近毕业的我,毕业设计还没开始做呢.时间不等人,再过两个月就要答辩了,我得开始做我的毕设了,虽然我现在还没能力完全把毕设做出来,但总得先迈出第一步吧.今天先做一小部分. 话不多说,先来看我得毕业 ...

  3. 老李分享:MySql的insert语句的性能优化方案

    老李分享:MySql的insert语句的性能优化方案   性能优化一直是测试人员比较感兴趣的内容,poptest在培训学员的时候也加大了性能测试调优的方面的内容,而性能优化需要经验的积累,经验的积累依 ...

  4. 手机自动化测试:appium问题解决

    手机自动化测试:appium问题解决   Appium遇到问题: 问题一:问题org.openqa.selenium.remote.UnreachableBrowserException: Could ...

  5. 手机自动化测试:appium源码分析之bootstrap十

    手机自动化测试:appium源码分析之bootstrap十   poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣, ...

  6. C# 快速高效率复制对象另一种方式 表达式树

    1.需求 在代码中经常会遇到需要把对象复制一遍,或者把属性名相同的值复制一遍. 比如: public class Student { public int Id { get; set; } publi ...

  7. Java ---理解MVC架构

    之间的文章,我们主要是介绍了jsp的相关语法操作,我们可以通过请求某个jsp页面,然后由相对应的servlet实例给我们返回html页面.但是在实际的项目中,我们很少会直接的请求某个页面,一般都是请求 ...

  8. c++中的namespace(附程序运行图)

    实验于华中农业大学逸夫楼2017.3.10 namespace中文意思是命名空间或者叫名字空间,传统的C++只有一个全局的namespace,但是由于现在的程序的规模越来越大,程序的分工越 来越细,全 ...

  9. asp.net core 编译mvc,routing,security源代码进行本地调试

    因为各种原因,需要查看asp.net core mvc的源代码来理解运行机制等等,虽说源代码查看已经能很好的理解了.但是能够直接调试还是最直观的.所有就有了本次尝试. 因调试设置源代码调试太辍笔,所以 ...

  10. iptables配置详解

    iptables主要参数 -A 向规则链中添加一条规则,默认被添加到末尾 -T指定要操作的表,默认是filter -D从规则链中删除规则,可以指定序号或者匹配的规则来删除 -R进行规则替换 -I插入一 ...