JavaSecurity和JAAS——Java标准安全体系概述(下)

java标准安全体系分为两大部分,一个是在JDK1.0引入并在JDK2进行了重构的代表着以代码为中心的授权体系。此体系下,关注的重点在于“这段代码能访问哪些系统资源”;另一个是在JDK1.3以扩展的形式引入,并在JDK1.4作为核心集成进来的以用户为中心的认证与授权体系JAAS。此时,关注的重点变成了“运行这段代码的用户的访问权限是什么”。其中JAAS是在java security基础上对组件进行了增强和扩展。
接下来,将分别介绍两种体系的整体概念及使用样例,并对其中的核心算法及技术要点进行分析讨论。

第二部分 JAAS——以为用户中心的认证授权体系

2.1 使用样例

JAAS规范中认证和授权逻辑上分为两部分,其中认证部分使用可插拔风格,这可以使得应用程序独立于底层使用的认证技术,无需修改应用程序就可以替换底层的认证技术。
JAAS授权部分同Java访问控制模型一起保护对于敏感资源的访问许可,对java security architecture基于代码的授权体系组件进行了加强和扩展,使其不仅基于代码还基于运行该代码的用户身份来决定是否允许对敏感资源的访问。
下面是一个JAAS认证与授权的简单示例。说明:本例使用maven管理jar包及开发环境中项目之间的依赖,若没有使用maven,则需要自行管理项目之间的依赖。例如:在普通java project环境中可以将项目X打成jar包然后引入到项目Y中。

2.1.1 认证(Authentication)
2.1.1.1 实现并使用自定义的认证模块
a. 创建项目sampleLoginModule
创建maven项目sampleLoginModule,在pom.xml中定义其groupId、artifactId、version如下:

<groupId>security.jaas.test</groupId>
 <artifactId>loginModule</artifactId>
 <version>0.0.1-SNAPSHOT</version>

创建类SamplePrincipal实现接口Principal:

public class SamplePrincipal implements Principal {
	  private String name;
	  public SamplePrincipal(String name) {
	    this.name = name;
	  }
	  public String getName() {
	    return name;
	  }
	  public boolean equals(Object o) {
	    return (o instanceof SamplePrincipal)
	        && this.name.equalsIgnoreCase(((SamplePrincipal) o).name);
	  }
	  public int hashCode() {
	    return name.toUpperCase().hashCode();
	  }
}


然后创建类SampleLoginModule实现接口LoginModule:
public class SampleLoginModule implements LoginModule {
	  private boolean isAuthenticated = false;
	  private CallbackHandler callbackHandler;
	  private Subject subject;
	  private SamplePrincipal principal;

	  public void initialize(Subject subject, CallbackHandler callbackHandler,
	      Map sharedState, Map options) {
	    this.subject = subject;
	    this.callbackHandler = callbackHandler;
	  }

	  public boolean login() throws LoginException {
	    try {
	      NameCallback nameCallback = new NameCallback("username");
	      PasswordCallback passwordCallback = new PasswordCallback("password",
	          false);
	      final Callback[] calls = new Callback[] { nameCallback, passwordCallback };

	      // 获取用户数据
	      callbackHandler.handle(calls);
	      String username = nameCallback.getName();
	      String password = String.valueOf(passwordCallback.getPassword());

	      // TODO 验证,如:查询数据库、LDAP。。。
	      boolean loginFlag = "wd".equals(username) && "111111".equals(password);

	      if (loginFlag) {// 验证通过
	        principal = new SamplePrincipal(username);
	        isAuthenticated = true;
	      } else {
	        throw new LoginException("user or password is wrong");
	      }


	    } catch (IOException e) {
	      throw new LoginException("no such user");
	    } catch (UnsupportedCallbackException e) {
	      throw new LoginException("login failure");
	    }
	    return isAuthenticated;
	  }


	  /**
	   * 验证后的处理,在Subject中加入用户对象
	   */
	  public boolean commit() throws LoginException {
	    if (isAuthenticated) {
	      subject.getPrincipals().add(principal);
	    } else {
	      throw new LoginException("Authentication failure");
	    }
	    return isAuthenticated;
	  }

