2015年7月14日星期二

全部用startssl生成的证书,配置Apache使其支持SSL - yJken的博客

本邮件内容由第三方提供,如果您不想继续收到该邮件,可 点此退订
全部用startssl生成的证书,配置Apache使其支持SSL - yJken的博客  阅读原文»

Apache的编译安装见这篇:

http://www.cnblogs.com/yjken/p/3921840.html

网上查阅了一大批资料,得知自己生成的证书是会被浏览器提示“证书不安全”的,我也就没有去生成证书,而是直接去了startssl 申请了一个免费的证书,据说startssl也是全球唯一的一个可以申请免费ssl证书的地方,并且是被主流浏览器(firefox,chrome,IE,safari)认可的,所以,对于安全性要求不高的情景,免费的证书已经完全满足需求了,当然,如果你的安全性要求比较高,startssl也是有更高级的安全证书支持的,不过,这些更安全的证书可就是收费的了。

简单说下申请证书的步骤:

1. 去www.startssl.com注册一个帐号

2. 因为startssl不是那种常见的帐号密码登录的模式,而是不需要密码,甚至也不需要帐号,而是安装一个startssl的个人证书到你的浏览器上,下次进入这个网站,直接在弹出的证书上点确认就可以了,所以,第一次注册后,根据它的引导,会生成一张个人证书,安装至你的浏览器上,这个证书只是用来登录startssl网站用的,没有其它用处。

3. 验证邮箱,验证域名,startssl需要确认你确实是某域名的所有者,所以它会首先要求验证域名的所有权,验证步骤也很简单,假设你的域名是abc.com , 那么它会将验证字符串发送到诸如webmaster@abc.com这样的邮箱,你打开这个邮箱,把验证字符串拷贝并粘贴过去,就算成功了,很简单。

4. 生成证书,首先会生成一张顶级域证书,再生成一张二级域证书(整个步骤根据它的向导来,就可以了),期间有个密码,是必须要记住的,不然生成的证书,就会没法用了。

最终会得到下面几个文件:

ca.pem , ssl.crt , ssl.key , sub.class1.server.ca.pem

其实这张ssl.key是加密过的,直接配置到apache上也是可以的,只是这样很不方便,因为,每次启动apache都必须输入一遍密码,所以,你可以直接在线解密这个私钥的.

这样生成的私钥,配置到Apache上,就不需要启动Apaceh时输入密码短语了。

配置如下:

<VirtualHost *:443>
SSLEngine On
SSLCertificateFile certificate/ssl.crt
SSLCertificateKeyFile certificate/ssl.key
SSLCertificateChainFile certificate/sub.class1.server.ca.pem
SSLCACertificateFile certificate/ca.pem

ServerAdmin jken@abc.com
DocumentRoot
"~/www/abc"
ServerName www.abc.com:
443
ErrorLog
"logs/error/abc_error_log"
CustomLog
"logs/custom/abc_log" common
</VirtualHost>

主要是这几行:

SSLEngine On
SSLCertificateFile certificate/ssl.crt
SSLCertificateKeyFile certificate/ssl.key
SSLCertificateChainFile certificate/sub.class1.server.ca.pem
SSLCACertificateFile certificate/ca.pem

这个服务器名称需要增加443,端口号:

ServerName www.abc.com:443

这样,配置就完成了,不过,如果你这个时候重启Apache,你会发现用https访问不了网站,我当时配置的时候,就忽略了这一点,结果找了一大堆资料,还是不行,没办法,只好自己研究了,去服务器上,用netstat命令查看,发现,完全没有开启433监听端口,也就是说Apache的配置上还漏了一条,我在“Listen 80”下面,又加了一行“Listen 443”

Listen 80
Listen
443

这样,再重启Apache,使用https方式,打开网站,畅通无阻,问题解决。

对了,如果你的Apache不是我的安装方式,可能还会出问题,就是,你可能压根就没有安装ssl,我在安装Apache的时候,是有这条的:

with-ssl=/usr/include/openssl

这样,安装了ssl功能,如果不是编译成静态的,而是动态库的话,还得去掉下面这行的注释:

LoadModule ssl_module modules/mod_ssl.so

好了,这样,一般是不会有什么问题的了。


本文链接:全部用startssl生成的证书,配置Apache使其支持SSL,转载请注明。

追根溯源:EntityFramework 实体的状态变化 - 田园里的蟋蟀  阅读原文»

阅读目录:

  • 1. 应用场景
  • 2. 场景测试
  • 3. 问题分析
  • 4. 追根溯源
  • 5. 简要总结

1. 应用场景

