Tomcat Siteminder SSO Agent

不知道有没有童鞋像我那样需要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”

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值