	  public boolean abort() throws LoginException {
	    return false;
	  }

	  public boolean logout() throws LoginException {
	    subject.getPrincipals().remove(principal);
	    principal = null;
	    return true;
	  }
}

b. 编写JAAS login configuration配置文件

将上述的LoginModule配置进一个叫做Sample的记录入口里。下面客户端初始化LoginContext时需要指定该入口。

新建文件,如/yourpath/jaas.conf, 内容如下:
Sample {
  	yourpackage.SampleLoginModule required debug=true;
};

JAAS的认证模块是可插拔的,就体现在具体使用的认证模块可以在login configuration文件中配置。在上述配置文件中yourpackage.SampleLoginModule就是我们上面编写的自定义的LoginModule。此处可以修改为其他LoginModule,也可以定义多个LoginModule,我们将在下面例子中再增加一个系统内置实现的LoginModule。
该配置文件的位置可以通过两种方式指定:
  • 启动应用时使用命令行参数指定:
java -Djava.security.auth.login.config==/yourpath/jaas.conf yourapp
  • 在java安全参数文档中指定:
java安全参数文档中存在名字格式为login.config.url.n的一个(组)属性,用于定义JAAS login configuration文件,从1开始往上增加。例如:
login.config.url.1=file:/yourpath/jaas.conf


c. 创建项目sampleProjectY

创建maven项目sampleProjectY,在pom.xml中添加对sampleLoginModule的依赖:

<dependency>
 	<groupId>security.jaas.test</groupId>
  	<artifactId>loginModule</artifactId>
  	<version>0.0.1-SNAPSHOT</version>
</dependency>

编写SampleCallbackHandler实现接口CallbackHandler:
public class SampleCallbackHandler implements CallbackHandler {
	 private String username;
	  private String password;

	  public SampleCallbackHandler(final String username, final String password) {
	    this.username = username;
	    this.password = password;
	  }

	  public void handle(Callback[] callbacks) throws IOException,
	      UnsupportedCallbackException {
	    for (int index = 0; index < callbacks.length; index++) {
	      if (callbacks[index] instanceof NameCallback) {
	        NameCallback ncb = (NameCallback) callbacks[index];
	        ncb.setName(username);
	      }
	      if (callbacks[index] instanceof PasswordCallback) {
	        PasswordCallback pcb = (PasswordCallback) callbacks[index];
	        pcb.setPassword(password.toCharArray());
	      }
	    }
	  }
}
SampleCallbackHandler的意义在于使得用户凭证的获得与执行认证功能解偶。使获得用户凭证与实际执行认证都可以独立于对方来更改替换。具体说明请参见下面。
然后编写客户端程序JustLogin:

public class JustLogin {
	public static LoginContext login(String name, String password) {
	    try {
	      //此处指定了使用配置文件的“Sample”验证模块,对应的实现类为SampleLoginModule
	      LoginContext lc = new LoginContext("Sample", new SampleCallbackHandler(
	          name, password));
	      lc.login();// 如果验证失败会抛出异常
	      
	      return lc;
	    } catch (LoginException e) {
	      e.printStackTrace();
	      return null;
	    } catch (SecurityException e) {
	      e.printStackTrace();
	      return null;
	    }
	  }
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static void main(String args[]) throws Exception{
		// 打开系统安全权限检查开关  
	    System.setSecurityManager(new SecurityManager());
		
		
	    LoginContext lc = JustLogin.login("wd", "111111");
	    System.out.println("===============完成登录===========");
		Subject sub = lc.getSubject();
		System.out.println("当前用户身份:");
		for(Principal p : sub.getPrincipals()){
			System.out.println("------"+p.getClass()+"/"+p.getName());
		}
	    lc.logout();
	    System.out.println("===============用户登出===========");
	    sub = lc.getSubject();
	    System.out.println("当前用户身份:");
	    if(sub.getPrincipals()==null || sub.getPrincipals().isEmpty()){
	    	System.out.println("------无");
	    }else{
	    	for(Principal p : sub.getPrincipals()){
	    		System.out.println("------"+p.getClass()+"/"+p.getName());
	    	}
	    }
	}
}
注意:其中创建LoginContext时传递的第一个参数就是我们上面配置文件的入口。也就是在应用创建LoginContext时需要指定login configuration文件中配置好的一个入口记录名称。通过入口记录名称,使得当前应用使用指定的LoginModule(s)来完成认证。

