1、环境说明
ORACLE 客户端版本 |
11.2.0.1 |
ORACLE 服务端版本 |
12.2.0.1 |
2、异常现象
客户端(下文也称为Cp)访问服务端(Sp),报了一个错误:
Figure 1
以错误码为关键字在网上查找原因,有网友建议把服务器的sqlnet.ora文件中的SQLNET.ALLOWED_LOGON_VERSION_SERVER参数 和 SQLNET.ALLOWED_LOGON_VERSION_CLIENT参数改为8(原值为12)。如下图所示:
Figure 2
改过之后重新连接,糟糕,出现了新的错误:
Figure 3
可是我从其它的客户端(后来发现这些客户端版本均高于11.2.0.1)连接该Sp均没有问题。我以为是不是有什么后台程序会把输入的所有小写强制转成大写,于是我将账户密码从sys修改为SYS,然后再连接:
Figure 4
成功了。从现象上来看,似乎真的就是大小写的关系。可是真的是的吗?我现在将密码从SYS改回sys,然后再连接:
Figure 5
这回用sys作为密码登陆,也成功了。看来真正的原因,不是什么后台程序强制转换了大小写。那究竟是什么原因呢?
3、原理解释
其实,从现象上来看,变化最初是在设置了
SQLNET.ALLOWED_LOGON_VERSION_CLIENT
和SQLNET.ALLOWED_LOGON_VERSION_SERVER两个参数后产生的。那这两个参数到底起什么作用呢?
先看一下SQLNET.ALLOWED_LOGON_VERSION_CLIENT在官方文档中的说明:
Figure 6
这个参数是12C新引进的参数。它表示Cp在向Sp发送认证(authentication)申请时,所使用的最低版本的认证协议。注意此处的认证协议版本并不等同于Oracle Database的版本。那不同的认证协议版本主要区别在哪儿呢?除了协议语义上的区别,在我看来,最重要的区别在于不同认证协议的版本对应着不同的database version,而不同的database version则可能使用不同的hash算法对密码进行加密。不同的hash算法就是不同的password_version,这个可以从dba_users字典表的password_versions字段中得到说明:
Figure 7
Oracle在存储每个account的密码时,并非是明文存储,而是会将明文进行哈希加密存储,哈希加密算法即为该密码的version,即password_version。从上图Figure 7中可知,password_version实际上表示是同版本(并非完全一致,见最后的附表)的database 所提供的hash算法,例如password_version 10g就表示database 10g所提供的hash算法。如果Oracle所有的新版本都只使用新版本所特有的hash算法,那么一些较早的客户端因为还没有这些hash算法,就没法通过hash算法得到hash值,也就没法让服务器去验证这些hash值。为了解决兼容性的问题,Oracle会同时用多种hash算法(即password_version)对密码进行运算,并将多个运算结果均保留下来。在低版本客户端访问高版本的服务器时,低版本的客户端可以通报自己使用的认证协议以及使用该协议对应的hash算法所得到的密码hash值,服务器根据认证协议去查看是否存储了该协议对应的hash算法的hash值,如果存在,就比对两个hash值是否一致;如果不存在或两个hash值不一致,就报错。其流程图大致如下所示(仅代表自己的理解):
在上述密码认证流程图中,标红的子流程——"判断该账户的password_versions中是否包含client version",有一个疑问:account有哪些password version,是由什么决定的呢?这就引出了另一个参数:SQLNET.ALLOWED_LOGON_VERSION_SERVER。
关于这个参数的作用,它介绍了为这个参数设置不同的值所带来的影响,主要是对PASSWORD_VERSIONS的影响。最后附带了一张表,详细了列出了SQLNET.ALLOWED_LOGON_VERSION_SERVER设置不同值,所对应的password_versions。也说明了如果要与设置成当前值的12C数据库进行密码认证,所支持的client version。从上图可以看出,即使SQLNET.ALLOWED_LOGON_VERSION_SERVER设置为8,但生成的最低版本的password version也是10G。因此也说明,8I,9I的客户端因为没法理解10G的哈希函数,也就没法完成登陆认证。关于客户端到服务器端相互之间的兼容性如下表:
4、案例重演
-
在SQLNET.ALLOWED_LOGON_VERSION_SERVE=12时,为sys用户设置了密码,因此sys账户的password_versions=11G,12C
-
这时用client version 11.2.0.1请求登陆,因为client version 11.2.0.1的认证协议版本为11(该版本小于11.2.0.3,并没有打CPUOct2012补丁,因为认证协议版本为11),所以流程如红线1所示:
-
然后手动将SQLNET.ALLOWED_LOGON_VERSION_SERVE改为8,但因为只是参数变化,并没有重建密码,因此该account的实际password仍然为11G,12C,所以请求登陆的流程如红线2所示:
-
重新生成密码。因为是在SQLNET.ALLOWED_LOGON_VERSION_SERVE=8的情况下生成的密码,因此该account所对应的password_versions=10G,11G,12C,如下图所示:
而该password_versions是支持client version 11.2.0.1密码认证的,所以请求登陆的流程如红线3所示:
5、认证协议版本、database版本与password版本之间的关系
如下表: