背景:
实现Microsoft@Windows@桌面和基于JBOSS的服务器之间的无缝单点登录。
注:本文不含与kerberos相关的服务器端设置,windows桌面设置相关的内容。
原理:
Kerberos:MIT发明的一种网络安全认证协议
SPNEGO:Microsoft对Kerberos进行了扩展,制定了称为简单和受保护的GSSS-API协商机制(Simple and Protected GSS-API Negotiation,SPNEGO)的协议,用于与WEB服务器共享Windows凭证,此协议允许客户端机器与WEB服务器进行协商,确定使用什么协议进行彼此通信。在配置Windwos桌面使用Active Directory(AD)域时,使用核心为Kerberos的安全基础架构,
JBOSS默认使用JAAS来实现具体的用户认证与授权工作,
JAAS,JAVA认证和授权服务,通过提供认证用户和确定用户授权来增强JAVA解决方案的动态安全性,使得资源能够得到很好得到保护和控制(JAAS使用动态的安全策略来定义权限,而不是将其静态的嵌入到代码中)。
JAAS采用的是插件的运行方式,一开始就被设计成可插拔的(Pluggable),根据应用的需要,只要配置一下JAAS的配置文件,这些组件即可包含 在我们的应用程序中。使用JAAS包接口,开发者和第三方可以开发一些组件或者BEAN来实现登陆认证,或者通过与使用者或外部的系统的进行交互来访问认 证信息。JAAS提供了一组用于用户鉴别的类和接口,这意味着支持JAAS的应用会要求用户登陆,同时 JAAS提供了另一组用于用户授权的类和接口。在讨论例子之前,先对JAAS API中常用的一些类和接口做个简单的说明。
LoginModule :确认用户的合法性(使用CallbackHandler或者其他类方法),并分配访问权限principal给subject;
LoginContext:为了实现用户鉴别,建立相应的环境,从配置文件中导入规则;
CallbackHandler:回调处理器,负责与用户(代码拥有者和执行者)交互,确认其身份的合法性;
Subject:表示登陆处理的目标,即一个被鉴别的用户。并可关联一个或多个pirncipal;
Principal:表示具有访问权限的一个实体,可以看作是可以执行某种操作的证件。
理解这些类和接口的关系我给个生动的比方:一个军事学校,入学的时候校方(LoginModule)根据学生(Subject)的入学通知来确定其合法 性,这个过程交由某工作人员(CallbackHandler)执行,(CallbackHandler)确认后,(LoginModule)给不同 (Subject)根据其身份发给相关的证件(Principal),有了该证件就可以访问对应的资源,(Subject)根据自己的 (Principal)的级别可以使用和访问学校不同资源。
一个(Subject)的(Principal)如果是公司普通职员级,那么可以访问的资源就相对少些,如果是经理级那就多些。当然一个(Subject)可以拥有多个(Principal)。
通过分析我们会发现,JAAS采用的也是身份检查+权限分配模式。因此JAAS的应用也分成两个部分:(1)认证;(2)授权。过程是先认证后根据身份来授权。
那么JAAS是如何实现认证的呢?又是如何实现授权的呢?
二 JAAS的认证原理
(1) 设置JAAS配置文件,关于配置非常有技巧,跟设置防火墙的过滤规则有得一拼;
(2) 根据JAAS配置文件的条目加载一个或者多个LoginModule(通常一个,也可以使用多个);
(3) 为了管理用户认证的有关过程,将提供一个可选的LoginModule构造函数和一个回调处理器CallbackHandler。如果没有在构造函数中提供回调处理器,系统采用默认设置;
(4) 初始并实例化LoginContext(加载配置规则),如果成功,则调用LoginContext的login方法。无论是否需 要,LoginContext都会去首先读取JAAS配置文件,从中获得要加载的登陆模块信息,其initialize方法将按照配置文件中的相关内容提 供LoginModule运行所需要的信息;
(5) LoginContext的login方法将调用LoginModule的login方法,确定用户身份。该方法将设置相关的回调,并由回调处理器CallbackHandler来管理登陆处理回调;
(6) LoginModule的login方法将负责与用户进行交互(可能是人机交互,也可能是机机交互),如果用户输入信息无效,则该方 法返回FALSE,一次交互过程结束,如果用户输入信息有效,则该方法将设置Principal对象的Subject对象,并返回TRUE;当然 LoginModule也可以将与用户之间的所有交互过程全部委托给处理器CallbackHandler来处理。如果登陆成功, LoginContext将调用LoginModule的commit方法将结果提交给LoginModule实例的内部状态。
在应用程序中使用JAAS验证通常会涉及到以下几个步骤:
1. 创建一个LoginContext的实例。
2. 为了能够获得和处理验证信息,将一个CallBackHandler对象作为参数传送给LoginContext。
3. 通过调用LoginContext的login()方法来进行验证。
4. 通过使用login()方法返回的Subject对象实现一些特殊的功能(假设登录成功)。
LoginModule描述由身份验证技术提供程序实现的接口。LoginModule 插入到应用程序中以提供特定类型的身份验证。
当应用程序写入 LoginContext API 时,身份验证技术提供程序将实现 LoginModule接口。Configuration指定将与特定登录应用程序一起使用的 LoginModule(s)。因此可以将不同的 LoginModule 插入到应用程序中,而无需修改应用程序本身。
LoginContext负责读取 Configuration和实例化适当的 LoginModule。每个 LoginModule都是使用 Subject、CallbackHandler、共享的 LoginModule状态和特定于 LoginModule 的选项来实例化的。 Subject表示当前正进行身份验证的 Subject,如果身份验证成功,则使用相关的 Credential 更新它。LoginModule 使用CallbackHandler与用户进行通信。例如,CallbackHandler可以用于提示要求用户名和密码。注意, CallbackHandler可以为 null。确实需要一个 CallbackHandler来对 Subject进行身份验证的 LoginModule 可以抛出 LoginException。LoginModule 可以有选择地使用共享状态来共享它们之间的信息或数据。
特定于 LoginModule 的选项表示由管理员或用户在 Configuration中为此 LoginModule配置的选项。这些选项由 LoginModule自身定义,并在其中控制其行为。例如,LoginModule可以定义支持调试/测试功能的选项。这些选项是使用键-值语法定义的,例如 debug=true。LoginModule以 Map形式存储这些选项,因此可以使用键来检索这些值。注意,对 LoginModule选择定义的选项个数是没有限制的。
调用应用程序将身份验证过程视为单个操作。但是,LoginModule中的身份验证过程分两个不同的阶段进行。在第一个阶段,LoginModule 的 login方法由 LoginContext 的 login方法调用。LoginModule的 login方法执行实际的身份验证(例如,提示并验证密码),并将身份验证状态作为私有状态信息保存。一旦完成上述操作,LoginModule 的 login将返回 true(如果成功)或 false(如果应该忽略它),或抛出 LoginException来指示失败。在失败的情况下,LoginModule不必再尝试进行身份验证或者引入延迟。由应用程序完成这类任务。如果应用程序试图重新尝试身份验证,将会再次调用 LoginModule 的 login方法。
在第二个阶段,如果 LoginContext 的整个身份验证成功(相关的 REQUIRED、REQUISITE、SUFFICIENT 和 OPTIONAL LoginModule 成功),则调用 LoginModule的 commit方法。LoginModule的 commit方法检查其私有保存状态,以查看自己的身份验证是否成功。如果整个 LoginContext身份验证成功,并且 LoginModule 自己的身份验证也获得成功,则 commit方法会将相关的 Principal(已进行身份验证的身份)和 Credential(身份验证数据,如加密密钥)与位于 LoginModule中的 Subject联系在一起。
如果 LoginContext 的整个身份验证失败(相关的 REQUIRED、REQUISITE、SUFFICIENT 和 OPTIONAL LoginModule 没有成功),则调用每个 LoginModule的 abort方法。在这种情况下,LoginModule移除/销毁原先保存的任何状态。
注销 Subject只涉及一个阶段。LoginContext调用 LoginModule 的 logout方法。然后 LoginModule的 logout方法执行注销过程,例如从 Subject中移除 Principal 或 Credential,或者记录会话信息。
LoginModule实现必须有一个无参数的构造方法。这允许加载 LoginModule的类对其进行实例化
一些JBOSS的安全概念:
JBOSS WEB身份验证
在大多数情况下,JBOSS SERVER的HTTP客户端(WEB浏览器)仅限于基于凭据或者用户ID/密码提示的简单身份验证机制,通过表单登录或者BasicAuth提示实现。JBOSS通过jboss negotiation提供了对SPNEGO的支持,jboss negotiation是jboss security的一个组件。
这个图片描述了典型的use case
1.用户登录桌面(比如Windows),桌面登录用活动目录(Active Directory)来管理。
2.用户使用浏览器(IE/Firefox)访问一个使用jboss negotiation,放在JBOSS服务器中的WEB系统
3.浏览器传输桌面登录信息到Web系统
4.JBOSS服务器根据后台的GSS信息通过(Active Directory)活动目录或者任一Kerberos 服务器来验证用户
5.用户无缝登录Web系统
在基于From表单保护来实现登录验证的WEB系统总,提供log out, log on as someone else 功能.
部署在Jboss中的web应用,如果选择使用容器提供的认证授权功能,需要在jboss-web.xml中定义对于的security domain,例如:
<jboss-web>
<security-domain flushOnSessionInvalidation=”true”>java:/jaas/sso</security-domain>
</jboss-web>
该domain应该预先定义在$JBOSS_HOME/server/$configuration/conf/login-config.xml中
<application-policy name = "sso">
<authentication>
<login-module code="org.jboss.security.auth.spi.IdentityLoginModule" flag="required">
<module-option>leow</module-option>
</login-module>
<login-module code="com.leow.security.auth.SSOLoginModule" flag="sufficient">
</login-module>
<login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule" flag="sufficient">
<module-option>props/users.properties</module-option>
<module-option>props/roles.properties</module-option>
</login-module>
<login-module code="com.leow.security.auth.FailLoginModule" flag="required">
</login-module>
</authentication>
</application-policy>
我们可以看见,在security-domain中,包含着一些login-module,实际的验证用户名密码,给用户添加对应角色的功能就是由这些login-module来完成。login-module是可插拔的,详见JAAS
Login-module的flag标志对于值的含义:
1.required:必须成功,后面的login-module继续执行
2.requisite:必须成功,成功,后面的login-module继续执行,失败,立即返回
3.sufficient:不要求必须成功,如果成功,就返回,失败,后面的login-module继续执行
4.optional:可选择,失败成功没有关系
SSO总体思路:
通过Spnego拿到windows登录凭证(登录用户名:比如我机器的登录名为
[email protected])
在SSOLoginModule中验证当前windows登录用户是否合法的Web系统用户,如果是,就登录成功,如果不是,就提供一个登录页面,让用户输入用户名/密码来进行登录。
具体实现:
在登录页面中,首先调用此方法:
fr.doume.authenticator.KerberosAuthenticator.authenticate(request, response);
此方法会在HttpSession中放入从Spnego中得到的windows登录名,如果没有拿到,则放入
NOT_AUTHORIZED_USER,即session.setAttribute(“PrincipalName”,”
[email protected]”);(在探索的过程中,我们发现,通过spnego的协商机制发送浏览器请求来获得windows用户名时,会发送401请求,而Tiles框架不知道如何处理401请求,所以调用此方法的登录页面,不能使用Tiles框架);然后尝试登录,response.sendRedirect(“j_security_check?j_username=”+ session.getAttribute(“PrincipalName”) + “&j_password=null”);
在SSOLoginMoudle中,判断拿到的windows登录用户名是否当前Web系统的合法用户,
private static final String WEB_REQUEST_KEY = “javax.servlet.http.HttpServletRequest”;
HttpServletRequest request = (HttpServletRequest)PolicyContext.getContext(WEB_REQUEST_KEY);
拿到request对象,然后拿到session对象,就可以在这里做判断
因为这是在JBOSS的环境下,所以在写我们自己的loginmodule时,需要遵守JBOSS对JAAS封装的规范,最好继承它的两个抽象类,AbstractLoginMoudle或UsernamePasswordLoginMoule。