d. 配置安全规则文件
由于启动了SecurityManager,因此需要对于几个敏感操作赋予相应的访问许可:
  • 客户端中创建LoginContext需要javax.security.auth.AuthPermission "createLoginContext.<name>"的权限,其中name代表了我们在login configuration中配置,并在初始化LoginContext时指定的记录入口名称。因此需要为sampleProjectY增加Permission javax.security.auth.AuthPermission "createLoginContext.Sample"。
  • LoginModule中在登录成功以后需要执行subject.getPrincipals().add(principal)来添加Principal,该操作需要javax.security.auth.AuthPermission "modifyPrincipals"的权限。因此需要为sampleLoginModule增加permission javax.security.auth.AuthPermission "modifyPrincipals"。

关于Permission的详细介绍,请参考Java Security Architecture--Java安全体系技术文档翻译(二),3.1节。

新建文件/yourpath/mpolicy.txt,内容如下:

grant codebase "file:/home/wangd/work/test/sampleProjectY/target/classes"
 {   
  permission javax.security.auth.AuthPermission "createLoginContext.Sample";
 };
grant codebase "file:/home/wangd/work/test/sampleLoginModule/target/classes"
 {   
  permission javax.security.auth.AuthPermission "modifyPrincipals";
 };

e: 在命令行参数中指定policy file和config file并运行程序JustLogin:
-Djava.security.auth.login.config=/yourpath/jaas.conf -Djava.security.policy==/yourpath/mpolicy.txt

此时执行结果如下:
===============完成登录===========
	当前用户身份:
------class sampleLoginModule.SamplePrincipal/wd
===============用户登出===========
当前用户身份:
------无

f: 配置中增加另外一个LoginModule(com.sun.jmx.remote.security.FileLoginModule)

该FileLoginModule是系统内置的对于LoginModule的实现,是通过一个指定文件中的用户名密码内容来完成认证,默认该用户名密码文件放置在${java.home}/lib/management/jmxremote.password,配置FileLoginModule时可通过参数passwordFile指定一个自定义位置,我们修改步骤b中创建的文件/yourpath/jaas.conf,增加对于FileLoginModule的配置,并指定用户名密码放置在/yourpath/usernamepsw.txt中:

Sample {
  com.sun.jmx.remote.security.FileLoginModule required debug=false
         passwordFile="/yourpath/usernamepsw.txt";
  sampleLoginModule.SampleLoginModule required debug=true;
};
然后编辑/yourpath/usernamepsw.txt,在其中添加如下内容:
wd=111111
zs=123456
ls=123123
......

然后再次在命令行参数中指定policy file和config file并运行程序JustLogin:

-Djava.security.auth.login.config=/yourpath/jaas.conf -Djava.security.policy==/yourpath/mpolicy.txt
执行结果如下:
===============完成登录===========
当前用户身份:
------class javax.management.remote.JMXPrincipal/wd
------class sampleLoginModule.SamplePrincipal/wd
===============用户登出===========
当前用户身份:
------无

此时发现,该登录用户(Subject)同时拥有了两个身份(Principal):一个是我们自定义的登录模块使用的SamplePrincipal,另一个是FileLoginModule使用的JMXPrincipal。
Subject代表了当前完成了登录认证的“当事人”(可以是个人、企业等等),而Principal代表了这个当事人的某一身份或属性,例如姓名、身份证等等。因此同一个Subject可以拥有多个Principal。而授权时policy规则的配置是针对于Principal的,这就给权限配置增加了很大的灵活性。
至此登录与登出模块和系统的集成已经生效。

