不知道有没有童鞋像我那样需要Tomcat的siteminder sso agent,有的话这篇文章应该能给大家一点启示。
CA官方是没有Tomcat的sso agent的,替代办法是使用apache拦在tomcat前面,然后用apache专用的agent达到使用sso的目的。但如果之前一直使用tomcat JAAS控制权限的用户就会很不爽,验证方面需要改很多地方,apache方案基本没用。创维TTG也搞了个tomcat agent,但不是开源的,反编译发现居然还加了代码混淆,不用也罢。
经过一周的研究,要实现Tomcat的sso agent,基本就以下几种方案:
1、不用jaas的话,使用filter方案即可,在所有请求前面都拦一个filter,通过filter连policy server验证smsession,判断用户是否有权限访问受保护资源。
2、使用jaas的话,filter方案就不太好用了,因为filter是在触发FormAuthenticator之后的事情,也就是说还没等你的filter去验证smsession,就会被扔到form-login-page让你登陆。这里我想了一个比较绕的办法,但只是简单测了一下,基本可以用,但没有上生产跑过,大家慎用,也不太鼓励大家用。方案如下:
继续使用jaas,但form-login-page不是指向默认的登陆页面,而是指向一个servlet,暂定名为:autoLogin
autoLogin的doGet方法,可以写验证smsession的逻辑,具体方法是当smsession验证通过后,使用 httpclient请求一个受保护资源,得到一个JSESSIONID。然后再次使用httpclient用smsession decode出来的用户名去登陆,登陆方法就是post到j_security_check,当然大家要写一个自定义realm去做这个登陆,通过查DB 也好还是查Ldap也好,反正最后返回一个Principal。到这里,刚才得到的JSESSIONID就在服务器里认证通过了。然后redirect一个静态页面,将JSESSIONID当参数一起返回。
为什么要redirect到一个静态页面?因为当你访问一个受保护资源,服务器会自动给你产生一个JSESSIONID,这个 JSESSIONID和httpclient认证过的那个JSESSIONID不是同一个,所以即使在上一步通过setCookie把认证通过的 JSESSIONID加上,你仍然访问不了受保护资源,因为你将会有两个JSESSIONID!一个是认证通过的,一个是没有的。所以这个静态页面的作用就是通过js,把原来的JSESSIONID给干掉,再加上认证过的JSESSIONID,最后再转到需要访问的资源。
这个方法非常绕,玩玩还可以,不适合用在生产系统。
3、这是我最终选择的方案,也是最直接最完美的方案,就是修改tomcat源码。下面讲一下详细过程。
这次我选择的tomcat版本是7.0.8,最开始想用5.0.28,因为可以避免升级,但发现5.0和7.0版本在authenticate的实现上有点区别,5.0版本的authenticate方法里没有传入Request对象,这将导致无法在认证的时候获取smsession cookie进行decode。最后只能选择tomcat 7.0进行改造。
第一步:获取tomcat7.0.8源代码。
建议大家使用eclipse svn新建项目,通过以下地址获取源码:http://svn.apache.org/repos/asf/tomcat/archive/tc5.0.x/tags/TOMCAT_5_0_28
第二步:修改tomcat源代码。
因为我们使用的是form验证,因此需要修改 org.apache.catalina.authenticator.FormAuthenticator文件的public boolean authenticate(Request request,HttpServletResponse response,LoginConfig config)方法
在以下代码之后
// Have we authenticated this user before but have caching disabled?
if (!cache) {
session = request.getSessionInternal(true);
if (log.isDebugEnabled())
log.debug("Checking for reauthenticate in session " + session);
String username =
(String) session.getNote(Constants.SESS_USERNAME_NOTE);
String password =
(String) session.getNote(Constants.SESS_PASSWORD_NOTE);
if ((username != null) && (password != null)) {
if (log.isDebugEnabled())
log.debug("Reauthenticating username '" + username + "'");
principal = context.getRealm().authenticate(username, password);
if (principal != null) {
session.setNote(Constants.FORM_PRINCIPAL_NOTE, principal);
if (!matchRequest(request)) {
register(request, response, principal,
Constants.FORM_METHOD,
username, password);
return (true);
}
}
if (log.isDebugEnabled())
log.debug("Reauthentication failed, proceed normally");
}
}
加上自己的代码:
if(principal == null){
Cookie[] cookies = request.getCookies();
if(cookies != null){
principal = context.getRealm().authenticate(cookies);
}
if (principal != null) {
// Bind the authorization credentials to the request
request.setAuthType("FORM");
request.setUserPrincipal(principal);
session = request.getSessionInternal(true);
session.setPrincipal(principal);
return (true);
}
}
大家细心的话会发现,Realm接口里是没有authenticate(cookies)这个方法的,因此我们还需修改
org.apache.catalina.Realm接口,加上方法public Principal authenticate(Cookie[]
cookie);同时还需要在Realm的实现类RealmBase里,加上以下代码,让它默认返回null就可以了,将来我们可以自己写个realm类
继承ReamlBase,再重写这个方法,这样能减少对tomcat的改动。
public Principal authenticate(Cookie[] cookie) {
return null;
}
OK,对tomcat的改动就完成了,以下是重新编译这几个类,编译方法不建议用ant,会很麻烦,需要很多依赖包。建议下一个已经编译好的tomcat7.0.8,把lib下和bin下的所有jar包都引进classpath,直接通过javac去编译这三个类文件,然后替换catalina.jar里对应的class文件,最后用新的catalina.jar替换老的catalina.jar即可。建议使用jdk1.6以上版本,或者大家可以查看原来catalina.jar里的class文件的版本号选择对应的jdk也行。
第三步:编写自定义realm,处理smsession cookie。
自定义realm需要继承RealmBase,引入sso的javaagent,目前只测通了使用JNI的agent(smjavaagentapi.jar),pure java的agent测不过,用JNI最不好的地方就是会crash,看来CA还留了一手。
private static ResourceBundle bundle = null;
private static final String BUNDLE_NAME = "ssoconfig";
private static Logger log = Logger.getLogger(LDAPJDBCRealm.class);
/** SiteMinder AgentAPI objects */
private AgentAPI agentapi;
private boolean isAgentInit = false;
public LDAPJDBCRealm(){
bundle = ResourceBundle.getBundle(BUNDLE_NAME);
log.info("bundle init success!");
isAgentInit=smInitAPI();
}
public Principal authenticate(Cookie[] cookies) {
String ssoToken = null;
boolean flag = false;
log.info("start decode smsession!");
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
log.info("COOKIE:" +cookies[i].getName() + ":" + cookies[i].getValue());
if (cookies[i].getName().equalsIgnoreCase("SMSESSION")) {
ssoToken = cookies[i].getValue();
flag = true;
break;
}
}
}
if(flag){
//这个就是从sso里decode出来的用户名了,自己写方法验证吧!验证完记得return一个Principal。
String userName = smDecodeSSOtoken(ssoToken);
.....
}else{
log.info("没有找到SMSESSION!");
return null;
}
}
private String smDecodeSSOtoken(String ssoToken) {
int retcode;
// create attribute list to receive attributes from the SSO token
AttributeList attrList = new AttributeList();
TokenDescriptor tokendesc = new TokenDescriptor(0, false);
SessionDef sessionDef = new SessionDef();
// request that an updated token be produced
boolean updateToken = true;
// this object will receive the updated token
StringBuffer updatedSSOToken = new StringBuffer();
retcode = agentapi.decodeSSOToken(ssoToken.toString(), tokendesc,
attrList, updateToken, updatedSSOToken);
boolean isFirstElem = true;
Enumeration attributeListEnum = attrList.attributes();
if (!attributeListEnum.hasMoreElements()) {
log.info(bundle.getString("AGENTAPI_NONE"));
}
String userName = "";
while (attributeListEnum.hasMoreElements()) {
Attribute attr = (Attribute) attributeListEnum.nextElement();
log.info(attr.id + "\t" + new String(attr.value));
isFirstElem = false;
if(attr.id == 210){//smsession cookie里包含了很多信息,我需要的是attr210里的用户名,大家各取所需。
userName = new String(attr.value);
}
}
// this.setHeaderAttributes(attrList, ht);
//return updatedSSOToken.toString();
return userName;
}
boolean smInitAPI(){
String agentName = bundle.getString("AGENT_NAME");
String agentSecret = bundle.getString("AGENT_SECRET");
log.info("Loading configuration for agent_name:" + agentName);
agentapi = new AgentAPI();
ServerDef serverdef = new ServerDef();
serverdef.serverIpAddress = bundle.getString("PS_IP");
try {
serverdef.connectionMin = Integer.parseInt(bundle.getString("PS_CONMIN"));
serverdef.connectionMax = Integer.parseInt(bundle.getString("PS_CONMAX"));
serverdef.connectionStep = Integer.parseInt(bundle.getString("PS_CONSTEP"));
serverdef.timeout = Integer.parseInt(bundle.getString("PS_TIMEOUT"));
serverdef.authenticationPort = Integer.parseInt(bundle.getString("PS_AUPORT"));
serverdef.authorizationPort = Integer.parseInt(bundle.getString("PS_AZPORT"));
serverdef.accountingPort = Integer.parseInt(bundle.getString("PS_ACPORT"));
} catch (Exception e) {
log.info("Invalid agent configuration parameter - non numeric");
return false;
}
try{
InitDef initdef = new InitDef(agentName, agentSecret, false, serverdef);
int retcode = agentapi.init(initdef);
log.info("SSO agent初始化成功!");
if (retcode != AgentAPI.SUCCESS) {
log.info("Failed to connect to Siteminder policy server");
return false;
}
}catch(Throwable ex){
log.error("SSO AGENT初始化失败!");
log.error(ex.getMessage());
return false;
}
return true;
}
}
sso agent信息配置文件:
PS_IP = policy server地址
PS_CONMIN = 1
PS_CONMAX = 3
PS_CONSTEP = 1
PS_TIMEOUT = 75
PS_AUPORT = 44442
PS_AZPORT = 44443
PS_ACPORT = 44441
AGENT_NAME = agent名称
AGENT_SECRET = agent密码
AGENT_IP = agent IP
ADMIN_NAME = admin name
ADMIN_PWD = admpwd
USER_NAME = user name
USER_PWD = userpwd
LOGFILE_NAME = smjsdksample.log
LOGGING_DETAIL = false
第四步:配置tomcat。
增加如下配置,这里的配置只是个参考,具体根据个人设置而定,反正就是需要增加自己的realm。
Realm className="MyRealm"
第五步:配置policy server
根据上面的配置文件配就好了,注意要勾上“Support 4.x agents”
分享到:
相关推荐
Spring Security多身份验证展示柜基于Spring Boot的示例应用程序,展示了用于多个身份验证流程的案例Java配置-Siteminder SSO和基于表单的登录。入门/运行应用程序克隆存储库,使用maven将其打包为jar,然后从目标...
1. **商业软件**:一些大型公司如 Netgrity(Siteminder)、Novell(iChain)、RSA(ClearTrust)等提供专门的SSO解决方案。这些软件通常具备高度的定制性和安全性,适用于有严格安全需求的大型企业。它们可能需要在...
- **Siteminder**:由Netgrity开发,现已被CA Technologies收购,提供全面的身份管理和安全访问控制。 - **iChain**:Novell公司的产品,用于网络访问控制和身份管理。 - **ClearTrust**:RSA公司的产品,专注于...
Siteminder是一款由美国计算机协会(CA)开发的安全解决方案,广泛应用于企业环境中,用于身份验证、授权及单点登录(Single Sign-On, SSO)等功能。本文档将详细介绍Siteminder Option Pack r12.0SP2版本的安装步骤...
- Netgrity的Siteminder、Novell的iChain和RSA的ClearTrust等商业解决方案,它们通常提供全面的身份管理和安全服务,适用于对安全性要求高的大型企业。这些软件通常需要与企业现有的应用程序集成,可能需要安装代理...
头部名称因系统而异(例如,在 SiteMinder 中头部名称为“sm_user”),其内容是用户 ID。 如果 Access Point 未在头部找到用户 ID,则会将其重定向到指定的 URL(在大多数商业单点登录解决方案中,这种重定向通常...
实现 Java 单点登录的一种方法是选择专门的 SSO 商业软件,例如 Netgrity 的 Siteminder、Novell 公司的 iChain、RSA 公司的 ClearTrust 等这些商业软件通常适用于客户对 SSO 的需求很高,并且企业内部采用 COTS ...
**商业SSO软件**如Netegrity的SiteMinder(现已被CA收购)、Novell的iChain、RSA的ClearTrust等,通常针对大型企业,特别是那些使用Domino、SAP、Siebel等复杂系统的公司。这些软件往往提供预编译的代码模块以适应...
基本定义部分,手册可能详细解释了Apache WebServer、WebAgent、Novell eDirectory、Novell IDM和SiteMinder等相关概念。Apache是广泛使用的开源HTTP服务器,WebAgent是用于增强安全性、实现身份验证和授权的中间件...
1. 使用专业的SSO商业软件,如Netgrity的Siteminder、Novell的iChain、RSA的ClearTrust等。这些软件通常适用于高需求的SSO场景,特别是那些使用了COTS(Commercial Off The Shelf,即现成商业软件)如Domino、SAP、...
SiteMinder是一款针对网站用户设计的Chrome浏览器扩展插件,主要功能是提供停止销售报告和全日期报告。这款插件特别之处在于它支持西班牙语(español),为讲西班牙语的用户提供方便。通过在Chrome浏览器上安装...
- **Netegrity SiteMinder**:已被CA公司收购,提供全面的身份管理和SSO解决方案。 - **Novell iChain**:专注于网络访问控制和安全。 - **RSA ClearTrust**:RSA的安全产品,提供身份验证和访问管理服务。 2. *...
主要有:Netgrity的Siteminder,已经被CA收购。Novell 公司的iChain。RSA公司的ClearTrust等。 门户产品供应商自己的SSO产品, 如:BEA的WLES,IBM 的Tivoli Access Manager,Sun 公司的identity Server,...
商业SSO软件通常包括专门的SSO解决方案,如Netegrity的SiteMinder(现已被CA收购)、Novell的iChain、RSA的ClearTrust等,以及门户产品供应商提供的SSO组件,如BEA的WLES、IBM的Tivoli Access Manager、Sun的...
Netgrity的Siteminder被CA公司收购,Novell的iChain,RSA的ClearTrust等都是专门的SSO软件。此外,门户产品供应商如BEA的WLES,IBM的Tivoli Access Manager,Sun公司的Identity Server,以及Oracle的OID等也提供了...