- 浏览: 112808 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
honey_fansy:
能讲讲为什么要用final修饰吗?还是说不一定?我看了好几段代 ...
关于ThreadLocal的内存泄露 -
heipacker:
czpsailer 写道为啥这么多人投隐藏,如果LZ那里说的不 ...
关于ThreadLocal的内存泄露 -
r361251:
不懂,貌似好高级的样子。
机器学习常用方法 -
yangyi:
jackyin5918 写道个人认为, 内部的ThreadLo ...
关于ThreadLocal的内存泄露 -
jackyin5918:
yangyi 写道jackyin5918 写道C_J 写道- ...
关于ThreadLocal的内存泄露
这篇随笔谈一谈如何在Java环境下利用Unix/Linux的用户名和密码对用户的权限作出过滤。为方便大家学习交流,本文中给出了源代码,借此抛砖引玉,欢迎大家对这个简单的登录模型做出改进或者设计出自己的技术方案。
由标题我们不难看出,与本文相关的知识点主要有3个:
1 JAAS这个解耦设计的多层验证方法(1.4后已归入Java核心库中)
2 应用JNI访问底层代码,及JNI中简单的类型匹配
3 在shadow模式下,Unix/Linux系统的用户验证
首先聊聊JAAS,顾名思义,JAAS由认证和授权两个主要组件组成。JAAS的交互点在LoginContext这个类里面,在构造LoginContext时,常常需要指定两个内容(还有其他默认的构造子重载形式),这两个内容是LoginModule的名字和Subject。
为使描述直观,先给出代码如下:
先说LoginModule的名字,在系统属性java.security.auth.login.config中(或者在jre/lib/security/java.security)指定了LoginModule的配置文件,LoginModule在Java中被定义成一个接口,这个地方应用了面向对象的依赖倒置原则,使用了类似JDBC这样的SPI的机制来定制认证策略。根据用户在构造LoginContext时指定的LoginModule的名字Java在系统环境中找到对应的LoginModule配置文件,这个配置文件的最简单形式如下:
mylogin {
UnixLoginModule required
};
这时当我们应用mylogin这个名字实例化LoginContext的时候,系统就会自动的找到UnixLoginModule这个LoginModule去处理。后面的required是一个flag标志,表示此次验证的关键性,有4个值可以选择,当选定required时则表示必须成功,由此我们就可以定义一系列的验证,形成一个过滤层,并根据不同的flag得出最后的结论,比如:我们可能希望我们的web用户只要通过数据库的验证,而不必通过操作系统的验证。
此外我们还可以设置一些其他的参数(以key=value的形式),而且实际上验证是两阶段提交的,并且可以通过回调函数的形式在具体的认证平台上做一些个性化Context设置。对这些JAAS细节感兴趣的朋友可以读相应的JAAS文档规范。
再说第二个参数Subject,这个主题封装了用户需要验证的信息,主要包括principal和公钥私钥两部分,详细的设置方法可以参考上面的代码。
lc.login返回了一个true或false表示了这次的验证是否成功。
当一个Subject成功login后,就可以通过这个Subject做一些特许的动作Subject.doAs,这些动作根据Subject中principal的不同在com.sun.security.auth.PolicyFile指定的配置文件做了定义,这部分是属于JAAS授权的内容,因为在我们的程序中暂时用不到,所以不做详细讨论了,我们仅仅根据login返回的true或false来决定用户是否可以登录我们的系统即可。
OK,说到这里,我们给出UnixLoginModule的实现代码:
代码中的各个方法是LoginModule所定义的必须实现的方法。注意到代码中,我们应用了PasswdCheck.check(this.usr, this.passwd)来做最后的验证,这是因为对Unix系统用户的验证必须调用系统API才可以,而系统API是以C的形式提供的,因此我们需要借助JNI。现在我们看看PasswdCheck这个类:
在这里用到了JNI来调用底层的用户名密码验证方案,为此我们需要构造出libpasswd.so这个库。
一步一步来:
1 用javah生成JNI的头文件:
javah PasswdCheck
得到如下代码:
现在把头文件中定义的函数实现:
在jni.h这个头文件中定义了jni和c之间的类型关系,通过分析,用户名密码的字符串可以通过如下函数获取:
char * username =(*env)->GetStringUTFChars(env, usr, NULL);
char * password =(*env)->GetStringUTFChars(env, psw, NULL);
其他的简单型别很多被直接typedef了。
我们真对生成的头文件,实现如下:
上面的实现中的结构体spwd定义如下:
getspnam函数可以获取一个被单向加密后的密码(有4种可选加密形式)
crypt函数把我们的原始密码按相同密钥和算法加密后,即可通过比较加密后字符串的形式获取是否密码正确的信息。需要主义的是只有在使用shadow机制的系统中才应用getspnam,如果/etc/passwd直接描述了密码,则可以通过函数getpwnam来获取(或者直接解析文本),这时一般采用的是13位的DES加密,问题变得简单。
在编写完实现后通过命令
gcc -lcrypt PasswdCheck.c -shared -o libpasswd.so
进行编译,把这个库cp到/usr/lib(或其他ld_library_path)下就可以用平台相关的方式System.loadLibrary加载,否则就要用系统绝对路径名了(利用System.load)
因为只有root能获取到getspnam,所以我们只能这样来执行我们的java进行验证,sudo java MyLogin (yiyang is in group wheel defined in /etc/sudoers)
否则将得到如下出错信息:
当然,如果我们不用JNI,而采用Web Services(具体方法见笔者上一篇blog: http://yangyi.blogjava.net)那么可以通过set suid的形式定制一个进程了(不过这已经是另一个话题),毕竟用root启动tomcat不是很让人放心:
chown root XXX
chmod +s XXX
Anyway, 至此通过JAAS认证Unix用户的基本思路就描述完了,读者可以填补其中的漏洞并把JAAS用到自己的工作场景中。
由标题我们不难看出,与本文相关的知识点主要有3个:
1 JAAS这个解耦设计的多层验证方法(1.4后已归入Java核心库中)
2 应用JNI访问底层代码,及JNI中简单的类型匹配
3 在shadow模式下,Unix/Linux系统的用户验证
首先聊聊JAAS,顾名思义,JAAS由认证和授权两个主要组件组成。JAAS的交互点在LoginContext这个类里面,在构造LoginContext时,常常需要指定两个内容(还有其他默认的构造子重载形式),这两个内容是LoginModule的名字和Subject。
为使描述直观,先给出代码如下:
<!---->import java.security.Principal;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
public class MyLogin {
public MyLogin(){
Subject subject = new Subject();
subject.getPrincipals().add(new Principal(){
public String getName() {
return "yiyang";
}
});
subject.getPrivateCredentials().add("sh0w00f");
try {
LoginContext lc = new LoginContext("mylogin",subject);
lc.login();
} catch (LoginException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
new MyLogin();
}
}
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
public class MyLogin {
public MyLogin(){
Subject subject = new Subject();
subject.getPrincipals().add(new Principal(){
public String getName() {
return "yiyang";
}
});
subject.getPrivateCredentials().add("sh0w00f");
try {
LoginContext lc = new LoginContext("mylogin",subject);
lc.login();
} catch (LoginException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
new MyLogin();
}
}
先说LoginModule的名字,在系统属性java.security.auth.login.config中(或者在jre/lib/security/java.security)指定了LoginModule的配置文件,LoginModule在Java中被定义成一个接口,这个地方应用了面向对象的依赖倒置原则,使用了类似JDBC这样的SPI的机制来定制认证策略。根据用户在构造LoginContext时指定的LoginModule的名字Java在系统环境中找到对应的LoginModule配置文件,这个配置文件的最简单形式如下:
mylogin {
UnixLoginModule required
};
这时当我们应用mylogin这个名字实例化LoginContext的时候,系统就会自动的找到UnixLoginModule这个LoginModule去处理。后面的required是一个flag标志,表示此次验证的关键性,有4个值可以选择,当选定required时则表示必须成功,由此我们就可以定义一系列的验证,形成一个过滤层,并根据不同的flag得出最后的结论,比如:我们可能希望我们的web用户只要通过数据库的验证,而不必通过操作系统的验证。
此外我们还可以设置一些其他的参数(以key=value的形式),而且实际上验证是两阶段提交的,并且可以通过回调函数的形式在具体的认证平台上做一些个性化Context设置。对这些JAAS细节感兴趣的朋友可以读相应的JAAS文档规范。
再说第二个参数Subject,这个主题封装了用户需要验证的信息,主要包括principal和公钥私钥两部分,详细的设置方法可以参考上面的代码。
lc.login返回了一个true或false表示了这次的验证是否成功。
当一个Subject成功login后,就可以通过这个Subject做一些特许的动作Subject.doAs,这些动作根据Subject中principal的不同在com.sun.security.auth.PolicyFile指定的配置文件做了定义,这部分是属于JAAS授权的内容,因为在我们的程序中暂时用不到,所以不做详细讨论了,我们仅仅根据login返回的true或false来决定用户是否可以登录我们的系统即可。
OK,说到这里,我们给出UnixLoginModule的实现代码:
<!---->import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
public class UnixLoginModule implements LoginModule {
private String usr, passwd;
public boolean abort() throws LoginException {
return false;
}
public boolean commit() throws LoginException {
System.out.println("Passing final confirmation\ndone");
return true;
}
public boolean login() throws LoginException {
;
if (PasswdCheck.check(this.usr, this.passwd) == 0) {
System.out.println("Your Login Succeed");
return true;
}
System.out.println("Your Login failed");
return false;
}
public boolean logout() throws LoginException {
return false;
}
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map<String, ?> sharedState, Map<String, ?> options) {
this.passwd = (String) subject.getPrivateCredentials().iterator()
.next();
this.usr = (String) subject.getPrincipals().iterator().next().getName();
}
}
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
public class UnixLoginModule implements LoginModule {
private String usr, passwd;
public boolean abort() throws LoginException {
return false;
}
public boolean commit() throws LoginException {
System.out.println("Passing final confirmation\ndone");
return true;
}
public boolean login() throws LoginException {
;
if (PasswdCheck.check(this.usr, this.passwd) == 0) {
System.out.println("Your Login Succeed");
return true;
}
System.out.println("Your Login failed");
return false;
}
public boolean logout() throws LoginException {
return false;
}
public void initialize(Subject subject, CallbackHandler callbackHandler,
Map<String, ?> sharedState, Map<String, ?> options) {
this.passwd = (String) subject.getPrivateCredentials().iterator()
.next();
this.usr = (String) subject.getPrincipals().iterator().next().getName();
}
}
代码中的各个方法是LoginModule所定义的必须实现的方法。注意到代码中,我们应用了PasswdCheck.check(this.usr, this.passwd)来做最后的验证,这是因为对Unix系统用户的验证必须调用系统API才可以,而系统API是以C的形式提供的,因此我们需要借助JNI。现在我们看看PasswdCheck这个类:
<!---->public class PasswdCheck {
static{
System.out.println(System.getProperty("java.library.path"));
Runtime.getRuntime().load("/home/yiyang/eclipse/workspace/JAAS/libpasswd.so");
}
public native static int check(String usr, String passwd);
}
static{
System.out.println(System.getProperty("java.library.path"));
Runtime.getRuntime().load("/home/yiyang/eclipse/workspace/JAAS/libpasswd.so");
}
public native static int check(String usr, String passwd);
}
在这里用到了JNI来调用底层的用户名密码验证方案,为此我们需要构造出libpasswd.so这个库。
一步一步来:
1 用javah生成JNI的头文件:
javah PasswdCheck
得到如下代码:
<!---->/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class PasswdCheck */
#ifndef _Included_PasswdCheck
#define _Included_PasswdCheck
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: PasswdCheck
* Method: check
* Signature: (Ljava/lang/String;Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_PasswdCheck_check
(JNIEnv *, jclass, jstring, jstring);
#ifdef __cplusplus
}
#endif
#endif
#include <jni.h>
/* Header for class PasswdCheck */
#ifndef _Included_PasswdCheck
#define _Included_PasswdCheck
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: PasswdCheck
* Method: check
* Signature: (Ljava/lang/String;Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_PasswdCheck_check
(JNIEnv *, jclass, jstring, jstring);
#ifdef __cplusplus
}
#endif
#endif
现在把头文件中定义的函数实现:
在jni.h这个头文件中定义了jni和c之间的类型关系,通过分析,用户名密码的字符串可以通过如下函数获取:
char * username =(*env)->GetStringUTFChars(env, usr, NULL);
char * password =(*env)->GetStringUTFChars(env, psw, NULL);
其他的简单型别很多被直接typedef了。
我们真对生成的头文件,实现如下:
<!---->#include "PasswdCheck.h"//生成的头文件
#include "pwd.h"//getspnam
#include "stdio.h"
#include "unistd.h"//crypt必需
#include "shadow.h"//getspnam
#define _XOPEN_SOURCE//crypt必需
JNIEXPORT jint JNICALL Java_PasswdCheck_check
(JNIEnv * env, jclass jc, jstring usr, jstring psw){
char * username =(*env)->GetStringUTFChars(env, usr, NULL);
char * password =(*env)->GetStringUTFChars(env, psw, NULL);
struct spwd * sp = getspnam(username);
char* p;
p = crypt(password, sp->sp_pwdp);
return strcmp(sp->sp_pwdp,p);
}
#include "pwd.h"//getspnam
#include "stdio.h"
#include "unistd.h"//crypt必需
#include "shadow.h"//getspnam
#define _XOPEN_SOURCE//crypt必需
JNIEXPORT jint JNICALL Java_PasswdCheck_check
(JNIEnv * env, jclass jc, jstring usr, jstring psw){
char * username =(*env)->GetStringUTFChars(env, usr, NULL);
char * password =(*env)->GetStringUTFChars(env, psw, NULL);
struct spwd * sp = getspnam(username);
char* p;
p = crypt(password, sp->sp_pwdp);
return strcmp(sp->sp_pwdp,p);
}
上面的实现中的结构体spwd定义如下:
<!---->struct spwd {
char *sp_namp; /* user login name */
char *sp_pwdp; /* encrypted password */
long int sp_lstchg; /* last password change */
long int sp_min; /* days until change allowed. */
long int sp_max; /* days before change required */
long int sp_warn; /* days warning for expiration */
long int sp_inact; /* days before account inactive */
long int sp_expire; /* date when account expires */
unsigned long int sp_flag; /* reserved for future use */
}
char *sp_namp; /* user login name */
char *sp_pwdp; /* encrypted password */
long int sp_lstchg; /* last password change */
long int sp_min; /* days until change allowed. */
long int sp_max; /* days before change required */
long int sp_warn; /* days warning for expiration */
long int sp_inact; /* days before account inactive */
long int sp_expire; /* date when account expires */
unsigned long int sp_flag; /* reserved for future use */
}
getspnam函数可以获取一个被单向加密后的密码(有4种可选加密形式)
crypt函数把我们的原始密码按相同密钥和算法加密后,即可通过比较加密后字符串的形式获取是否密码正确的信息。需要主义的是只有在使用shadow机制的系统中才应用getspnam,如果/etc/passwd直接描述了密码,则可以通过函数getpwnam来获取(或者直接解析文本),这时一般采用的是13位的DES加密,问题变得简单。
在编写完实现后通过命令
gcc -lcrypt PasswdCheck.c -shared -o libpasswd.so
进行编译,把这个库cp到/usr/lib(或其他ld_library_path)下就可以用平台相关的方式System.loadLibrary加载,否则就要用系统绝对路径名了(利用System.load)
因为只有root能获取到getspnam,所以我们只能这样来执行我们的java进行验证,sudo java MyLogin (yiyang is in group wheel defined in /etc/sudoers)
否则将得到如下出错信息:
<!---->#
# An unexpected error has been detected by Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=0xb7ef95ad, pid=9726, tid=3084450720
#
# Java VM: Java HotSpot(TM) Client VM (1.6.0_03-b05 mixed mode)
# Problematic frame:
# C [libpasswd.so+0x5ad] Java_PasswdCheck_check+0x61
#
# An error report file with more information is saved as hs_err_pid9726.log
#
# If you would like to submit a bug report, please visit:
# http://java.sun.com/webapps/bugreport/crash.jsp
#
# An unexpected error has been detected by Java Runtime Environment:
#
# SIGSEGV (0xb) at pc=0xb7ef95ad, pid=9726, tid=3084450720
#
# Java VM: Java HotSpot(TM) Client VM (1.6.0_03-b05 mixed mode)
# Problematic frame:
# C [libpasswd.so+0x5ad] Java_PasswdCheck_check+0x61
#
# An error report file with more information is saved as hs_err_pid9726.log
#
# If you would like to submit a bug report, please visit:
# http://java.sun.com/webapps/bugreport/crash.jsp
#
当然,如果我们不用JNI,而采用Web Services(具体方法见笔者上一篇blog: http://yangyi.blogjava.net)那么可以通过set suid的形式定制一个进程了(不过这已经是另一个话题),毕竟用root启动tomcat不是很让人放心:
chown root XXX
chmod +s XXX
Anyway, 至此通过JAAS认证Unix用户的基本思路就描述完了,读者可以填补其中的漏洞并把JAAS用到自己的工作场景中。
发表评论
-
profiling java applications with jvisualvm
2011-09-29 18:52 9671) Open jvisualvm, which is bun ... -
static import
2011-05-22 10:51 1794Static import支持略去类型名的public sta ... -
JPA的一些知识(Entity)
2011-05-21 22:54 4238何为Entity Java EE规范中 ... -
关于Java中的编码
2011-04-26 17:30 960编码转换,实际就是把byte数组转换为char数组的过程,或者 ... -
commons-net FTPClient API存取设计
2010-07-07 22:51 1627文件系统无非就是文件的存取和组织结构。 访问一个文件系统的AP ... -
Spring Security 2 中动态角色实现的讨论
2009-03-04 13:04 1435安全框架的主体包括两 ... -
富客户端技术中的JavaScript脚本国际化
2008-12-24 13:31 1183当前的富客户端可以包 ... -
windows中不能双击打开jar文件的解决办法
2008-12-22 18:23 2942看此文前请保证jar包中有至少一个Main方法入口,及图形化的 ... -
JSON通用服务端处理
2008-11-24 18:18 1981最近在学习JavaScript,发现不论是ext还是proto ... -
[译]JDBC4.0具有哪些新特性?
2007-04-24 16:25 1385http://www.blogjava.net/yangyi/ ... -
myeclipse
2007-12-28 11:39 1256刚看了myeclipse,eclipse是一个很可怕的东西,它 ... -
如何实现包含插件功能的Applet Web界面
2008-01-02 15:07 1529不知诸位有没有想过用Applet来组织Web的程序界面?小弟最 ... -
如何学习spring
2008-01-16 10:19 2087学习这些框架技术,我觉得归根结底就是做什么的,为什么做,如何做 ... -
延迟加载技术及其在iBATIS中的实现
2007-12-09 19:56 1536O/R映射框架的延迟加载技术实现大体上有这么4种(参看Mart ... -
浅谈Java中的通信机制及与C/C++ API的集成
2007-12-06 21:05 2723背景: 对于旧有系统的 ... -
Apache commons-Email中文问题的解决办法
2006-12-12 19:15 2345Apache commons-email是对javamailA ... -
如何应用Hibernate在运行期获取属性的值
2006-12-12 19:11 1197java 代码 import java.lan ... -
如何用java做数组乱序?
2006-12-12 19:10 4734前些天用java做了一个数组乱序,首先建立快速排序算法,排序的 ... -
Spring Ioc值得注意的两个特性
2006-12-12 19:07 15321 Spring支持生命周期的回调,通过在bean配置属性中增 ... -
数字验证码小图生成程序
2006-12-12 18:44 1538做了一个登陆验证码的生成小程序,或许对大家有用。支持背景图和文 ...
相关推荐
JAAS JaasDemo JAAS例子
JAAS(Java Authentication and Authentication Service Jay,认证和授权服务)是Java安全编程的一个重要补 充。它提供了根据验证主题身份来验证主题和授权资源访问的标准方法。本文较全面地介绍了JAAS的特点、组成 ...
java JAAS登陆验证
Java安全框架最初集中在保护用户运行潜在的不可信任代码,是基于代码的来源(URL)和谁创建的代码(certificate)来给移动代码进行授权。...JAAS已经整合进了Java 2 SDK 1.4,作为标准的用户认证与授权模型。
jaas实现,单纯java文件
JAAS是对原有Java 2安全框架的一个重要补充。本文较全面地介绍了JAAS的特点、组成及其在Jay。安全编程中的应 用。并简单分析了一个例子,该例经扩充后可直接应用于实践
JAAS 强调的是通过验证谁在运行代码以及他/她的权限来保护系统面受用户的攻击。它让你能够将一些标准的安全机制,例如 Solaris NIS(网络信息服务)、Windows NT、LDAP(轻量目录存取协议),Kerberos等通过一种...
AAS灵活的Java安全机制
jaas 入门经典实例,jaas 入门经典实例,jaas 入门经典实例jaas 入门经典实例,jaas 入门经典实例
以完整的代码讲解JAAS( JAVA验证与授权服务)
针对目前 Java技术的广泛应用, 特别是很多基于 J2EE 平台的...份认证技术 JAAS(Java Authentication Authorization Service)。由于身份认证和授权与安全机制密切相关, 同时也对 Java的安 全及权限访问控制做了一些阐述
JAAS认证与授权教程,包含本教程的源代码
WebSphere Applicatoin Server 7.0 基于独立定制注册表方式实现单点登录,及身份权限认证。 关键词:Java WAS7.0 、独立定制注册表、SSO、JAAS、j_security_check、基于数据库认证单点登录。 关于Java单点登录虽然...
JAAS简介及实例.JAAS简介及实例.JAAS简介及实例.JAAS简介及实例.JAAS简介及实例.JAAS简介及实例.JAAS简介及实例.
tomcat下jaas配置实例(文档有不完善的地方) 1、需要修改 bin\startup.bat(根据自己的环境修改) SET JAVA_HOME=C:\programs\Java\jdk1.8.0_211 SET TOMCAT_HOME=C:\programs\apache-tomcat-5.5.20 2、需要修改 bin...
文件中包含 已配置好的tomcat7和jass示例 打包部署后即可运行 还有一份说明文档 tomcat配置的是mac版的,如果是windows的系统请自行修改tomcat启动参数
JAAS 本身包括文档和一个lib 目录,其中只有一个jar 文件(jaas.jar)。
Java Authentication Authorization Service(JAAS,Java验证和授权API)提供了灵活和可伸缩的机制来保证客户端或服务器端的Java程序
JAAS 例子代码,包括如何实现Kerberos Login,执行Action,以及实现Kerberos Delegation.