2.1.2 授权
a. 创建项目sampleProjectX
创建maven项目sampleProjectX,在pom.xml中定义其groupId、artifactId、version如下:
<groupId>security.jaas.test</groupId>
<artifactId>prox</artifactId>
<version>0.0.1-SNAPSHOT</version>

然后创建类SampleAction:
public class SampleAction implements PrivilegedAction {
	  private final static String FOLDER_PATH = "/home/wangd/data"; 
	  String fileName = "";
	  String message = "";
	  
	  public SampleAction(String fileName, String message){
		  this.fileName = fileName;
		  this.message = message;
	  }
	  
	  public Object run() {
		  try{
			  AccessControlContext acc = AccessController.getContext();
			  Subject sub = Subject.getSubject(acc);
			  System.out.println("==========="+message+"=============");
			  System.out.println("-----当前登录身份");
			  for(Principal p : sub.getPrincipals()){
				  System.out.println("---------"+p.getClass().getName()+"/"+p.getName());
			  }
			  System.out.println("-----开始执行操作");
			  FileUtil.makeFile(fileName);
			  System.out.println("======================================");
		  	}catch(Exception e){
			  System.err.println(message + ":" +e.getMessage());
		  	}
		  	return null;
	  }
	  
	  public static void makeFile(String fileName) throws Exception{
	        // 尝试在工程 A 执行文件的路径中创建一个新文件  
	        File fs = new File(FOLDER_PATH + "/" + fileName);   
	        fs.createNewFile();   
	        System.out.println("create "+fileName+" done!");
	    } 
}

b. 修改sampleProjectY

在项目sampleProjectY的pom.xml中增加对sampleProjectX的引用:

<dependency>
 	<groupId>security.jaas.test</groupId>
  	<artifactId>prox</artifactId>
  	<version>0.0.1-SNAPSHOT</version>
</dependency>

然后编写测试客户端LoginAndAuthorize:

public class LoginAndAuthorize {
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static void main(String args[]) throws Exception{
		// 打开系统安全权限检查开关  
	    System.setSecurityManager(new SecurityManager());
	    LoginContext lc = JustLogin.login("wd", "111111");
		
	    Subject sub = lc.getSubject();
		
	    PrivilegedAction action1 = new SampleAction("temp111.txt", "登录状态下doAsPrivileged调用该操作");
	    Subject.doAsPrivileged(sub, action1, null);
	    PrivilegedAction action2 = new SampleAction("temp112.txt", "登录状态下doAs调用该操作");
	    Subject.doAs(sub, action2);
	    
	    lc.logout();
	    PrivilegedAction action3 = new SampleAction("temp113.txt", "登出状态下doAsPrivileged调用该操作");
	    Subject.doAsPrivileged(sub, action3, null);
	    PrivilegedAction action4 = new SampleAction("temp114.txt", "登出状态下doAs调用该操作");
	    Subject.doAs(sub, action4);
	}
}

c. 修改规则配置

给sampleProjectY增加doAs、doAsPrivileged命名许可。并为sampleProjectX配置结合了登录身份(Principal)的许可。

修改/yourpath/mpolicy.txt文件,如下所示:
grant codebase "file:/home/wangd/work/test/sampleProjectY/target/classes"
 {   
  permission javax.security.auth.AuthPermission "createLoginContext.Sample";
  permission javax.security.auth.AuthPermission "doAs";
  permission javax.security.auth.AuthPermission "doAsPrivileged";
 };
grant codebase "file:/home/wangd/work/test/sampleLoginModule/target/classes"
 {   
  permission javax.security.auth.AuthPermission "modifyPrincipals";
 };
grant codebase "file:/home/wangd/work/test/sampleProjectX/target/classes", principal javax.management.remote.JMXPrincipal "wd", principal sampleLoginModule.SamplePrincipal "wd"
 {   
  permission javax.security.auth.AuthPermission "getSubject";
  permission java.io.FilePermission "/home/wangd/data/*", "write,read";
 };

关于规则文件格式的详细介绍请参考Java Security Architecture--Java安全体系技术文档翻译(三),3.3节。