首先,应用程序使用 EntityFramework,应用场景中有两个实体 S_Class(班级)和 S_Student(学生),并且是一对多的关系,即一个班级对应多个学生,业务存在这样的需求,学生要更换班级,所以我们要对某一个学生对应的班级值进行更改,就这么简单,这也是我们应用程序最常遇到的一个场景,即关联实体的更改。但我在更改中遇到了一些"奇怪"问题,修改学生对应的班级值后,在持久化保存的时候,EF 把修改的班级对象作为新对象添加了。

问题先抛在一边,我们看一下应用示例代码:

public class SchoolDbContext : DbContext
{
public SchoolDbContext()
:base("name=Demo")
{
this.Configuration.LazyLoadingEnabled = false;
Database.SetInitializer<SchoolDbContext>(null);
}

public DbSet<S_Student> S_Students { get; set; }
public DbSet<S_Class> S_Classs { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<S_Class>()
.HasKey(n => n.ClassId);

modelBuilder.Entity<S_Student>()
.HasKey(n => n.StudentId);
modelBuilder.Entity<S_Student>()
.HasRequired(w => w.S_Class);

base.OnModelCreating(modelBuilder);
}
}

public class S_Class
{
public int ClassId { get; set; }
public int Name { get; set; }
}

public class S_Student
{
public int StudentId { get; set; }
public int ClassId { get; set; }
public string Name { get; set; }
public virtual S_Class S_Class { get; set; }
}

需要注意的是,S_Class 和 S_Student 的关联配置是通过 .HasRequired(w => w.S_Class),如果我们把 Database.SetInitializer<SchoolDbContext>(null); 代码注释掉(不强制映射到数据库),在应用程序运行的时候,EF 会自动在 S_Student 表中生成一个 S_Class_Id 外键,如果我们把这个外键删掉(看起来很丑,我们想使用 ClassId),S_Class 和 S_Student 中的主键由 ClassId 和 StudentId,修改为 Id 和 Id,然后重新运行应用程序(Database.SetInitializer 代码取消注释),这时候就会抛出这样一个异常:

异常详情:{"列名 'S_Class_Id' 无效。"},其实解决上面这个异常问题,由很多的方法,如果我们不想使用 EF 自动生成的 S_Class_Id 外键,而是使用 ClassId,我们可以在 OnModelCreating 中添加一个外键字段映射就可以了,还有一种更好的方法是,就是上面的示例代码,因为在 S_Student 中定义了关联(或称之为导航)属性 S_Class,这时候 EF 在映射数据库的时候,会自动找 S_Class 中的主键,以及 S_Student 所对应的外键,如果找不到就会自动生成(在强制映射到数据库配置取消的情况下),那 EF 是怎么进行查找外键的呢?就是通过 S_Class 中的主键 ClassId,然后根据它的命名,去 S_Student 中找对应的属性字段,最后就找到了 S_Student 实体下的 ClassId,然后就把它当作了所谓的"外键"(数据库中并未映射),有人会说,这有什么用呢?看这样一段代码:

using (var context = new SchoolDbContext())
{
var student = context.S_Students.Include(s => s.S_Class).FirstOrDefault();
}

其实,ClassId 这个所谓的外键,就是我们在 Include 查询的时候会起作用,还有就是对 S_Student 实体的 S_Class,进行赋值的时候,会自动映射到 ClassId,最后的表现就是:数据库中 S_Students 表的 ClassId 值更改了。

有点扯远了,完全和主题不相关,关于这个问题,我最后想说的是:好的实体命名设计,可以帮你减少一些不必要的问题出现,并且省掉很多的工作

2. 场景测试

回到正题,针对一开始描述的问题,我们编写下测试代码:

static void Main(string[] args)
{
using (var context = new SchoolDbContext())
{
var s_Student = context.S_Students.Include(s => s.S_Class).FirstOrDefault(c => c.StudentId == 1);
var s_Class = GetSingleClass(2);
s_Student.Name = "xishuai";
s_Student.S_Class = s_Class;
context.SaveChanges();
}
}

public static S_Class GetSingleClass(int id)
{
using (var context = new SchoolDbContext())
{
return context.S_Classs.FirstOrDefault(c => c.ClassId == id);
}
}

测试代码所表达的意思是:取出一个 StudentId 为 1 的 s_Student 对象,然后再取出一个 ClassId 为 2 的 s_Class 对象,并将它赋值给 s_Student 的 S_Class 属性,如果运行正常的话,数据库中 S_Students 表的 ClassId 字段值,会更新为 2。

但测试的结果,却和我们预想的大相径庭,S_Classs 表中新增了一条数据,然后 S_Students 表的 ClassId 值为新增的 S_Classs 主键值,我们想对 S_Students 进行修改,最后却变成了对 S_Classs 的新增。

