星期四, 十二月 25, 2008

文本聚类算法TCUSS(Text clustering using semantic similarity)

http://club.ufida.com.cn/blogs/rbg/archive/2008/05/28/_8259554F6E789A5B2D4E8765575B267B324E8476F8763C4FA65E2800_ZT_2900_.aspx

动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法

动态规划求解最长公共子串问题


算法思想

求字符串str1,str2的最长公共子串的长度。

定义二元函数函数f(m,n):分别以str1[m],str2[n]结尾的连续公共子串的长度

而对于f(m+1,n+1) 有以下两种情况

1.str1[m+1] != str2[n+1],则有f(m+1,n+1) =0

2.str1[m+1] == str2[n+1],则有f(m+1,n+1) = f(m,n) + 1

另外f(0,j) = 0(j>=0)

f(j,0) = 0 (j>=0)

按照上面这个公式,我们用容易写出这个算法的实现

算法实现

1 int commstr(char *str1, char *str2)

2 /* 返回str1,str2的最长公共之串长度*/

3 {

4 int len1=strlen(str1),len2=strlen(str2),row,col,max=0;

5 int **pf = new int*[len1+1];//动态分配一个二维数组作为辅助空间

6 for (row=0; row

7 pf[row] = new int[len2+1];

8

9 //数组赋初值

10 for (row=0; row

11 pf[row][0] = 0;

12 for (col=0; col

13 pf[0][col] = 0;

14

15 for (row=1; row<=len1; row++)

16 for (col=1;col<=len2; col++)

17 {

18 if (str1[row-1] == str2[col-1])

19 {

20 pf[row][col] = pf[row-1][col-1] + 1;

21 max = pf[row][col] > max ? pf[row][col] : max;

22 }

23 else

24 pf[row][col] = 0;

25 }

26 //空间回收

27 for (row=0; row

28 delete[] pf[row];

29 delete[] pf;

30

31 return max;

32 }



程序的输出

字符串"blog.csdn.net"和"csdn.blog"求公共子串时的输出结果

String:

1. blog.csdn.net

2. csdn.blog

c s d n . b l o g

0 0 0 0 0 0 0 0 0 0

b 0 0 0 0 0 0 1 0 0 0

l 0 0 0 0 0 0 0 2 0 0

o 0 0 0 0 0 0 0 0 3 0

g 0 0 0 0 0 0 0 0 0 4

. 0 0 0 0 0 1 0 0 0 0

c 0 1 0 0 0 0 0 0 0 0

s 0 0 2 0 0 0 0 0 0 0

d 0 0 0 3 0 0 0 0 0 0

n 0 0 0 0 4 0 0 0 0 0

. 0 0 0 0 0 5 0 0 0 0

n 0 0 0 0 1 0 0 0 0 0

e 0 0 0 0 0 0 0 0 0 0

t 0 0 0 0 0 0 0 0 0 0


max substr length:5

这是程序的输出结果,请注意红色字体


时间空间复杂度分析

如果用n,m表示两个字符串的长度的话,那么算法的

时间复杂度为O(n*m),空间复杂度也为O(n*m)

附:完整的源程序g++编译通过

#include

#include

void print_table(char *str1,char *str2,int **pf)

{

int i,j,row,col;

row = strlen(str1);

col = strlen(str2);

printf("\t\t");

for (i=0; i

printf("%c\t",str2[i]);

for (i=0; i<=row; i++)

{

for (j=0; j<=col; j++)

{

if (j == 0)

{

printf("\n");

if (i)

printf("%c\t",str1[i-1]);

else

printf("\t");

}

printf("%d\t",pf[i][j]);

}

}

}

int commstr(char *str1, char *str2)

/* 返回str1,str2的最长公共之串长度*/

{

int len1=strlen(str1),len2=strlen(str2),row,col,max=0;

int **pf = new int*[len1+1];//动态分配一个二维数组作为辅助空间

for (row=0; row

pf[row] = new int[len2+1];

//数组赋初值

for (row=0; row

pf[row][0] = 0;

for (col=0; col

pf[0][col] = 0;

for (row=1; row<=len1; row++)

for (col=1;col<=len2; col++)

{

if (str1[row-1] == str2[col-1])

{

pf[row][col] = pf[row-1][col-1] + 1;

max = pf[row][col] > max ? pf[row][col] : max;

}

else

pf[row][col] = 0;

}

print_table(str1,str2,pf);

//空间回收

for (row=0; row

delete[] pf[row];

delete[] pf;

return max;

}

int main(int argc,char **argv)

{

if (argc >= 3)

{

printf("String:\n\t1. %s\n\t2. %s\n",argv[1],argv[2]);

printf("\nmax substr length:%d\n",commstr(argv[1],argv[2]));

}

return 0;

}

星期三, 十二月 24, 2008

数据库自我关联

这个是SQLITE 得到一个记录和其随后记录的SQL:通过rowid来关联

select a.*,b.* from _dbhistory a left join _dbhistory b on a.rowid=(b.rowid-1)

星期五, 十一月 21, 2008

DhtmlxGrid 错误跟踪

function myErrorHandler(type, desc, erData)
{
//custom code can be placed here
return false;
}
dhtmlxError.catchError("LoadXML", myErrorHandler);

星期三, 十一月 19, 2008

SQLITE 3.6.5 SQL小窍门

1.得到最近的几期数据

where rowid>((select max(rowid) from 表名)-想要的记录条数)

星期一, 十一月 17, 2008

MathType快捷键

插入一个分数:Ctrl+F
插入一个上标:Ctrl+H
插入一个下标:Ctrl+L

Right arrow with upper text slot (Ctrl+T,Shift+Right) , 输入条件推理
Right arrow with lower text slot (Ctrl+T,CtrlAlt+Right) 输入条件推理
Right arrow with upper and lower text slots (Ctrl+T,Right) 输入条件推理
Curly brackets 大括号{
Left-pointing angle brackets 向左的尖括号<
Right-pointing angle bracket from Symbol style (Ctrl+Shift+K,>)
Left white square bracket from Extra Math style (Ctrl+Shift+K,[)
左白方括号
Right white square bracket from Extra Math style (Ctrl+Shift+K,])

星期四, 十一月 06, 2008

Java JPopupMenu selection 丢失。

Java JpopupMenu selection 有时候invisible,这种问题原因是事件丢失。
简单使用如下方法就可以了:

jpopup.setInvoker(jpopup);

如果想修改selection的颜色,可以使用如下方法,

UIManager.put("MenuItem.selectionBackground", Color.YELLOW);


星期三, 十一月 05, 2008

小窍门:Java如何让一个窗体显示在最上面?

SwingUtilities.windowForComponent( JPupupMenu ).window.setAlwaysOnTop( true )

使用这个方法可以做到。

如何在任何地方得到鼠标的的屏幕坐标?

jdk 6.0提供MouseInfo.getPointerInfo() .getLocation() 得到Point.getX(),.getY()

星期六, 十一月 01, 2008

如何选择JTable中任一行和任一列?

主要代码如下:

table.setModel(dayModel);
table.setCellSelectionEnabled(true);
table.getSelectionModel().setSelectionInterval(2, 2);
table.getColumnModel().getSelectionModel().setSelectionInterval(3, 3);
你可以修改上面的2行,3列为你自己的任一行或列。非常的easy

星期一, 十月 27, 2008

如何提高SQLITE JDBC大批量插入数据的速度?

应为每一个Insert into操作默认都需要一个事物,所以通过显示的声明一个事物,可以大大提高数据插入的时间。
类似语句如下:

stmt.execute("BEGIN");
for(int i=0;i<commands.length;i++)
{
stmt.addBatch(commands[i]);
}
stmt.executeBatch();
stmt.execute("end");

曾经大约800数据,使用默认方法插入,大概需要141秒,而采用新方法后,使用纯Java的JDBC
速度提高到4秒,而使用JNI的JDBC,速度提高到几乎可以忽略不计的地步。

Very nice.!!!

星期五, 十月 24, 2008

Java JScrollPane getVerticalScrollBar.setValue没有效果解决方法。

JScrollPane scroll=new JscrollPane(new JEditorPane());

toolTip.getContentPane().add(scroll);
toolTip.pack();

//以下代码可以让scroll滚动到top顶端。

SwingUtilities.invokeLater(new Runnable(){public void run()
{
scroll.getVerticalScrollBar().setValue(0);
}
});

星期四, 十月 23, 2008

Servlet download 如何为已知的 MIME 类型激活“文件下载”对话框?

根据微软的一个文章:http://support.microsoft.com/kb/q260519/
参考如下:
您可以使用 Content-disposition 头来覆盖此默认行为。其格式是:
Content-disposition: attachment; filename=fname.ext
ASP:代码

Response.AddHeader "content-disposition","attachment; filename=fname.ext"

Servlet代码:
realName="yourfiel.n";
response.addHeader("Content-disposition","attachment; filename="+realName);
//realName如果是汉字编码会造成出现不了制定的文件名的情况,需要编码
//把以上代码修改为
//response.addHeader("Content-disposition","attachment; filename="+java.net.URLEncoder.encode(realName,"UTF-8"));

response.setIntHeader("Content-length", (int)rs.getBlob("ATTACH_FILE").length());

InputStream is = rs.getBinaryStream("ATTACH_FILE");

byte[] buf = new byte[3000];
int read = 0;
while ((read = is.read(buf)) > 0)
{
// fos.write(buf, 0, read);
outs.write(buf, 0, read);
}
// fos.close();
is.close();
outs.flush();
// outs.close();
}


星期四, 十月 16, 2008

SQLite自定义函数简介

SQLite自定义函数简介

SQLite最大的特色之一就是可以用户定义函数。用户自定义函数可以像系统内置函数一样可以在注册之后像系统内置函数一样在SQL语句中使用。用户使用自定义函数类似存储过程,方便用户对常见功能的调用,也加快了执行速度。用户自定义函数整体上可以分为两种:简单函数和聚集函数。

简单函数(simple function)

简单函数用在任何表达式中,常见的有max(x,y..), min(x,y..), random(*), last_insert_rowid(), length(x),lower(x), upper(x), round(x,y), round(*), substr(x,y,z), typeof(x)

聚集函数(aggregate function)

聚集函数经常用在select语句中,常见的有avg(x),count(x), count(*), max(x), min(x), sum(x), total(x)

  有些函数既是简单函数,又是聚集函数。比如只有一个参数的min()是一个聚集函数,而有多个参数的min()是一个简单函数。

SQLITE比较好的书有两本:
一个是:O'Reilly出版的 Inside SQLite
一个是:Apress.The.Definitive.Guide.to.SQLite.May.2006

星期一, 十月 13, 2008

如何立即刷新Java Swing Jcomponent组件?

例子:按下一个button,调用JTextArea.setText()之后,JTextArea的内容不会立即更新,必须要等待button捆绑的ActionListener过程完全完成之后,才会刷新。
如果想立即刷新JTextArea的内容去,需要调用一下语句。

JTextArea sql2= new JTextArea(5,10);
Point pt=sql2.getLocation();
Dimension ds=sql2.getSize();
sql2.paintImmediately((int)pt.getX(),(int)pt.getY(), (int)ds.getWidth(),(int)ds.getHeight());

这个工作的非常好。

星期四, 十月 09, 2008

得到当前运行class的物理位置

可以使用如下简单语句:

URL classFileDir =new yourClassName().getClass().getResource(".");

print结果为:file:....的URL

星期三, 十月 08, 2008

Windows XP SP2手工修改屏幕分辨率

有时候设置了不对的屏幕分辨率,会造成屏幕发黑无法启动。这是有需要在启动的时候按下F6键,然后进入注册表删除设置的屏幕分辨率,
Intel(R) Extreme Graphics 除了在注册表:
HKEY_CURRENT_CONFIG\System\CurrentControlSet\Control\VIDEO有引用,
本身在:
[HKEY_CURRENT_CONFIG\System\CurrentControlSet\SERVICES\IALM]也有设置,必须删除这个设置
再启动机器后,才能正常进入。

[HKEY_CURRENT_CONFIG\System\CurrentControlSet\SERVICES\IALM\DEVICE0]
[HKEY_CURRENT_CONFIG\System\CurrentControlSet\SERVICES\IALM\DEVICE0\Mon80861100]

这两个注册表项目要删除掉

星期六, 九月 27, 2008

beanshell Bsh.Console如何在jdk 1.6中使用

直接使用String.format(format,param)会出现错误。
必须使用如下语法,
String.format("%3.2f",new Object[]{(float)6/11});

就可以使用了

星期五, 九月 19, 2008

dhtmlXGrid 覆盖函数。

这是覆盖每个cell 动态的tooltip的提示的方法。

dhtmlXGridCellObject.prototype.getTitle=function()
{
ccolName=this.grid.getColumnId(this.cell._cellIndex);
rowID=this.cell.parentNode.idd;
result=this.cell.innerHTML;
if(ccolName=='RULE_NAME')
result=this.grid.cells(rowID,4).getValue();
return result;
}

修改每个行的颜色
mygrid.setRowColor(rowid,"yellow");
或者更加底层的,可以修改更多属性CSS

mygrid.rowsAr[idrow].style.backgroundColor="yellow"

星期四, 九月 18, 2008

dhtmlXGrid 1.3版本如何修改每个cell的帮助提示呢?Tooltip

经过dhtmlXGrid论坛发现了一个渐变的方法。

dhtmlXGridCellObject.prototype.getTitle=function()
{
return this.cell.innerHTML+"<h1>F1 HELP</H1><BR/><P>..."+this.cell._cellIndex+"|||"+this.grid.getColumnId(this.cell._cellIndex);
}

不过这个innerHTML只承认纯文本,所以HTML语法无法使用了,\nJavascript符号还是认识的。


星期六, 九月 13, 2008

JBOSS JAAS如何编写自己的身份(principal)?

详细的步骤在:http://wiki.jboss.org/wiki/UsingCustomPrincpalsWith
不过经过事件发现只需要重载两个方法就可以了。如果直接从org.jboss.security.auth.spi.DatabaseServerLoginModule扩展,这样可以利用JBOSS已经有的密码加密,编码等其他一些特性,不需要再重写。

类似如下:
public class StandardLoginModule extends DatabaseServerLoginModule
编写自己的定制身份类Principal,可以加入除了名字之外的其他属性.

public class userPropertiesPrincipal extends SimplePrincipal
{
//User's property
private String email;
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email=email;
}
//gender
private String gender;
public String getGender()
{
return gender;
}
public void setGender(String gender)
{
this.gender=gender;
}
public userPropertiesPrincipal(String name)
{
super(name);
}

}

必须重载的三个方法(红色)
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map sharedState, Map options)
   public boolean login() throws LoginException
{
}
   protected Principal getIdentity()
{
 可能需要重载的类.
protected Group[] getRoleSets() throws LoginException
{}

具体设置步骤:在login-config.xml里加入,通过
principalClass来制定自己定制的身份类.也注意加入了
userPropertiesQuery,这个需要在initialize方法中从Map options中取出来,如下:
super.initialize(subject, callbackHandler, sharedState, options);

userPropertiesQuery=(String ) options.get("userPropertiesQuery");
if(userPropertiesQuery==null)
userPropertiesQuery="select * from sysUser where userid=?";


<application-policy name = "seamAC">
<authentication>

<login-module code = "org.security.login.jass.StandardLoginModule"
flag = "required">

<module-option name = "dsJndiName">java:/SecMasterDS</module-option>
<module-option name = "principalsQuery">select password from sysUser where userid=? </module-option>
<module-option name = "userPropertiesQuery">select * from sysUser where userid=? </module-option>
<module-option name = "rolesQuery">SELECT A.roleID as Role,'Roles' FROM sysRoles A,UserRoles B WHERE A.roleID=B.roleID AND B.userId=?</module-option>
<module-option name = "debug">true</module-option>
<module-option name="principalClass">org.security.login.jass.userPropertiesPrincipal</module-option>
<!--Password is md5 encrypt-->
<module-option name="hashAlgorithm">MD5</module-option>
<module-option name ="hashEncoding">HEX</module-option>
</login-module>
</authentication>
</application-policy>

Web.xml中必须指明使用使用JAAS Security Manage RealM

<Realm className="org.jboss.web.tomcat.security.JBossSecurityMgrRealm"
certificatePrincipal="org.jboss.security.auth.certs.SubjectDNMapping"
allRolesMode="authOnly"
debug="99"
/>

星期四, 九月 11, 2008

Jboss如何获取用户验证后的角色?

通过JAAS验证的用户,要想得到验证的用户的所有的角色,JBOSS目前推荐的是如下代码段
主要是基于新的JACC规范:
Java Authorization Contract for Containers (Java ACC) specification (JSR-115)

import javax.security.auth.Subject ;
import javax.security.jacc.PolicyContext;

private String findRole() throws Exception
{
String result="";

// Get the Authenticated Subject
Subject subject = (Subject) PolicyContext.getContext("javax.security.auth.Subject.container");

// Now look for a Group called Roles
Set principals = subject.getPrincipals(Principal.class);
Iterator iter = principals.iterator();
while(iter.hasNext())
{
Principal p = (Principal)iter.next();
if(p instanceof SimpleGroup)
{
SimpleGroup sg = (SimpleGroup)p;
if("Roles".equals(sg.getName()))
{
Enumeration en = sg.members();
while(en.hasMoreElements())
{
String role = en.nextElement().toString();
result=result+","+role;
System.out.println("Role:"+role);
}
}
}
}
return result;
}

JAASRealm和Jboss(CMA) Container Manager Authentication

这是为了结合JAAS和CMA两者的有点提出的接口。
在http://www.jboss.org/file-access/default/members/jbossweb/freezone/docs/2.1.0/realm-howto.html#JAASRealm,描述了相关配置
web.xml

<Realm className="org.jboss.web.tomcat.security.JBossSecurityMgrRealm"
certificatePrincipal="org.jboss.security.auth.certs.SubjectDNMapping"
allRolesMode="authOnly"
debug="99"
/>


其他配置同JAAS标准配置一样,需要在web.xml,login-config.xml和LoginModule中调用同一个appName

但是注意,在这个LoginModule你必须自己保存用户的验证信息。
It is the responsibility of your login module to create and save User and Role objects representing Principals for the user

下列代码就是Jboss实现的:org.jboss.web.tomcat.security.login.WebAuthentication


/*
* JBoss, Home of Professional Open Source
* Copyright 2007, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.web.tomcat.security.login;

import java.security.Principal;
import java.security.cert.X509Certificate;

import javax.naming.NamingException;

import org.apache.catalina.Session;
import org.apache.catalina.authenticator.Constants;
import org.apache.catalina.connector.Request;
import org.jboss.web.tomcat.security.SecurityAssociationValve;

//$Id$

/**
* JBAS-4077: Programmatic Web Login
* @author Anil.Saldhana@redhat.com
* @since Mar 12, 2007
* @version $Revision$
*/
public class WebAuthentication
{
private static final String AUTH_TYPE = "PROGRAMMATIC_WEB_LOGIN";
public WebAuthentication()
{
}

/**
* Login an user via the CLIENT-CERT method
* @param certs X509 certificates
* @return Authenticated User Principal
*/
public boolean login(X509Certificate[] certs)
{
//Get the active request
Request request = (Request) SecurityAssociationValve.activeRequest.get();
if(request == null)
throw new IllegalStateException("request is null");
Principal p = request.getContext().getRealm().authenticate(certs);
if(p != null)
{
register(request,p, null, null);
}
return p!= null;
}

/**
* Login an user via the BASIC, FORM, DIGEST methods
* @param username
* @param credential
* @return
* @throws NamingException
*/
public boolean login(String username, Object credential)
{
//Get the active request
Request request = (Request) SecurityAssociationValve.activeRequest.get();
if(request == null)
throw new IllegalStateException("request is null");

Principal p = null;
if(credential instanceof String)
{
p = request.getContext().getRealm().authenticate(username, (String)credential);
}
else if (credential instanceof byte[])
{
p = request.getContext().getRealm().authenticate(username, (byte[])credential);
}
if(p != null)
{
register(request,p, username, credential);
}
return p != null;
}

/**
* Log the user out
*
*/
public void logout()
{
//Get the active request
Request request = (Request) SecurityAssociationValve.activeRequest.get();
if(request == null)
throw new IllegalStateException("request is null");
unregister(request);
}

/**
* Register the principal with the request, session etc just the way AuthenticatorBase does
* @param request Catalina Request
* @param principal User Principal generated via authentication
* @param username username passed by the user (null for client-cert)
* @param credential Password (null for client-cert and digest)
*/
protected void register(Request request, Principal principal, String username, Object password)
{
request.setAuthType(AUTH_TYPE);
request.setUserPrincipal(principal);

//Cache the authentication principal in the session
Session session = request.getSessionInternal(false);
if(session != null)
{
session.setAuthType(AUTH_TYPE);
session.setPrincipal(principal);
if (username != null)
session.setNote(Constants.SESS_USERNAME_NOTE, username);
else
session.removeNote(Constants.SESS_USERNAME_NOTE);
if (password != null)
session.setNote(Constants.SESS_PASSWORD_NOTE, getPasswordAsString(password));
else
session.removeNote(Constants.SESS_PASSWORD_NOTE);
}
}

/**
* Log the user out
* @param request
*/
protected void unregister(Request request)
{
request.setAuthType(null);
request.setUserPrincipal(null);

//Cache the authentication principal in the session
Session session = request.getSessionInternal(false);
if(session != null)
{
session.setAuthType(null);
session.setPrincipal(null);
session.removeNote(Constants.SESS_USERNAME_NOTE);
session.removeNote(Constants.SESS_PASSWORD_NOTE);
}
}

private String getPasswordAsString(Object cred)
{
String p = null;

if(cred instanceof String)
{
p = (String)cred;
}
else if(cred instanceof byte[])
{
p = new String((byte[])cred);
}
return p;
}
}

星期三, 九月 10, 2008

JBOSS JAAS和tomcat j_security_check CMS关系(之二)

Jboss 的 WebAuthentication的login方法到底做了什么工作?将JAAS和tomcat container链接了起来?
这个要查看代码:

http://www.javakey.net/source/jboss/4.x/org/jboss/web/tomcat/security/login/WebAuthentication.java.html

从这里可以看到,如何从tomcat中获取当前激活的
 org.apache.catalina.connector.Request(http://www.docjar.com/html/api/org/apache/catalina/connector/Request.java.html)
的后门方法

Request request = (Request) SecurityAssociationValve.activeRequest.get();

这个方式是非常cool的
.这里实际调用了Request上下文的RealM的
authenticate多元方法.
这个方法是在tomcat web.xml里通过来定义的。
比如:jboss 4.2.3GA. server\default\deploy\jboss-web.deployer\server.xml
里定义:
<realm classname="org.jboss.web.tomcat.security.JBossSecurityMgrRealm" certificateprincipal="org.jboss.security.auth.certs.SubjectDNMapping" allrolesmode="authOnly">

Jboss相关类源代码:http://www.javakey.net/source/jboss/4.x/allClasses.html
整个工作流程:
JAASRealm authenticates the user and creates a GenericPrincipal with
userPrincipal set to some principal returned by LoginModule.

Later RealmBase.hasResourcePermission() calls request.getUserPrincipal()
to recover authenticated user principal

Request.getUserPrincipal() checks if the principal is instanceof
GenericPrincipal, and if it is, it returns its userPrincipal.

RealmBase.hasRole() checks if the principal is instanceof GenericPrincipal
and if not it fails immediately.

Note: previous versions of JAASRealm had their own hasRole() implementation.

Note: request.isUserInRole() is not getting userPrincipal from
GenericPrincipal when calling realm.hasRole() and this one seems to

work.


星期六, 九月 06, 2008

JBOSS JAAS和tomcat j_security_check CMS关系

JAAS是JAVA强大的安全框架模型,JBOSS 对其的支持也是完全和强劲的。

但JAAS总体来说是基于组件的AAA体系(Authentication(验证) Authorization(授权) Accounting(审计)).

Web应用中,往往是基于角色的安全控制(RBAC Role-Based Access Control)比较多,往往授权关系是树形的和矩阵性的复杂关系。

tomcat实现了一个容器类的范围内的验证系统,也就是所谓的CMS(Container Managed Security),这种方式的好处是比较符合要么全有,

要么全无的安全认证,粒度不如JAAS控制的细致。web.xml通过

<security-constraint>
  <web-resource-collection>

<auth-constraint>

三个标志来控制不同角色访问的内容范围.

JBOSS中支持这两种验证方式,但是就出现了一个比较严重的问题。

两种方式是独立的,有各自的LoginContext上下文,必须通过程序的方式在JAAS模块中对Container来进行验证。JBOSS 4.2.3通过class:

org.jboss.web.tomcat.security.login.WebAuthentication;

来实现了这两者对接。综合了两种方式的有点。

另外一种常见的技术是用login proxy的方式来手工调用jsp让内置的j_security_check起作用来综合JAAS和CMS的有点,这里也加入简单说明

在servlet或JSF的backing bean的action对应的方法中,加入如下语句:
String url="/login.jsp"

        RequestDispatcher  dispatcher = request.getRequestDispatcher(url);
        dispatcher.forward(request, response);
login.jsp包含如下内容:

<html>
    <head>
      <meta
http-equiv="Content-Type"
content="text/html; charset=windows-1252"/>
<title>Logging in</title>
    </head>
      <body
onload="document.forms[0].submit()">
      <form
method="post" action="j_security_check">
<input type="hidden" name="j_username"
value="${param.j_username}"/>
<input type="hidden" name="j_password"
value="${param.j_password}"/>
      </form>
    </body>
  </html>

上面的j_username和j_password都是从上页用户输入后传递过来的

上述的方式比较简陋,所以下面就是程序完全控制的方式。

以一个JSF的完全应用为例子

首先编写:login.jsp

<?xml version="1.0" encoding="UTF-8"?>
<jsp:root version="2.0"
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
>
<jsp:directive.page  contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" />
<html>
    <head>
    <meta http-equiv="Content-Type"
            content="text/html; charset=UTF-8"></meta>
        <title>Login Wizard</title>
    </head>
    <body>

<f:view >
        <h:form >
            <h:outputLabel  value="#{SysRes.username}"/><h:inputText value="#{loginManager.userID}" id="j_username" /><br/>
            <h:outputLabel value="#{SysRes.password}"/><h:inputSecret value="#{loginManager.password}" id="j_password" /><br/>
            <h:commandButton id="checkout" value="${SysRes.OK}" action="#{loginManager.loginAction}" />   
        </h:form>
</f:view>
    </body>   
</html> 
</jsp:root>

 

其次配置faces-config.xml这个是默认的配置文件,可以通过在web.xml中定义改变之,后面举例

<managed-bean>
<managed-bean-name>loginManager</managed-bean-name>
<managed-bean-class>org.security.login.LoginManager</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>

 

三:再看managed-bean,代码如下

package org.security.login;

import java.security.Principal;
import java.util.Set;

import javax.faces.application.Application;
import javax.faces.application.NavigationHandler;
import javax.faces.context.FacesContext;

import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;
import javax.security.auth.login.LoginContext;
import javax.security.auth.callback.CallbackHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import javax.faces.event.ActionEvent;

import javax.security.auth.Subject ;
import javax.servlet.RequestDispatcher;

import org.jboss.security.SimplePrincipal;
import org.jboss.web.tomcat.security.login.WebAuthentication;
import org.security.login.jass.*;

public class LoginManager
{
       private String _uid = "";
        private String _pwd = "";
         private String requestURI="";
        public String getUserID() { return _uid; }
        public void setUserID(String uid) { _uid = uid; }
        public String getPassword() { return _pwd; }
        public void setPassword(String pwd) { _pwd = pwd; }
        public String loginAction() throws Exception
        {
            FacesContext context = FacesContext.getCurrentInstance();
            HttpServletResponse response =
            (HttpServletResponse) context.getExternalContext().getResponse();
            HttpServletRequest request =
                (HttpServletRequest) context.getExternalContext().getRequest();           
        String securityDomain = "seamAC";//安全区域,在logic-config.xml中定义
        CallbackHandler callbackHandler = new StandardCallbackHandler(getUserID(),getPassword());
        try
        {
            LoginContext lc =
            new LoginContext( securityDomain,
                      callbackHandler );
            lc.login();
            WebAuthentication pwl = new WebAuthentication();
            boolean flag=pwl.login(getUserID(),getPassword());
            System.out.println("WebAuthentication is?"+flag);
            System.out.println("User Principal="+request.getUserPrincipal());
        }
        catch ( Exception e )
        {
            e.printStackTrace();
//            FacesContext context = FacesContext.getCurrentInstance();
//            HttpServletResponse response =
//            (HttpServletResponse) context.getExternalContext().getResponse();
//            response.sendError(100);
//            context.responseComplete();
           // FacesContext.getCurrentInstance().addMessage("checkout",new FacesMessage(e.getMessage()));
//            throw e;
            return "loginFail";
        }
        String url=request.getHeader("referer");
        String cUrl=request.getRequestURI();
        //再次获取
        request =(HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();       
        System.out.println("referer:"+url+" curl:"+cUrl);
        System.out.println("用户角色sysAdmin:"+request.isUserInRole("sysAdmin"));
        System.out.println("用户角色guest:"+request.isUserInRole("guest"));

//        Principal principal = new SimplePrincipal(getUserID());
//        ObjectName jaasMgr = new ObjectName("jboss.security:service=JaasSecurityManager");
//        Object[] params = { securityDomain, principal };
//        String[] signature = { "java.lang.String", Principal.class.getName() };
//        MBeanServer server = (MBeanServer) MBeanServerFactory.findMBeanServer(null).get(0);
//        server.invoke(jaasMgr, "flushAuthenticationCache", params, signature);
        if(!(url==null ||url.trim().equals("")))
        {   
            String host=request.getHeader("host");
            String cpath=request.getContextPath();
            url=url.replace("http://"+host+cpath,"");
            System.out.println("url now:"+url+" cpath:"+cpath);           
                response.sendRedirect(cpath+"/"+url);   
        //use j_security_check proxy tech,j_username,j_password
//        RequestDispatcher  dispatcher = request.getRequestDispatcher(url);
//        dispatcher.forward(request, response);
        }       
        return "loginPass";   
        }
        public void logout(){
            FacesContext fc = javax.faces.context.FacesContext.getCurrentInstance();
            ((HttpSession)fc.getExternalContext().getSession(false)).invalidate();
            Application ap = fc.getApplication();
            NavigationHandler nh = ap.getNavigationHandler();
            nh.handleNavigation(fc,null,"logout");
        }

}

 

四:配置web.xml 加入受保护的资源

<!-- 安全部分的配置 -->
<security-constraint>
  <web-resource-collection>
   <web-resource-name>Secure Pages</web-resource-name>
   <url-pattern>/Genesis/*</url-pattern>
   <url-pattern>/Guest/*</url-pattern>  
   <http-method>GET</http-method>
   <http-method>POST</http-method>
   <!-- http-method>HEAD</http-method>
   <http-method>PUT</http-method>
   <http-method>OPTIONS</http-method>
   <http-method>TRACE</http-method>
   <http-method>DELETE</http-method -->
  </web-resource-collection>
<!-- only Admin role can access -->
  <auth-constraint>
<role-name>sysAdmin</role-name>
  </auth-constraint>

</security-constraint>

<!-- 登录验证方式 -->
<login-config>
  <auth-method>FORM</auth-method>
  <realm-name>seamAC</realm-name>
  <form-login-config>
   <form-login-page>/Login.jsp</form-login-page>
   <form-error-page>/Login.jsp</form-error-page>
  </form-login-config>
</login-config>
<!-- Declare all Role List -->
<security-role>
  <role-name>sysAdmin</role-name>
</security-role>
  <security-role>
  <role-name>sysGuest</role-name> 
</security-role>

五:配置jboss相关部分.使用Jboss 4.2.3 GA JDK1.6 update 10
   JAAS部分:conf/login-config.xml,注意seamAC就是LoginManager class中的login方法中引用的,Realm名字.

<!--JAAS  -->
    <application-policy name = "seamAC">
       <authentication>
          <login-module code = "org.jboss.security.auth.spi.DatabaseServerLoginModule"
             flag = "required">
             <module-option name = "unauthenticatedIdentity">guest</module-option>
             <module-option name = "dsJndiName">java:/SecMasterDS</module-option>
             <module-option name = "principalsQuery">select password from sysUser where userid=? </module-option>
             <module-option name = "rolesQuery">SELECT A.roleID as Role,'Roles' FROM sysRoles A,UserRoles B WHERE A.roleID=B.roleID AND B.userId=?</module-option>
             <module-option name = "debug">true</module-option>
<!--Password is md5 encrypt-->
                        <module-option name="hashAlgorithm">MD5</module-option>
            <module-option name ="hashEncoding">HEX</module-option>
          </login-module>
       </authentication>
    </application-policy>

注意:rolesQuery部分,根据Jboss的class org/jboss/security/auth/spi/DatabaseServerLoginModule.java中有关角色部分的说明,必须有一个roleGroup的字段值为Roles,否则出现用户没有角色的错误,小心注意,这是很愚蠢的假定,往往配置了很久,不能工作,就是这些小地方。

  141   /** Overriden by subclasses to return the Groups that correspond to the
142   to the role sets assigned to the user. Subclasses should create at
143   least a Group named "Roles" that contains the roles assigned to the user.
144   A second common group is "CallerPrincipal" that provides the application
145   identity of the user rather than the security domain identity.
146   @return Group[] containing the sets of roles
147   */

数据库部分:deploy/mysql-ds.xml(使用MYSQL 5.1),注意SecMasterDS



<local-tx-datasource>
   <jndi-name>SecMasterDS</jndi-name>
   <connection-url>jdbc:mysql://localhost:3306/gate?useUnicode=TRUE</connection-url>
   <driver-class>com.mysql.jdbc.Driver</driver-class>
   <user-name>root</user-name>
   <password></password>
   <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>
   <metadata>
      <type-mapping>mySQL</type-mapping>
   </metadata>
</local-tx-datasource>

 


支持,繁琐的配置终于可以工作了,访问一个目录,然后经过验证,显示出来内容,实在是非常惬意的事情。更加人性化的界面,比如不具有权限的人向访问一个特定角色的资源,可以在web.xml中截获403错误来做到:



< error-page>
  <error-code>403</error-code>
  <location>/roleerror.jsp</location>
</error-page>

JBOSS JAAS 同JSF 相关问题

如果出现验证后出现,isUserInRole返回false的情况
首先打开
conf/jboss-log4j.xml 的debug.


http://wiki.jboss.org/wiki/SecurityFAQ

<!--Debug log login sql -->
<category name="org.jboss.security">
<priority value="TRACE" class="org.jboss.logging.XLevel"/>
</category>
<category name="org.jboss.web.tomcat.security">
<priority value="TRACE" class="org.jboss.logging.XLevel"/>
</category>
<category name="org.apache.catalina">
<priority value="DEBUG"/>
</category>

星期五, 九月 05, 2008

JSF 或JSP EL(Express Language)中的变量

在JSF和JSP中,EL引用一个变量,语法如下#{class.fieldName}或${class.fieldName}
这里fieldName必须是首字母小写的,但对应的class的方法名为getFieldName()
这种奇怪的语法是来自类 java.beans.PropertyDescriptor 的构造函数规定的
PropertyDescriptor

public PropertyDescriptor(String propertyName,
Class beanClass) throws IntrospectionException

为某特性构造 PropertyDescriptor ,它通过拥有 getFoo 和 setFoo 访问者来遵循标准 Java 约定。因此若参数名是“fred”,则假定读程序方法是“getFred”并且写程序方法是“setFred”。注意特性名应以小写字符开始, 它将在方法名中被大写化。

Note that the property name should start with a lower case character, which will be capitalized in the method names.

星期五, 八月 15, 2008

JSON-Lib版本检查

JSON-Lib是套开源的 JSON库,
目前2.2.x系列是基本没有错误的版本。
1.1系列的问题很大,主要是 不能保存原始数据解析顺序的问题。

本来想寻找版本的信息。但没有发现,不过2.2增加了net.sf.json.JsonConfig类,1.1.x序列的没有
所以可以在代码中加上检查

public static Boolean checkJSONLibVersion()
{
try
{
Class.forName("net.sf.json.JsonConfig",false,ClassLoader.getSystemClassLoader());
}catch(ClassNotFoundException e)
{
return false;//1.1.x版本,解析数据顺序不对。
}
return true; //2.2.x版本
}

星期四, 八月 14, 2008

BLOGGER竟然有一个比较好的来显示代码的tag

blogger内置的标签:

blockquote

only a test
test for what

星期二, 八月 12, 2008

JBOSS 4.2.1 打开Log Trace 开关

1.conf/login-config.xml

加入:

<category name="org.jboss.security">
   <priority value="TRACE"/>
</category>
<category name="org.jboss.web.tomcat.security">
   <priority value="TRACE"/>
</category>
<category name="org.apache.catalina">
   <priority value="TRACE"/>
</category>

2.conf/jboss-log4j.xml,修改system.out Logger

<appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
   <errorHandler class="org.jboss.logging.util.OnlyOnceErrorHandler"/>
   <param name="Target" value="System.out"/>
   <param name="Threshold" value="TRACE"/>

   <layout class="org.apache.log4j.PatternLayout">
      <!-- The default pattern: Date Priority [Category] Message\n -->
      <param name="ConversionPattern" value="%d{ABSOLUTE} %-5p [%c{1}] %m%n"/>
   </layout>
</appender>

星期六, 八月 09, 2008

JAAS:灵活的Java安全机制

John Musser/Paul Feuer著 冯睿编译

ava Authentication Authorization Service(JAAS,Java验证和授权API)提供了灵活和可伸缩的机制来保证客户端或服务器端的Java程序。Java早期的安全框架强调的是通过验证代码的来源和作者,保护用户避免受到下载下来的代码的攻击。JAAS强调的是通过验证谁在运行代码以及他/她的权限来保护系统面受用户的攻击。它让你能够将一些标准的安全机制,例如Solaris NIS(网络信息服务)、Windows NT、LDAP(轻量目录存取协议),Kerberos等通过一种通用的,可配置的方式集成到系统中。本文首先向你介绍JAAS验证中的一些核心部分,然后通过例子向你展示如何开发登录模块。
你是否曾经需要为一个应用程序实现登录模块呢?如果你是一个比较有经验的程序员,相信你这样的工作做过很多次,而且每次都不完全一样。你有可能把你的登录模块建立在Oracle数据库的基础上,也有可能使用的是NT的用户验证,或者使用的是 LDAP目录。如果有一种方法可以在不改变应用程序级的代码的基础上支持上面提到的所有这一些安全机制,对于程序员来说一定是一件幸运的事。
现在你可以使用JAAS实现上面的目标。JAAS是一个比较新的的Java API。在J2SE 1.3中,它是一个扩展包;在J2SE 1.4中变成了一个核心包。在本文中,我们将介绍JAAS的一些核心概念,然后通过例子说明如何将JAAS应用到实际的程序中。本文的例子是根据我们一个基于Web的Java应用程序进行改编的,在这个例子中,我们使用了关系数据库保存用户的登录信息。由于使用了JAAS,我们实现了一个健壮而灵活的登录和身份验证模块。
Java验证和授权:概论
在JAAS出现以前,Java的安全模型是为了满足跨平台的网络应用程序的需要而设计的。在Java早期版本中,Java通常是作为远程代码被使用,例如Applet,。因此最初的安全模型把注意力放在通过验证代码的来源来保护用户上。早期的Java安全机制中包含的概念,如 SercurityManager,沙箱概念,代码签名,策略文件,多是为了保护用户。
JAAS的出现反映了Java的演变。传统的服务器/客户端程序需要实现登录和存取控制,JAAS通过对运行程序的用户的进行验证,从而达到保护系统的目的。虽然JAAS同时具有验证和授权的能力,在这篇文章中,我们主要介绍验证功能。
通过在应用程序和底层的验证和授权机制之间加入一个抽象层,JAAS可以简化涉及到Java Security包的程序开发。抽象层独立于平台的特性使开发人员可以使用各种不同的安全机制,而且不用修改应用程序级的代码。和其他Java Security API相似,JAAS通过一个可扩展的框架:服务提供者接口(Service Provider Interface,SPI)来保证程序独立于安全机制。服务提供者接口是由一组抽象类和接口组成的。图一中给出了JAAS程序的整体框架图。应用程序级的代码主要处理LoginContext。在LoginContext下面是一组动态配置的LoginModules。LoginModule使用正确的安全机制进行验证。
图一给出了JAAS的概览。应用程序层的代码只需要和LoginContext打交道。在LoginContext之下是一组动态配置的LoginModule对象,这些对象使用相关的安全基础结构进行验证操作。

 jt-2003-1-14-image001

                                               图一 JAAS概览

JAAS提供了一些LoginModule的参考实现代码,比如JndiLoginModule。开发人员也可以自己实现LoginModule接口,就象在我们例子中的RdbmsLonginModule。同时我们还会告诉你如何使用一个简单的配置文件来安装应用程序。
为了满足可插接性,JAAS是可堆叠的。在单一登录的情况下,一组安全模块可以堆叠在一起,然后被其他的安全机制按照堆叠的顺序被调用。
JAAS的实现者根据现在一些流行的安全结构模式和框架将JASS模型化。例如可堆叠的特性同Unix下的可堆叠验证模块(PAM,Pluggable Authentication Module)框架就非常相似。从事务的角度看,JAAS类似于双步提交(Two-Phase Commit,2PC)协议的行为。JAAS中安全配置的概念(包括策略文件(Police File)和许可(Permission))来自于J2SE 1.2。JAAS还从其他成熟的安全框架中借鉴了许多思想。

 

客户端和服务器端的JAAS

开发人员可以将JAAS应用到客户端和服务器端。在客户端使用JAAS很简单。在服务器端使用JAAS时情况要复杂一些。目前在应用服务器市场中的 JAAS产品还不是很一致,使用JAAS的J2EE应用服务器有一些细微的差别。例如JBossSx使用自己的结构,将JAAS集成到了一个更大的安全框架中;而虽然WebLogic 6.x也使用了JAAS,安全框架却完全不一样。

现在你能够理解为什么我们需要从客户端和服务器端的角度来看JAAS了。我们将在后面列出两种情况下的例子。为了使服务器端的例子程序更加简单,我们使用了Resin应用服务器。

核心JAAS类

在使用JAAS之前,你首先需要安装JAAS。在J2SE 1.4中已经包括了JAAS,但是在J2SE 1.3中没有。如果你希望使用J2SE 1.3,你可以从SUN的官方站点上下载JAAS。当正确安装了JAAS后,你会在安装目录的lib目录下找到jaas.jar。你需要将该路径加入 Classpath中。(注:如果你安装了应用服务器,其中就已经包括了JAAS,请阅读应用服务器的帮助文档以获得更详细的信息)。在Java安全属性文件java.security中,你可以改变一些与JAAS相关的系统属性。该文件保存在<jre_home>/lib/security目录中。

在应用程序中使用JAAS验证通常会涉及到以下几个步骤:

1. 创建一个LoginContext的实例。

2. 为了能够获得和处理验证信息,将一个CallBackHandler对象作为参数传送给LoginContext。

3. 通过调用LoginContext的login()方法来进行验证。

4. 通过使用login()方法返回的Subject对象实现一些特殊的功能(假设登录成功)。

下面是一个简单的例子:

LoginContext lc = new LoginContext("MyExample");
try {
lc.login();
} catch (LoginException) {
// Authentication failed.
}

// Authentication successful, we can now continue.
// We can use the returned Subject if we like.
Subject sub = lc.getSubject();
Subject.doAs(sub, new MyPrivilegedAction());


在运行这段代码时,后台进行了以下的工作。

1. 当初始化时,LoginContext对象首先在JAAS配置文件中找到MyExample项,然后更具该项的内容决定该加载哪个LoginModule对象(参见图二)。

2. 在登录时,LoginContext对象调用每个LoginModule对象的login()方法。

3. 每个login()方法进行验证操作或获得一个CallbackHandle对象。

4. CallbackHandle对象通过使用一个或多个CallBack方法同用户进行交互,获得用户输入。

5. 向一个新的Subject对象中填入验证信息。

我们将对代码作进一步的解释。但是在这之前,让我们先看代码中涉及到的核心JAAS类和接口。这些类可以被分为三种类型:

普通类型 Subject,Principal,凭证

验证 LoginContext,LoginModule,CallBackHandler,Callback

授权 Policy,AuthPermission,PrivateCredentialPermission

上面列举的类和接口大多数都在javax.security.auth包中。在J2SE 1.4中,还有一些接口的实现类在com.sun.security.auth包中。

普通类型:Subject,Principal,凭证

Subject类代表了一个验证实体,它可以是用户、管理员、Web服务,设备或者其他的过程。该类包含了三中类型的安全信息:

身份(Identities):由一个或多个Principal对象表示

公共凭证(Public credentials):例如名称或公共秘钥

私有凭证(Private credentials):例如口令或私有密钥

Principal对象代表了Subject对象的身份。它们实现了java.security.Principal和 java.io.Serializable接口。在Subject类中,最重要的方法是getName()。该方法返回一个身份名称。在Subject对象中包含了多个Principal对象,因此它可以拥有多个名称。由于登录名称、身份证号和Email地址都可以作为用户的身份标识,可见拥有多个身份名称的情况在实际应用中是非常普遍的情况。

在上面提到的凭证并不是一个特定的类或借口,它可以是任何对象。凭证中可以包含任何特定安全系统需要的验证信息,例如标签(ticket),密钥或口令。Subject对象中维护着一组特定的私有和公有的凭证,这些凭证可以通过 getPrivateCredentials()和getPublicCredentials()方法获得。这些方法通常在应用程序层中的安全子系统被调用。

验证:LoginContext

在应用程序层中,你可以使用LoginContext对象来验证Subject对象。LoginContext对象同时体现了JAAS的动态可插入性(Dynamic Pluggability),因为当你创建一个LoginContext的实例时,你需要指定一个配置。LoginContext通常从一个文本文件中加载配置信息,这些配置信息告诉LoginContext对象在登录时使用哪一个LoginModule对象。

下面列出了在LoginContext中经常使用的三个方法:

login () 进行登录操作。该方法激活了配置中制定的所有LoginModule对象。如果成功,它将创建一个经过了验证的Subject对象;否则抛出LoginException异常。

getSubject () 返回经过验证的Subject对象

logout () 注销Subject对象,删除与之相关的Principal对象和凭证

验证:LoginModule

LoginModule是调用特定验证机制的接口。J2EE 1.4中包含了下面几种LoginModule的实现类:

JndiLoginModule 用于验证在JNDI中配置的目录服务

Krb5LoginModule 使用Kerberos协议进行验证

NTLoginModul 使用当前用户在NT中的用户信息进行验证

UnixLoginModule 使用当前用户在Unix中的用户信息进行验证

同上面这些模块绑定在一起的还有对应的Principal接口的实现类,例如NTDomainPrincipal和UnixPrincipal。这些类在com.sun.security.auth包中。

LoginModule接口中包含了五个方法:

initialize () 当创建一LoginModule实例时会被构造函数调用

login () 进行验证

commit () 当LgoninContext对象接受所有LoginModule对象传回的结果后将调用该方法。该方法将Principal对象和凭证赋给Subject对象。

abort () 当任何一个LoginModule对象验证失败时都会调用该方法。此时没有任何Principal对象或凭证关联到Subject对象上。

logout () 删除与Subject对象关联的Principal对象和凭证。

在应用程序的代码中,程序员通常不会直接调用上面列出的方法,而是通过LigonContext间接调用这些方法。

验证:CallbackHandler和Callback

CallbackHandler和Callback对象可以使LoginModule对象从系统和用户那里收集必要的验证信息,同时独立于实际的收集信息时发生的交互过程。

JAAS在javax.sevurity.auth.callback包中包含了七个Callback的实现类和两个CallbackHandler的实现类:ChoiceCallback、ConfirmationCallback、LogcaleCallback、NameCallback、 PasswordCallback、TextInputCallback、TextOutputCallback、 DialogCallbackHandler和TextCallBackHandler。Callback接口只会在客户端会被使用到。我将在后面介绍如何编写你自己的CallbackHandler类。

配置文件

上面我已经提到,JAAS的可扩展性来源于它能够进行动态配置,而配置信息通常是保存在文本。这些文本文件有很多个配置块构成,我们通常把这些配置块称作申请(Application)。每个申请对应了一个或多个特定的LoginModule对象。

当你的代码构造一个LoginContext对象时,你需要把配置文件中申请的名称传递给它。LoginContext将会根据申请中的信息决定激活哪些LoginModule对象,按照什么顺序激活以及使用什么规则激活。

配置文件的结构如下所示:

Application {
ModuleClass Flag ModuleOptions;
ModuleClass Flag ModuleOptions;
...
};
Application {
ModuleClass Flag ModuleOptions;
...
};
...

下面是一个名称为Sample的申请

Sample {
com.sun.security.auth.module.NTLoginModule Rquired debug=true;
}


上面这个简单的申请指定了LoginContext对象应该使用NTLoginModule进行验证。类的名称在ModuleClass中被指定。 Flag控制当申请中包含了多个LoginModule时进行登录时的行为:Required、Sufficient、Requisite和 Optional。最常用的是Required,使用它意味着对应的LoginModule对象必须被调用,并且必须需要通过所有的验证。由于Flag本身的复杂性,本文在这里不作深究。

ModuleOption允许有多个参数。例如你可以设定调试参数为True(debug=true),这样诊断输出将被送到System.out中。

配置文件可以被任意命名,并且可以被放在任何位置。JAAS框架通过使用java.securty.auth.long.config属性来确定配置文件的位置。例如当你的应用程序是JaasTest,配置文件是当前目录下的jaas.config,你需要在命令行中输入:

java -Djava.security.auth.login.config=jass.config JavaTest

图二描述了配置文件中各元素之间的关系

 jt-2003-1-14-image002

                    图二 JAAS的配置文件

通过命令行方式进行登录验证

为了说明JAAS到底能干什么,我在这里编写了两个例子。一个是简单的由命令行输入调用的程序,另一个是服务器端的JSP程序。这两个程序都通过用户名/密码的方式进行登录,然后使用关系数据库对其进行验证。

为了通过数据库进行验证,我们需要:

1. 实现RdbmsLoginModul类,该类可以对输入的信息进行验证。

2. 编辑一个配置文件,告诉LoginContext如何使用RdbmsLoginModule。

3. 实现ConsoleCallbackHandler类,通过该类可以获取用户的输入。

4. 编写应用程序代码。

在RdbmsLoginModul类中,我们必须实现LgoinModule接口中的五个方法。首先是initialize()方法:

public void initialize(Subject subject, CallbackHandler
callbackHandler,

Map sharedState, Map options)
{
this.subject = subject;
this.callbackHandler = callbackHandler;
this.sharedState = sharedState;
this.options = options;

url = (String)options.get("url");
driverClass = (String)options.get("driver");
debug = "true".equalsIgnoreCase((String)options.get("debug"));
}

LoginContext在调用login()方法时会调用initialize()方法。RdbmsLoginModule的第一个任务就是在类中保存输入参数的引用。在验证成功后将向Subject对象中送入Principal对象和凭证。

CallbackHandler对象将会在login()方法中被使用到。sharedState可以使数据在不同的LoginModule对象之间共享,但是在这个例子中我们不会使用它。最后是名为options的Map对象。options向LgoinModule对象传递在配置文件 ModuleOption域中定义的参数的值。配置文件如下所示:

Example {
RdbmsLoginModule required
driver="org.gjt.mm.mysql.Driver"
url="jdbc:mysql://localhost/jaasdb?user=root"
debug="true";
};

在配置文件中,RdbmsLoginModule包含了五个参数,其中driver、url、user和password是必需的,而debug是可选阐述。driver、url、user和password参数告诉我们如何获得JDBC连接。当然你还可以在ModuleOption域中加入数据库中的表或列的信息。使用这些参数的目的是为了能够对数据库进行操作。在LoginModule类的initialize()方法中我们保存了每个参数的值。

我们前面提到一个LoginContext对应的配置文件告诉它应该使用文件中的哪一个申请。这个信息是通过LgoinContext的构造函数传递的。下面是初始化客户端的代码,在代码中创建了一个LoginContex对象并调用了login()方法。

ConsoleCallbackHandler cbh = new ConsoleCallbackHandler();
LoginContext lc = new LoginContext("Example", cbh);
lc.login();

当LgoinContext.login()方法被调用时,它调用所有加载了的LoginModule对象的login()方法。在我们的这个例子中是RdbmsLoginModule中的login()方法。

RdbmsLoginModule中的login()方法进行了下面的操作:

1. 创建两个Callback对象。这些对象从用户输入中获取用户名/密码。程序中使用了JAAS中的两个Callback类:NameCallback和 PasswordCallback(这两个类包含在javax.security.auth.callback包中)。

2. 通过将callbacks作为参数传递给CallbackHandler的handle()方法来激活Callback。

3. 通过Callback对象获得用户名/密码。

4. 在rdbmsValidate()方法中通过JDBC在数据库中验证获取的用户名/密码。

下面是RdbmsLoginModule中的login()方法的代码

public boolean login() throws LoginException {
if (callbackHandler == null)
throw new LoginException("no handler");

NameCallback nameCb = new NameCallback("user: ");
PasswordCallback passCb = new PasswordCallback("password: ", true);
callbacks = new Callback[] { nameCb, passCb };
callbackHandler.handle(callbacks);

String username = nameCb.getName();
String password = new String(passCb.getPassword());
success = rdbmsValidate(username, password);

return(true);
}

在ConsoleCallbackHandler类的handle()方法中你可以看到Callback对象是如何同用户进行交互的:

public void handle(Callback[] callbacks)
throws java.io.IOException, UnsupportedCallbackException {

for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
NameCallback nameCb = (NameCallback)callbacks[i];
System.out.print(nameCb.getPrompt());
String user=(new BufferedReader(new

InputStreamReader(System.in))).readLine();
nameCb.setName(user);
} else if (callbacks[i] instanceof PasswordCallback) {
PasswordCallback passCb = (PasswordCallback)callbacks[i];
System.out.print(passCb.getPrompt());
String pass=(new BufferedReader(new

InputStreamReader(System.in))).readLine();
passCb.setPassword(pass.toCharArray());
} else {
throw(new UnsupportedCallbackException(callbacks[i],
"Callback class not supported"));
}
}
}