然后在命令行参数中指定policy file和config file并运行程序LoginAndAuthorize:
-Djava.security.auth.login.config=/yourpath/jaas.conf -Djava.security.policy==/yourpath/mpolicy.txt

执行结果如下:
===========登录状态下doAsPrivileged调用该操作=============
-----当前登录身份
---------javax.management.remote.JMXPrincipal/wd
---------sampleLoginModule.SamplePrincipal/wd
-----开始执行操作
create temp111.txt done!
======================================
登录状态下doAs调用该操作:access denied ("javax.security.auth.AuthPermission" "getSubject")
登出状态下doAsPrivileged调用该操作:access denied ("javax.security.auth.AuthPermission" "getSubject")
登出状态下doAs调用该操作:access denied ("javax.security.auth.AuthPermission" "getSubject")

其中,登出状态下没有访问权限是很容易理解的,但是对于登录状态下使用doAs调用action时也没有访问权限是怎么回事,我们明明配置了sampleProjectX在登录用户"wd"的执行下对于javax.security.auth.AuthPermission "getSubject"的许可权限。具体原因我们将在下面核心算法介绍中给出。
至此,一个基于JAAS的认证与授权的完整的例子已经完成。我们同时使用了自定义的SampleLoginModule和系统内置实现的FileLoginModule完成了对于一个当事人(Subject)的认证,并由这两个LoginModule赋予该当事人两个层面的身份(Principal)。然后基于当事人身份和代码源配置了访问控制规则(Policy),并使用Subject.doAS和Subject.doAsPrivileged将当事人身份信息绑定进要执行的动作的调用堆栈上下文中。最终根据访问控制算法,得到了允许执行还是拒绝执行的反馈结果。
接下来我们就上面例子涉及到的相关概念及核心算法做一下介绍。

2.2 JAAS演化历史

Java认证与授权服务(JAAS)在Java 2 SDK和JDK1.3中作为一个可选的扩展程序包引入,并在JDK1.4中集成进核心JDK中。

2.3 JAAS核心概念

2.3.1 公共类(Common Classes)
公共类是在认证部分和授权部分都用到的类,其中JAAS中最核心的类就是Subject类,它包含了一组用户的身份(Principals),公开凭证(public credentials)及私有凭证(private credentials)。
Subject类用于表示在给定系统中认证的用户。Subject包含一组Principal对象(和其他有关用户的信息,如credentials),其中每个Principal对象表示同一个用户的不同“身份”,例如,一个 Principal 可能是我在一个终端系统上的用户 ID,而另一个可能是我在同一系统上所属于的“组”。在上面给出的例子中,SampleLoginModule和FileLoginModule分别对用户凭证做认证,通过以后各自给该Subject用户添加了一个身份。
credential的概念,是一个证书或者单纯的对象。其中可以与别人分享的证书或对象放在pubCredentials,而私有的需要保密的证书或对象放在privCredentials中。需要不同的访问许可来访问和修改这些Credential Set。

2.3.2 认证相关的类和接口
  • LoginContext
javax.security.auth.login.LoginContext类提供了用户认证的基本方法,并提供了一种使应用独立于底层认证技术的方式。LoginContext咨询一个Configuration来决定应用程序使用哪个(些)授权服务,也就是LoginModule(s)。因此,应用可以在不修改任何代码的情况下随意插拔不同的LoginModules。
LoginContext初始化时必须使用的一个参数是String name,用来指定使用login Configuration中的哪个入口记录中配置的LoginModule(s)进行认证。
  • LoginModule
接口LoginModule使开发者可以实现不同类型的认证技术,并能无需修改代码的插入到一个应用中。例如,一种类型的LoginModule可能基于用户名密码方式做认证。其他的LoginModules可能被硬件设备实现,用指纹或脸做认证了。
该接口中定义了认证所需要的通用操作,并使用CallbackHandler和Callback来将获得用户凭证与实际执行认证解偶。使获得用户凭证与实际执行认证都可以独立于对方来更改替换。
  • CallbackHandler