3. 问题分析

看了上面的描述,我想有人应该可以看出问题缘由,没错,就是 S_Classs 的获取和 S_Students 的更新,不在同一个 DbContext 中。

一开始,我压根没从这个问题上想,而是认为是 EF 的映射配置出了问题,走了很多弯路,因为我太相信 EF 了,s_Class 对象是从数据库中获取的,那么我赋值更新 S_Students 中的 S_Class 对象,s_Class 主键是存在的,在 EF SaveChanges 持久化保存的时候,应该会根据 s_Class 的 ClassId,去找这个 S_Class 是否存在,如果存在的话,S_Students 中的 S_Class 则相应更新,但结果显然不是这样,EF 并没有根据外键去找这个对象是否存在,而是通过上下文中对象的状态去持久化,如果切换上下文,那么对象的状态将会丢失。

来自《Understanding How DbContext Responds to Setting the State of a Single Entity》文章的一段描述:

Setting an entity to the Detached state used to be important before the Entity Framework supported POCO objects. Prior to POCO support, your entities would have references to the context that was tracking them. These references would cause issues when trying to attach an entity to a second context. Setting an entity to the Detached state clears out all the references to the context and also clears the navigation properties of the entity―so that it no longer references any entities being tracked by the context. Now that you can use POCO objects that don't contain references to the context that is tracking them, there is rarely a need to move an entity to the Detached state. We will not be covering the Detached state in the rest of this chapter.

关键句:These references would cause issues when trying to attach an entity to a second context.

你可以在上面的测试代码中,添加一行这样的代码:

Console.WriteLine(context.Entry(s_Student.S_Class).State);//输出结果:Added

既然不在一个上下文中,s_Student.S_Class 对象的状态会变成 Added,那如果我们将操作放在一个上下文中,结果会怎样呢?我们来写下测试代码:

static void Main(string[] args)
{
using (var context = new SchoolDbContext())
{
var s_Student = context.S_Students.Include(s => s.S_Class).FirstOrDefault(s => s.StudentId == 1);
var s_Class = context.S_Classs.FirstOrDefault(c => c.ClassId == 2);
s_Student.Name = "xishuai";
s_Student.S_Class = s_Class;
Console.WriteLine(context.Entry(s_Student).State);//输出结果:Modified
Console.WriteLine(context.Entry(s_Student.S_Class).State);//输出结果:Unchanged
context.SaveChanges();
}
}

先说下结果:数据库中 S_Students 表的 ClassId 字段值会更新为 2,并且 S_Classs 表不会新增数据,这是我们想要的结果,但我们发现 s_Student.S_Class).State 的值为 Unchanged,按理说,它应该为 Modified 的,那 Unchanged 是什么意思呢?从字面上就可以看到是无修改或无变化的意思,上面文章中也有对它的具体说明:

  • Unchanged: The entity already exists in the database and has not been modified since it was retrieved from the database. SaveChanges does not need to process the entity.

说白了就是,如果实体的状态是 Unchanged,那么 SaveChanges 将不进行更新,从 EF 的源码中就可以看出(稍后说下),既然 s_Student.S_Class 的状态是 Unchanged,那为什么 EF 又对它进行更新了呢?一个问题还没解决,又引出了另一个问题。

只做测试,很显然不能了解更多内容,如果想刨根问底的话,我们就必须从 EF 的源码下手(EF7):https://github.com/aspnet/EntityFramework

4. 追根溯源

首先,有一个疑问:实体中的属性,它的作用,其实说白了,就是存储数据值的,那属性值的对象状态变化是如何记录的呢?从测试代码中,可以看出,我们并没有对属性状态做一些修改,而 context.Entry(s_Student).State 却可以得到 Modified 的状态值,很显然,答案就在 context.Entry 中,我们找下 DbContext.Entry 的相关源码,发现了下面的一些东西:

public virtual EntityEntry<TEntity> Entry<TEntity>([NotNull] TEntity entity) where TEntity : class
{
Check.NotNull(entity, nameof(entity));
TryDetectChanges(GetStateManager());
return EntryWithoutDetectChanges(entity);
}

TryDetectChanges 很直观,就是去发现实体的一些修改(好的命名是多么的重要啊!!!),最后,顺藤摸瓜,我们又找到了下面的一些东西:

public virtual void DetectChanges(InternalEntityEntry entry)
{
DetectPropertyChanges(entry);
DetectRelationshipChanges(entry);
}

private void DetectPropertyChanges(InternalEntityEntry entry)
{
var entityType = entry.EntityType;

if (entityType.HasPropertyChangedNotifications())
{
return;
}

var snapshot = entry.Tr

阅读更多内容

没有评论:

发表评论