在一些情况下一个LoginModule必须与用户交互以得到认证信息。LoginModules使用一个接口javax.security.auth.callback.CallbackHandler来达到该目的。应用负责实现接口CallbackHandler并将其传递给LoginContext,而LoginContext会直接将其传递给底层的LoginModule。LoginModule使用CallbackHandler从用户收集输入信息,或者向用户提供信息。通过让应用指定具体的CallbackHandler,底层的LoginModules就可以独立于应用跟用户之间交互的部分。
  • Callback
javax.security.auth.callback包中包含了接口Callback和几个Callback的实现。LoginModule和CallbackHandler之间通过一个数组Callback[]交换信息。
2.3.3 授权相关的类
  • Policy
Policy是首先出现在java security体系中的类,我们在第一部分已经对其进行了详细的介绍。在JDK1.4中,为了支持JAAS的基于用户的授权体系,Policy API进行了升级强化。policy file文件结构及语法格式的描述请参见:《Java Security Architecture--Java安全体系技术文档翻译(三)》。
  • AuthPermission
javax.security.auth.AuthPermission类包含了JAAS所需要的基本许可。是一个命名许可(定义了名称,没有动作),你要么拥有该许可,要么没有。许可相关信息请参见:《Java Security Architecture--Java安全体系技术文档翻译(二)》。

可以回顾一下,上述例子中,我们为了让认证和授权可以顺利运行,在规则文件中配置了如下的权限:

permission javax.security.auth.AuthPermission "createLoginContext.Sample";
permission javax.security.auth.AuthPermission "doAs";
permission javax.security.auth.AuthPermission "doAsPrivileged";
permission javax.security.auth.AuthPermission "modifyPrincipals";
permission javax.security.auth.AuthPermission "getSubject";

  • PrivateCredentialPermission
javax.security.auth.PrivateCredentialPermission类用于保护Subject的私有凭证(private credentials)的访问权限。只有拥有该权限的类才能访问。
2.3.4 对java security architecture中的组件的升级
除了上面提到的Policy,为了支持JAAS对原有的组件的升级包括了:ProtectionDomain,AccessControlContext,SubjectDomainCombiner等。
其中ProtectionDomain中增加了对Principals的引用,为保护域引入了是谁在运行该域中代码的概念。
AccessControlContext及SubjectDomainCombiner扩展了AccessControlContext,使其支持由绑定了Subject的Combiner对调用栈中的保护域绑定登录用户身份。

2.3.5 整体概念
JAAS规范中认证和授权逻辑上分为两部分,其中认证部分使用可插拔风格,这可以使得应用程序独立于底层使用的认证技术,无需应用程序改变就可以替换底层的认证技术。
JAAS授权部分同Java访问控制模型一起保护对于敏感资源的访问许可,对java security architecture基于代码的授权体系组件进行了加强和扩展,使其不仅基于代码还基于运行该代码的用户身份来决定是否允许对敏感资源的访问。
认证一个主题(用户或服务),需要执行如下步骤:
  • 应用初始化一个LoginContext。
  • LoginContext咨询一个login配置,并加载指定入口配置的所有LoginModules。
  • 应用调用LoginContext的login方法。
  • LoginContext的login方法触发所有加载的LoginModules的login方法,每一个LoginModule尝试认证该用户,如果认证成功就将一个相关的身份和凭证关联到该Subject上。
  • LoginContext将认证状态返回给应用。
  • 如果认证成功,应用可以通过LoginContext获得用户(Subject)信息。
为了让JAAS授权生效,提供不仅基于代码来源,还基于谁在运行它的访问控制许可,需要以下几步:
  • 用户必须已经被认证
  • 认证后的用户信息(Subject)必须与安全控制上下文关联起来,通过Subject.runAs和Subject.runAsPrivileged
  • 必须配置基于用户身份的安全规则

2.4 核心算法介绍

2.4.1 doAs中的AccessControlContext计算

在用户登录之后调用Subject.doAs(subject, action)方法来执行对认证用户相关的许可中的系统资源的访问。在内部,这个调用会产生下列活动:
  1.  调用AccessController.getContext()获得当前执行线程的安全上下文快照,其内部首先调用native方法getStackAccessControlContext()以获得一个只与当前调用堆栈相关的上下文快照,然后执行该快照的optimize方法进行优化去重。并将其与封装了登录用户(Subject)的SubjectDomainCombiner一起创建一个新的AccessControlContext对象。
  2.  调用AccessController.doPrivileged()方法执行action,并将第一步得到的新的AccessControlContext对象作为参数传入。
  3.  doPrivileged方法是一个native方法,内部会将传入的AccessControlContext保存为privilegedContext。并执行action的run()方法。此run方法中访问受保护的资源时,就会触发AccessController.checkPermission()方法。
  4.  checkPermission方法会调用AccessController.getContext()以获得当前调用栈的安全上下文,这时同样首先调用native方法getStackAccessControlContext()以获得一个只与当前调用堆栈相关的上下文快照,然后执行该快照的optimize方法。但此时,isPrivileged为true,privilegedContext是我们第2步传入的context。并且该context中存在一个DomainCombiner combiner,也就是第1步中生成的封装了登录用户Subject的SubjectDomainCombiner,因此其优化就首先需要执行combiner.combine行为。该行为会将combiner自己封装的用户的Principals信息绑定到当前调用栈相关的保护域上,并将privilegeContext中的保护域合并进来,然后查重(注意:用==做判断)。
  5.  最终返回一个优化好了的AccessControlContext,并逐个检查其中的ProtectionDomain的访问许可,如果有不满足action要求的访问权限的域则直接报异常。
  6.  若所有域都拥有访问权限,继续执行action。
说明:最终返回的上下文快照中的保护域分为两部分:基于action中当前调用堆栈的保护域和运行doPrivileged方法时传入的privilegeContext中的保护域。其中privilegeContext中的保护域并没有与当前认证用户Subject中的Principal相关联。
2.4.2 doAsPrivileged中的AccessControlContext计算
如上所述,在调用doAs之前,请求的Permission必须由出现在调用堆栈中的ProtectionDomains所隐含。就像前面给出的例子一样,很多情况下我们并不希望是这样。
PrivilegedAction可能实际上表示一些服务器代表客户机执行的一些操作。在这种情况下,在调用doAs之前调用堆栈的快照将包含服务器的内部代码的ProtectionDomain,而让这些ProtectionDomain必须隐含一个任意请求的Permission显然没有意义。
因此,这种情况下可以通过Subject.doAsPrivileged(subject, action, acc)来解决。其执行步骤绝大部分与doAs相同,只有最开始不是通过AccessController.getContext()获得当前上下文快照,而是通过参数指定的一个上下文快照。如果该快照是null,就创建一个ProtectionDomain[]为空的快照。
当传递的上下文快照参数为null时,代表执行action动作时只校验action内部的调用堆栈上下文,换句话说,只校验与登录身份相关的执行上下文中的ProtectionDomain;而当传递的上下文快照不为null时,代表需要使用该上下文以及action内部的调用堆栈上下文一起校验访问权限。因此该传入的上下文需要隐含action中要求的访问许可。

2.4.3 示例代码中doAs和doAsPrivileged结果不同的原因分析
通过上面对于doAs和doAsPrivileged算法的解读,2.1.2中授权样例的执行结果就不难解释了,doAs(subject, action)会校验当前调用doAs的代码在没有登录情况下是否仍然隐含了action中需要的访问许可,如果没有就会报异常。而我们并没有配置该许可所以示例中doAs报异常。
相反doAsPrivileged(subject, action, null)只会校验action中调用堆栈上下文中涉及到的ProtectionDomain的访问许可,而这些Domain在执行AccessController.getContext()时都已经被绑定了用户登录身份信息的。所以示例中doAsPrivileged顺利完成。

2.5 总结

本文由示例引导深入分析了JAAS的认证及授权的总体概念及核心算法。由于java security与JAAS已经集成进JDK核心代码,在很多方面对Java语言产生了影响,同时也会影响我们编写的程序。因此,无论是否采用JAAS作为认证与授权框架,了解其底层机制也都是很有必要的。






上一篇:JavaSecurity和JAAS——Java标准安全体系样例及概述(上)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值