java验证身份证合法性_Java安全性,第2部分:身份验证和授权

关于本教程

本教程是关于什么的?

也许没有比应用程序安全更重要的软件工程主题。 攻击是昂贵的,无论是来自内部还是外部,而且某些攻击可能会使软件公司承担赔偿责任。 随着计算机(尤其是Internet)技术的发展,安全攻击变得越来越复杂和频繁。 掌握最新技术和工具是应用程序安全的关键之一。 另一个是成熟的技术的坚实基础,例如数据加密,身份验证和授权。

在这个分为两部分的教程中,我们已经学习了Java平台的安全性功能。 第1部分是Java密码学的初学者介绍。 在这里,在第2部分中,我们将扩展讨论以涵盖访问控制,访问控制由Java身份验证和授权服务(JAAS)在Java平台中进行管理。

我应该学习本教程吗?

这是一个中级教程。 假定您知道如何读写基本的Java应用程序和applet。 如果您已经是Java程序员,并且对认证和授权技术以及支持它们的Java库感到好奇,那么本教程适合您。

我们将首先介绍身份验证和授权的基本概念,以及JAAS的体系结构概述。 接下来,我们将使用JAAS示例应用程序,通过分解应用程序的组件并查看最终执行结果,从理论到实践全面理解JAAS。 作为本练习的一部分,我们将研究各种JAAS配置选项,这些选项将有助于进一步巩固您所学的概念。

JAAS是一项复杂的技术,具有丰富的功能和特性。 我们将以一口大小的小块慢慢地介绍它,建议您不止一次地研究每个新概念。 到本教程结束时,您将拥有一个单独使用JAAS的良好基础。

您无需阅读本教程的第1部分即可了解第2部分。

工具,代码示例和安装要求

JAAS最初是对Java 2平台标准版的扩展。 但是,最近它已添加到1.4版中。 要完成本教程,您将需要以下内容:

  • JDK 1.4,标准版
  • 本教程的源代码和类JavaSecurity2-source.jar ,以便您可以按照示例进行操作。
  • 支持Java 1.4插件的浏览器。

您可以使用JDK 1.3.x,但是必须自己安装JCE和JSSE。

概念概述

认证与授权

认证是人类用户或计算设备验证其身份的过程。 授权是一个过程,敏感软件通过该过程可以进行访问和依赖请求用户身份的操作。 这两个概念是齐头并进的。 未经授权,几乎不需要知道用户的身份。 如果没有身份验证,就无法区分受信任的用户和不受信任的用户,这使得无法安全地授权访问系统的许多部分。

识别或认证单个实体并不总是必要的; 在某些情况下,您可以按组进行身份验证,对给定组内的所有实体授予一定的授权。 在其他情况下,个人身份验证对于系统的安全至关重要。

身份验证和授权的另一个有趣方面是,单个实体在系统中可以具有多个角色。 例如,人类用户既可以是公司的雇员(这意味着他将需要访问公司电子邮件),又可以是公司内的一名会计师,这意味着他将需要访问公司会计系统。

认证要素

身份验证基于以下一个或多个元素:

  • 你知道什么。 此类别包括其他人通常不知道的个人知道的信息。 示例包括PIN,密码和个人信息,例如母亲的娘家姓。
  • 你有什么。 此类别包括使个人可以访问资源的物理项目。 示例包括ATM卡,安全ID令牌和信用卡。
  • 你是谁。 此类别包括生物识别技术,例如指纹,视网膜轮廓和面部照片。

通常,仅使用一个类别进行授权是不够的。 例如,通常将ATM卡与PIN结合使用。 即使物理卡丢失,用户和系统也是安全的,因为小偷必须知道PIN才能访问任何资源。

授权要素

有两种控制对敏感代码的访问的基本方法:

  • 声明性授权可以由系统管理员执行,该系统管理员配置系统的访问权限(即声明谁可以访问系统中的哪些应用程序)。 使用声明性授权,可以在不影响基础应用程序代码的情况下添加,更改或撤消用户访问特权。
  • 程序授权使用Java应用程序代码来做出授权决策。 当授权决策需要更复杂的逻辑和决策时,程序授权是必需的,这超出了声明性授权的范围。 由于程序授权内置于应用程序代码中,因此要进行程序授权更改,就需要重写部分应用程序代码。

在本教程中,您将了解声明式和程序性授权技术。

保护用户和代码彼此

Java平台允许根据用户对代码的信任程度对计算资源(例如磁盘文件和网络连接)进行细粒度的访问控制。 Java平台的大多数基本安全功能旨在保护用户免受潜在恶意代码的侵害。 例如,由第三方证书支持的数字签名代码可确保代码源的身份。 根据他对代码源的了解,用户可以选择授予或拒绝对此代码的执行权限。 同样,用户可以基于给定代码源的下载URL授予或拒绝访问。

基于Java的系统上的访问控制是通过策略文件实现的,该策略文件包含以下语句:

grant signedBy "Brad", codeBase "http://www.bradrubin.com" {

       permission java.io.FilePermission "/tmp/abc", "read";
};

该语句允许由“ Brad”签名并从http://www.bradrubin.com加载的代码读取/ tmp / abc目录。

其他Java平台功能(例如缺少指针)进一步保护了用户免受潜在恶意代码的侵害。 JAAS的认证和授权服务一起工作,提供了一项补充功能:它们保护敏感的Java应用程序代码免受潜在的恶意用户的攻击。

可插拔认证模块

JAAS实现了Java版本的可插入身份验证模块(PAM)框架。 Sun Microsystems为其Solaris操作系统创建了PAM。 通过JAAS,PAM现在可以独立于平台使用。

PAM的主要目的是允许应用程序开发人员在开发时写入标准身份验证接口,而由系统管理员决定使用哪种身份验证技术(以及如何使用它们)。 身份验证技术是在登录模块中实现的,登录模块可以在编写应用程序之后进行部署,并在称为登录配置文件的文本文件中指定(在本教程中名为login.config)。 login.config文件不仅可以指定要调用的模块,还可以指定总体身份验证成功的条件。

PAM允许将新的身份验证技术或技术更轻松地添加到现有应用程序中。 同样,可以通过更新login.config文件而不是重写整个应用程序来更改身份验证策略。

JDK 1.4随附以下PAM模块。 我们将在其中使用其中之一,并在本教程的后面部分练习编写我们自己的两个:

  • com.sun.security.auth.module.NTLoginModule
  • com.sun.security.auth.module.NTSystem
  • com.sun.security.auth.module.JndiLoginModule
  • com.sun.security.auth.module.KeyStoreLoginModule
  • com.sun.security.auth.module.Krb5LoginModule
  • com.sun.security.auth.module.SolarisSystem
  • com.sun.security.auth.module.UnixLoginModule
  • com.sun.security.auth.module.UnixSystem

JAAS示例和图表

在本教程中,我们将逐段查看JAAS示例应用程序的代码。 为了帮助跟踪全局,下图显示了所有这些部分如何组合在一起。 正在运行的示例(主程序JAASExample)首先使用两种技术或登录模块对用户进行身份验证,然后根据身份验证步骤的结果允许或禁止(或授权)访问两段敏感代码。

这是JAASExample程序的图。 下一个面板描述了操作流程。

java验证身份证合法性_Java安全性,第2部分:身份验证和授权_第1张图片

JAAS操作流程示例

以下是JAASExample图所示的总体认证和授权流程的简要说明。 在本教程的其余部分中,将更详细地描述以下每个步骤。

我们从身份验证的第一步开始,这是创建登录上下文并尝试登录LoginContext是一个Java类,它使用login.config文件中的信息来确定要调用的登录模块以及将使用哪些条件。确定成功。 对于此示例,有两个登录模块。 第一个称为AlwaysLoginModule ,不需要密码,因此它总是成功的(这是不现实的,但是足以说明JAAS的工作原理)。 这个模块用关键字required标记,这意味着它是成功所必需的(它总是如此)。 第二个模块称为PasswordLoginModule ,它需要一个密码,但是此模块的成功是可选的,因为它被标记为关键字optional 这意味着即使PasswordLoginModule失败,总体登录仍然可以成功。

初始化之后,所选的登录模块将经历由LoginContext控制的两阶段提交过程。 在此过程中,将调用UsernamePasswordCallbackHandler以从由Subject对象表示的个人获取用户名和密码。 如果认证成功,一个Principal被添加到Subject Subject可能具有多个Principal (在本例中为“ Brad”和“ joeuser”),每个Principal都授权用户对系统进行不同级别的访问。 这样就完成了身份验证步骤。

身份验证完成后,我们将使用编程授权技术和doAs方法,使用“ Subject尝试执行一些敏感的工资单操作代码。 JAAS将检查Subject是否被授权访问。 如果Subject具有授权访问薪资代码的Principal ,则可以继续执行。 否则,将拒绝执行。

接下来,我们尝试使用声明性授权技术和doAsPrivilaged方法执行一些敏感的人员操作代码。 这次,JAAS部署了用户定义的权限( PersonnelPermission ),Java策略文件(jaas.policy)和Java访问控制器( AccessController )来确定执行是否可以继续。

JAAS中的认证

总览

在本节中,我们将重点介绍JAAS中的身份验证元素。 我们将从简单的登录和认证过程的描述开始,它将为您提供JAAS认证体系结构的高级视图。 接下来,我们将详细讨论架构的每个部分。 在本节的最后,您将有机会详细研究两个登录模块的代码。

如果您尚未这样做,那么现在该下载本教程的源代码JavaSecurity2-source.jar了 。 资料来源将更好地说明以下讨论中概述的步骤。

学科与校长

Subject是表示单个实体(例如单个实体)的Java对象。 单个Subject可以具有多个关联的标识,每个由Principal对象表示。 因此,假设一个Subject代表需要同时访问电子邮件系统和会计系统的员工。 Subject将具有两个Principal ,一个与员工的电子邮件访问用户ID关联,另一个与他的会计系统用户ID关联。

Principal s为不是持久的,所以他们必须被添加到Subject每一种在用户登录时Principal被添加到Subject作为一个成功的认证过程的一部分。 同样,如果身份验证失败,则从Principal中删除Subject 无论身份验证是成功还是失败,当应用程序执行注销时,所有Principal被删除。

除了包含一组PrincipalSubject还可以包含两组凭据:一组公共凭据和一组私有凭据。 凭证是密码,密钥,令牌等。 对公共和私有证书集的访问由Java权限控制,我们将在本教程的后面部分讨论。 有关凭据的完整讨论超出了本教程的范围。

受试者的方法

Subject对象具有几种方法,其中一些方法如下:

  • subject.getPrincipals()返回一组Principal对象。 因为结果是Set ,所以remove()add()contains()
  • subject.getPublicCredentials()返回与Subject关联的一组公共可访问凭据。
  • subject.getPrivateCredentials()返回与Subject关联的一组私有可访问凭据。

主体接口

Principal是Java接口。 程序员编写的PrincipalImpl对象实现Principal接口以及Serializable接口,名称字符串,返回该字符串的getName()方法以及其他支持方法,例如hashCode()toString()equals()

在登录过程中将Principal添加到Subject 正如我们将在后面看到的那样,声明性授权基于策略文件中的条目。 提出授权请求后,系统的授权策略将与Subject包含的Principal进行比较。 如果SubjectPrincipal符合策略文件中的安全性要求,则授予授权; 否则将被拒绝。

首席Impl

这是我们将在本教程中使用的PrincipalImpl

import java.io.Serializable;
import java.security.Principal;

//
// This class defines the principle object, which is just an encapsulated 
// String name 
public class PrincipalImpl implements Principal, Serializable {

     private String name;

     public PrincipalImpl(String n) {
       name = n;
     }

     public boolean equals(Object obj) {
       if (!(obj instanceof PrincipalImpl)) {
         return false;
       }
       PrincipalImpl pobj = (PrincipalImpl)obj;
       if (name.equals(pobj.getName())) {
         return true;
       }
       return false;
     }

     public String getName() {
       return name;
     }

     public int hashCode() {
       return name.hashCode();
     }

     public String toString() {
       return getName();
     }

}

登录配置

JAAS在Subject所需的身份验证过程的种类,执行的顺序以及在Subject被视为经过身份验证之前所需的身份验证成功或失败的组合方面提供了极大的灵活性。

JAAS使用login.config文件为每个登录模块指定认证条款。 在Java执行命令行上,使用-Djava.security.auth.login.config==login.config属性指定login.config文件。 Java具有默认的登录配置文件,因此双等号( == )替换了系统登录配置文件。 如果使用单个等号,则login.config文件将添加到系统登录配置文件中,而不是替换该文件。 因为我们不知道您的系统文件中可能包含什么内容,所以我们这样做是为了确保广泛的教程用户都能获得可靠的结果。

login.config文件包含LoginContext构造函数中引用的文本字符串和登录过程列表。 几个参数用于指定给定登录过程的成功或失败对整个身份验证过程的影响。 参数如下:

  • required表示登录模块必须成功。 即使登录失败,也将调用其他登录模块。
  • optional表示登录模块可能会失败,但是如果另一个登录模块成功,则总体登录可能仍会成功。 如果所有登录模块都是可选的,则必须至少成功登录一个模块,身份验证才能成功。
  • requisite意味着登录模块必须成功,如果失败,则不会调用其他登录模块。
  • sufficient意味着如果没有其他必需或必需的登录模块失败,则如果登录模块成功,则总体登录将成功。

示例login.config文件

我们将在本教程中使用的login.config文件如下:

JAASExample {
      AlwaysLoginModule required;
      PasswordLoginModule optional;
};

如您所见, AlwaysLoginModule必须成功,而PasswordLoginModule可以成功或失败。 这不是现实的情况,但是我们稍后将修改这些参数,以查看不同的配置如何更改代码行为。

要实现此登录配置技术,重要的一点是,它需要在部署时确定所有主要决策(例如所需的身份验证类型以及身份验证成功或失败的特定标准)。 如果登录成功,将导致增加了一个新的SubjectLoginContext ,同时将所有成功认证的Principal那个S Subject

登录上下文

LoginContext是一个Java类,用于设置登录过程,实际登录并在登录成功后获取Subject 它有四种主要方法,如下所示:

  • LoginContext("JAASExample", newUsernamePasswoerdCallbackHandler())是构造函数。 它以login.config文件中使用的字符串作为第一个参数,并执行实际工作的回调处理程序作为第二个参数。 (接下来我们将讨论回调处理程序。)
  • login()实际上会尝试登录,但要遵循login.config文件中指定的规则。
  • 如果登录成功,则getSubject()返回经过身份验证的Subject
  • logout()SubjectLoginContext logout()

回调处理程序

JAAS登录使用回调处理程序从用户获取身份验证信息。 CallbackHandlerLoginContext对象的构造函数中指定。 在本教程中,回调处理程序使用几种提示来从用户那里获取用户名和密码信息。 从登录模块调用的handle()程序的handle()方法将Callback对象数组作为其参数。 在登录期间,处理程序将遍历Callback的数组。 handle()方法检查Callback对象的类型并执行适当的用户操作。 Callback类型如下:

  • NameCallback
  • PasswordCallback
  • TextInputCallback
  • TextOutputCallback
  • LanguageCallback
  • ChoiceCallback
  • ConfirmationCallback

在某些应用程序中,不需要用户交互,因为将使用JAAS与操作系统的身份验证机制进行接口。 在这种情况下, LoginContext对象中的CallbackHandler参数将为null。

工作中的回调处理程序

以下是本教程中使用的UsernamePasswordCallbackHandler的代码。 它总是由AlwaysLoginModule调用(只有一个回调来获取用户ID),一次由PasswordLoginModule调用(带有两个回调来获取用户ID和密码)。

import java.io.*;
import java.security.*;
import javax.security.auth.*;
import javax.security.auth.callback.*;
//
// This class implements a username/password callback handler that gets 
// information from the user public class
UsernamePasswordCallbackHandler implements CallbackHandler {
     //
     // The handle method does all the work and iterates through the array
     // of callbacks, examines the type, and takes the appropriate user
     // interaction action.
     public void handle(Callback[] callbacks) throws
         UnsupportedCallbackException, IOException {

       for(int i=0;i<callbacks.length;i++) {
         Callback cb = callbacks[i];
         //
         // Handle username aquisition
         if (cb instanceof NameCallback) {
           NameCallback nameCallback = (NameCallback)cb;
           System.out.print( nameCallback.getPrompt() + "? ");
           System.out.flush();
           String username = new BufferedReader(
               new InputStreamReader(System.in)).readLine();
           nameCallback.setName(username);
           //
           // Handle password aquisition
         } else if (cb instanceof PasswordCallback) {
           PasswordCallback passwordCallback = (PasswordCallback)cb;
           System.out.print( passwordCallback.getPrompt() + "? ");
           System.out.flush();
           String password = new BufferedReader(
               new InputStreamReader(System.in)).readLine();
           passwordCallback.setPassword(password.toCharArray());
           password = null;
           //
           // Other callback types are not handled here
         } else {
           throw new UnsupportedCallbackException(cb, "Unsupported 
Callback Type");
         }
       }
     }
}

登录模块

LoginModule是参与JAAS身份验证过程所需方法的接口。 因为在执行其他登录过程之前可能无法知道特定登录过程的成功或失败,所以使用两阶段提交过程来确定成功。 以下方法由LoginModule对象实现:

  • initialize( subject, callbackHandler, sharedState, options)初始化LoginModule (请注意,对sharedStateoptions的讨论不在本教程的讨论范围之内。)
  • login()设置所有必要的回调,调用CallbackHandler进行处理,并将返回的信息(即用户名和密码)与允许的值进行比较。 如果匹配,则登录模块成功,但是如果另一个登录模块不成功,登录模块仍然可以中止,具体取决于login.config文件中的设置。
  • 作为两阶段提交过程的一部分,将调用commit()确定成功。 如果所有登录模块均成功完成,并且受login.config文件中指定的约束的约束,则将创建一个新的Principal和用户名,并将其添加到Subject的主体集中。
  • 如果整体登录失败,则调用abort() ;否则,将调用abort() 如果发生异常终止,则必须清除内部LoginModule状态。
  • 调用logout()Subject的主体集中删除Principal ,并进行其他内部状态清除。

以下两节说明了两个登录模块。 第一个, AlwaysLoginModule ,总是成功的。 仅当用户ID和密码与某些硬编码值匹配时,第二个PasswordLoginModule才成功。 尽管这两个示例模块都不是现实的实现,但是它们一起展示了各种JAAS选项的结果。

AlwaysLoginModule

AlwaysLoginModule身份验证将始终成功,因此它实际上仅用于通过NameCallback函数获取用户名。 假设其他登录模块成功, AlwaysLoginModulecommit()方法将使用用户名创建一个新的PrincipalImpl对象,并将其添加到SubjectPrincipal集中。 注销删除PrincipalImplSubjectPrincipal组。

import java.security.*;
import javax.security.auth.*;
import javax.security.auth.spi.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import java.io.*;
import java.util.*;

// This is a JAAS Login Module that always succeeds.  While not realistic, 
// it is designed to illustrate the bare bones structure of a Login Module 
// and is used in examples that show the login configuration file 
// operation.

public class AlwaysLoginModule implements LoginModule {

     private Subject subject;
     private Principal principal;
     private CallbackHandler callbackHandler;
     private String username;
     private boolean loginSuccess;
     //
     // Initialize sets up the login module.  sharedState and options are
     // advanced features not used here
     public void initialize(Subject sub, CallbackHandler cbh,
       Map sharedState, Map options) {

       subject = sub;
       callbackHandler = cbh;
       loginSuccess = false;
     }
     //
     // The login phase gets the userid from the user
     public boolean login() throws LoginException {
       //
       // Since we need input from a user, we need a callback handler
       if (callbackHandler == null) {
         throw new LoginException( "No CallbackHandler defined");

       }
       Callback[] callbacks = new Callback[1];
       callbacks[0] = new NameCallback("Username");
       //
       // Call the callback handler to get the username
       try {
         System.out.println( "\nAlwaysLoginModule Login" );
         callbackHandler.handle(callbacks);
         username = ((NameCallback)callbacks[0]).getName();
       } catch (IOException ioe) {
         throw new LoginException(ioe.toString());
       } catch (UnsupportedCallbackException uce) {
         throw new LoginException(uce.toString());
       }
       loginSuccess = true;
       System.out.println();
       System.out.println( "Login: AlwaysLoginModule SUCCESS" );
       return true;
     }
     //
     // The commit phase adds the principal if both the overall authentication
     // succeeds (which is why commit was called) as well as this particular
     // login module
     public boolean commit() throws LoginException {
       //
       // Check to see if this login module succeeded (which it always will

       // in this example)
       if (loginSuccess == false) {
         System.out.println( "Commit: AlwaysLoginModule FAIL" );
         return false;
       }
       //
       // If this login module succeeded too, then add the new principal
       // to the subject (if it does not already exist)
       principal = new PrincipalImpl(username);
       if (!(subject.getPrincipals().contains(principal))) {
         subject.getPrincipals().add(principal);
       }
       System.out.println( "Commit: AlwaysLoginModule SUCCESS" );
       return true;
     }
     //
     // The abort phase is called if the overall authentication fails, so
     // we have to clean up the internal state
     public boolean abort() throws LoginException {

       if (loginSuccess == false) {
         System.out.println( "Abort: AlwaysLoginModule FAIL" );
         principal = null;
         return false;
       }
       System.out.println( "Abort: AlwaysLoginModule SUCCESS" );
       logout();

       return true;
     }
     //
     // The logout phase cleans up the state
     public boolean logout() throws LoginException {

       subject.getPrincipals().remove(principal);
       loginSuccess = false;
       principal = null;
       System.out.println( "Logout: AlwaysLoginModule SUCCESS" );
       return true;
      }
}

密码登录模块

PasswordLoginModule使用NameCallback获取用户名,并使用PasswordCallback获取密码。 如果用户名是“ joeuser”,密码是“ joe”,则此身份验证将成功。

import java.security.*;
import javax.security.auth.*;
import javax.security.auth.spi.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import java.io.*;
import java.util.*;
//
// This is a JAAS Login Module that requires both a username and a  
// password. The username must equal the hardcoded "joeuser" and 
// the password must match the hardcoded "joeuserpw". 
public class
PasswordLoginModule implements LoginModule {

     private Subject subject;
     private Principal principal;
     private CallbackHandler callbackHandler;
     private String username;
     private char[] password;
     private boolean loginSuccess;
     //
     // Initialize sets up the login module.  sharedState and options are
     // advanced features not used here
     public void initialize(Subject sub, CallbackHandler cbh,
       Map sharedState,Map options) {

       subject = sub;
       callbackHandler = cbh;
       loginSuccess = false;
       username = null;
       clearPassword();
     }
     //
     // The login phase gets the userid and password from the user and
     // compares them to the hardcoded values "joeuser" and "joeuserpw".
     public boolean login() throws LoginException {
       //
       // Since we need input from a user, we need a callback handler
       if (callbackHandler == null) {
          throw new LoginException("No CallbackHandler defined");
       }
       Callback[] callbacks = new Callback[2];
       callbacks[0] = new NameCallback("Username");
       callbacks[1] = new PasswordCallback("Password", false);
       //
       // Call the callback handler to get the username and password
       try {
         System.out.println( "\nPasswordLoginModule Login" );
         callbackHandler.handle(callbacks);
         username = ((NameCallback)callbacks[0]).getName();
         char[] temp = ((PasswordCallback)callbacks[1]).getPassword();
         password = new char[temp.length];
         System.arraycopy(temp, 0, password, 0, temp.length);
         ((PasswordCallback)callbacks[1]).clearPassword();
       } catch (IOException ioe) {
         throw new LoginException(ioe.toString());
       } catch (UnsupportedCallbackException uce) {
         throw new LoginException(uce.toString());
       }
       System.out.println();
       //
       // If username matches, go on to check password
       if ( "joeuser".equals(username)) {
         System.out.println
           ( "Login: PasswordLoginModule Username Matches" );
         if ( password.length == 5 &&
             password[0] == 'j' &&
             password[1] == 'o' &&
             password[2] == 'e' &&
             password[3] == 'p' &&
             password[4] == 'w' ) {
           //
           //If userid and password match, then login is a success
           System.out.println
             ( "Login: PasswordLoginModule Password Matches" );
           loginSuccess = true;
           System.out.println
             ( "Login: PasswordLoginModule SUCCESS" );
           clearPassword();
           return true;
         } else {
           System.out.println
            ( "Login: PasswordLoginModule Password Mismatch" );
         }
       } else {
         System.out.println( "Login: PasswordLoginModule Username Mismatch" );
       }
       //
       // If either mismatch, then this login module fails

       loginSuccess = false;
       username = null;
       clearPassword();
       System.out.println( "Login: PasswordLoginModule FAIL" );
       throw new FailedLoginException();
     }
     //
     // The commit phase adds the principal if both the overall 
     // authentication succeeds (which is why commit was called) 
     // as well as this particular login module
     public boolean commit() throws LoginException {

       //
       // Check to see if this login module succeeded
       if (loginSuccess == false) {
         System.out.println( "Commit: PasswordLoginModule FAIL" );
         return false;
       }
       // If this login module succeeded too, then add the new principal
       // to the subject (if it does not already exist)
       principal = new PrincipalImpl(username);
       if (!(subject.getPrincipals().contains(principal))) {
         subject.getPrincipals().add(principal);
       }
       username = null;
       System.out.println( "Commit: PasswordLoginModule SUCCESS" );
       return true;
     }
     //
     // The abort phase is called if the overall authentication fails, so
     // we have to cleanup the internal state
     public boolean abort() throws LoginException {

       if (loginSuccess == false) {
         System.out.println( "Abort: PasswordLoginModule FAIL" );
         principal = null;
         username = null;
         return false;
       }
       System.out.println( "Abort: PasswordLoginModule SUCCESS" );
       logout();
       return true;
     }
     //
     // The logout phase cleans up the state
     public boolean logout() throws LoginException {
       subject.getPrincipals().remove(principal);
       loginSuccess = false;
       username = null;
       principal = null;
       System.out.println( "Logout: PasswordLoginModule SUCCESS" );
       return true;
     }
     //
     // Private helper function to clear the password, a good programming
     // practice
     private void clearPassword() {
       if (password == null) {
         return;
       }
       for (int i=0;i

JAAS中的授权

总览

了解Java平台如何实现授权访问控制很重要,以了解我们将在本节中讨论的概念。 Java平台使用访问控制上下文的概念来确定当前执行线程的权限。 从概念上讲,这可以看作是附加到每个执行线程的令牌。 在JAAS之前,访问控制基于了解当前Java .class文件的代码源或数字签名者的身份。 在此模型下,访问控制基于知道代码从何而来。 使用JAAS,我们可以改变模型。 通过将Subject添加到访问控制上下文中,我们可以根据谁正在执行(或要求执行)给定的代码段来开始授予或拒绝访问。

在本部分中,您将了解JAAS的用于控制对敏感代码的访问的机制。 我们将首先描述授权在JAAS中的工作方式,然后再对授权框架的每个组件进行更深入的描述。 我们将以较大的运行示例中使用的一些代码示例结束本节,这些示例演示了编程授权和声明性授权技术。 在本节的最后,您应该对JAAS的身份验证和授权机制如何协同工作以保护基于Java的系统有一个清晰的了解。

访问控制和权限

由于执行线程可以跨越具有不同上下文特征的多个模块,因此Java平台实现了最低特权的概念。 在与给定执行线程有关的整个调用程序堆栈中,其中调用堆栈的成员具有不同的特征,用于确定权限的结果是所有这些特征的交集或最小公分母。 例如,如果一段调用代码的权限有限(也许因为未签名而不受信任),但它调用了一段更受信任的代码(也许此代码具有签名),则该权限被调用代码中的减少以匹配较小的信任。

将访问控制上下文中包含的权限特征与策略文件中的Java权限grant语句进行比较,以指示是否允许进行敏感操作。 这是通过称为AccessController的Java工具完成的,该工具具有用于以编程方式检查权限并获取与活动访问控制上下文关联的当前Subject的接口。 (较早的Java Security Manager接口已经过时,因此请使用AccessController方法。)

将主题绑定到访问控制上下文

因为可以在应用程序启动后对Subject进行身份验证,所以必须有一种方法可以将Subject动态绑定到访问控制上下文,以创建一个包含代码授权(从何处加载和签名者)以及代码授权的单个上下文。用户权限( Subject )。 为此,我们使用Object doAs(Subject subject, PrivilegedAction action) doAs方法调用专门为授权而设计的类,该类实现PrivilegedAction接口。

Object doAsPrivileged(Subject, PrivilegedAction action, AccessControlContext acc)方法可用来指定访问控制上下文,而不是使用线程的当前Object doAsPrivileged(Subject, PrivilegedAction action, AccessControlContext acc) 此方法的一种特殊用法是将AccessControlContext设置为null,这会在发生doAsPrivileged调用时使调用堆栈短路,从而允许增加 PrivilegedAction对象中的PrivilegedAction 当对象返回给调用者时,权限将在以后减少。 这两种技术将在本教程的后面部分进行说明。

doAsdoAsPrivileged方法都以允许引发PrivilegedActionException形式出现。

权限

Java平台具有许多内置权限,这些权限用于控制对系统资源的访问。 例如:

grant signedBy "Brad", codeBase "http://www.bradrubin.com" {
       permission java.io.FilePermission "/tmp/abc", "read";
};

允许由“ Brad”签名并从“ http://www.bradrubin.com”加载的代码读取/tmp/abc目录。 请参阅相关主题的Java权限的完整列表。

创建自己的权限

Java平台使您可以创建自己的权限对象。 像常规权限一样,这些可以放置在策略文件中并在部署时进行配置。 为了演示,请看以下PersonnelPermission 稍后我们将使用此代码来允许访问某些敏感的人员操作代码。

import java.security.*;
//
// Implement a user defined permission for access to the personnel //
code for this example public class PersonnelPermission extends
BasicPermission {

     public PersonnelPermission(String name) {
       super(name);
     }

     public PersonnelPermission(String name, String action) {
       super(name);
     }
}

这是关于上述权限的注意事项:首先,构造函数采用用户定义的权限名称(在此示例中,只有一种类型,称为access )。 然后,第二个构造函数接受一个额外的优化参数,称为action ,尽管我们在这里不使用它。 在此示例中,我们将使用BasicPermission类。 如果需要更多功能,可以使用Permission类。

政策文件

策略文件是控制对系统资源(包括敏感代码)的访问的主要机制。 该示例中的策略文件名为jaas.policy,并在Java命令行中由-Djava.security.policy==jaas.policy属性-Djava.security.policy==jaas.policy 双等号( == )替换系统策略文件,而不是添加到系统策略文件权限。 这是我们在本教程中使用的jaas.policy文件:

grant {
     permission javax.security.auth.AuthPermission "createLoginContext";
     permission javax.security.auth.AuthPermission "doAs";
     permission javax.security.auth.AuthPermission "doAsPrivileged";
     permission javax.security.auth.AuthPermission "modifyPrincipals";
     permission javax.security.auth.AuthPermission "getSubject"; };

grant      principal PrincipalImpl "Brad" {
     permission PersonnelPermission "access";
};

系统必须具有某些权限(即示例中的前五个权限),以便引导JAAS机制。 有了这些权限后,就可以向主体“ Brad”授予对PersonnelPermission用户定义权限的访问权限。

JAAS主程序示例

这是此示例的主要应用程序(从命令行调用的程序)。 它实例化一个登录上下文,登录,尝试执行两个敏感对象(一个使用程序授权,另一个使用声明性授权),然后注销。 接下来,我们将仔细研究主程序的两个元素:程序化授权和声明性授权。

import java.security.*;
import javax.security.auth.*;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
//
// This is the main program in the JAAS Example.  It creates a Login  
// Context, logs the user in based on the settings in the Login  
// Configuration file,and calls two sensitive pieces of code, the  
// first using programmatic authorization, and the second using 
// declarative authorization.
public class JAASExample {

     static LoginContext lc = null;

     public static void main( String[] args) {
       //
       // Create a login context
       try {
         lc = new LoginContext("JAASExample",
            new UsernamePasswordCallbackHandler());
       } catch (LoginException le) {
         System.out.println( "Login Context Creation Error" );
         System.exit(1);
       }
       //

       // Login
       try {
         lc.login();
       } catch (LoginException le) {
         System.out.println( "\nOVERALL AUTHENTICATION FAILED\n" );
         System.exit(1);
       }
       System.out.println( "\nOVERALL AUTHENTICATION SUCCEEDED\n" );
       System.out.println( lc.getSubject() );
       //
       // Call the sensitive PayrollAction code, which uses programmatic
       // authorization.
       try {
         Subject.doAs( lc.getSubject(), new PayrollAction() );
       } catch (AccessControlException e) {
         System.out.println( "Payroll Access DENIED" );
       }
       //
       // Call the sensitive PersonnelAction code, which uses declarative
       // authorization.
       try {
         Subject.doAsPrivileged( lc.getSubject(), new PersonnelAction(), null );
       } catch (AccessControlException e) {
         System.out.println( "Personnel Access DENIED" );
       }
       try {
         lc.logout();
       } catch (LoginException le) {
         System.out.println( "Logout FAILED" );
         System.exit(1);
       }
       System.exit(0);

     }
}

程序授权示例

在此示例中,我们将看到如何对程序权限决策进行编码。 PrivilegedAction类由主JAASExample程序中的doAs方法调用,因此,经过身份验证的Subject进入run方法时,将绑定到线程上的应用程序上下文。

我们从访问控制器中检索当前的Subject ,并遍历任何包含的经过身份验证的Principal ,以查找“ joeuser”。 如果找到他,我们可以进行敏感的操作并返回。 如果没有,我们抛出一个AccessControlException 显然,在现实生活中,我们将使用一种更易于管理且可扩展的技术,而不是将用户名直接硬编码到应用程序中。

import java.io.*;
import java.security.*;
import javax.security.auth.*;
import javax.security.auth.login.*;
import java.util.*;
//
// This class is a sensitive Payroll function that demonstrates the
// use of programmatic authorization which only allows a subject 
// that contains the principal "joeuser" in class PayrollAction
implements PrivilegedAction {
     public Object run() {
       // Get the passed in subject from the DoAs
       AccessControlContext context = AccessController.getContext();
       Subject subject = Subject.getSubject( 
context );
       if (subject == null ) {
         throw new AccessControlException("Denied");
       }
       //
       // Iterate through the principal set looking for joeuser.  If
       // he is not found,
       Set principals = subject.getPrincipals();
       Iterator iterator = principals.iterator();
       while (iterator.hasNext()) {
         PrincipalImpl principal = (PrincipalImpl)iterator.next();
         if (principal.getName().equals( "joeuser" )) {
           System.out.println("joeuser has Payroll access\n");
           return new Integer(0);
         }
       }
       throw new AccessControlException("Denied");
     }
}

声明式授权示例

在此示例中,我们显示了如何使用用户定义的权限PersonnelPermission通过策略文件中的权限授予以声明方式控制授权检查。 我们只是问问AccessController是否已授予此权限,如果尚未授予,则将引发AccessControlException如果已授予,则将继续运行。 我们使用doAsPrivileged调用和主要JAASExample代码中的空访问控制上下文来调用此PrivilegedAction ,以在调用时使调用堆栈短路。 之所以需doAsPrivileged ,是因为在将SubjectdoAsPrivileged调用中的上下文合并之前, Subject不是上下文的一部分,也没有对Grant语句的授权,并且由于特权和权限交叉的使用最少,增加否则将不允许授权。

import java.io.*;
import java.security.*;
//
// This class is a sensitive Personnel function that demonstrates 
// the use of declarative authorization using the user defined 
// permission PersonnelPermission, which throws an exception 
// if it not granted 
class PersonnelAction implements PrivilegedAction {
     public Object run() {
       AccessController.checkPermission(new PersonnelPermission("access"));
       System.out.println( "Subject has Personnel access\n");
       return new Integer(0);
     }
}

JAAS示例

总览

最后,我们进入了教程的有趣部分:边做边学。 我们将从运行功能全面的JAASExample应用程序开始,该应用程序包含我们在整个教程中使用的所有身份验证和授权机制(和代码)。 在运行示例并查看第一个配置的结果之后,我们将探索一些不同的配置选项,并在进行过程中检查结果。 在本教程最后一部分的最后,您将初尝使用JAAS进行编程的初衷。

运行示例

JAASExample应用程序旨在显示几种身份验证和授权技术以及某些配置设置的影响。 使用以下语句开始运行示例,您可以在教程源文件的run.bat文件中找到该语句(请参阅JavaSecurity2-source.jar )。

java -Djava.security.manager
-Djava.security.auth.login.config==login.config
-Djava.security.policy==jaas.policy JAASExample

该语句指示系统的默认安全管理器使用名为login.config的登录配置文件,使用名为jaas.policy的安全策略文件,并最终运行主应用程序JAASExample。 请注意,双等号( == )表示系统默认登录配置和策略文件不应该被添加到我们这里列出的。 单个等号( = )表示文件应与系统默认值串联。

示例结果和注释

这是运行JAASExample的结果:

AlwaysLoginModule Login
Username? Brad

Login: AlwaysLoginModule SUCCESS

PasswordLoginModule Login
Username? joeuser
Password? joepw

Login: PasswordLoginModule Username Matches
Login: PasswordLoginModule Password Matches
Login: PasswordLoginModule SUCCESS
Commit: AlwaysLoginModule SUCCESS
Commit: PasswordLoginModule SUCCESS

OVERALL AUTHENTICATION SUCCEEDED

Subject:
Principal: Brad
Principal: joeuser

joeuser has Payroll access

Subject has Personnel access

Logout: AlwaysLoginModule SUCCESS
Logout: PassswordLoginModule SUCCESS

这是上述结果中正常执行的详尽描述:

  1. login.config定义了两个登录模块; AlwaysLoginModule是必需的。 它首先运行。
  2. AlwaysLoginModule从登录阶段开始,该阶段调用回调处理程序以获取用户名( Brad )。 登录成功。
  3. 第二个登录模块PasswordLoginModule是可选的。 它随后运行,调用回调处理程序以同时获得用户名( joeuser )和密码( joepw )。 此登录也成功。
  4. 因为必需模块和可选模块都成功,所以在两个登录模块上都调用了commit ,整个身份验证就成功了。 结果, Subject现在同时包含PrincipalBradjoeuser
  5. 使用程序授权的工资程序将检查joeuser是否在SubjectPrincipal集中,并授予访问权限。
  6. 人事程序,它使用声明性授权,看到有在使用的jaas.policy文件中的授权语句,授予Brad许可权PersonnelPermission ,所以它也成功。
  7. 两个登录模块都注销。

身份验证失败

只是为了好玩,让我们看看当我们做错事情时会发生什么。 在下面的示例中,设置相同,但是我们将为joeuser输入错误的密码。 检查下面的输出,并亲自查看与上面的结果有何不同。

AlwaysLoginModule Login
Username? Brad

Login: AlwaysLoginModule SUCCESS

PasswordLoginModule Login
Username? joeuser
Password? wrongpw

Login: PasswordLoginModule Username Matches
Login: PasswordLoginModule Password Mismatch
Login: PasswordLoginModule FAIL
Commit: AlwaysLoginModule SUCCESS
Commit: PasswordLoginModule FAIL

OVERALL AUTHENTICATION SUCCEEDED

Subject:
Principal: Brad

Payroll Access DENIED
Subject has Personnel access

Logout: AlwaysLoginModule SUCCESS
Logout: PasswordLoginModule SUCCESS

如您所见, PasswordLoginModule登录失败。 但是,由于此模块在login.config文件中配置为optional ,因此总体身份验证仍然成功。 区别在于仅Brad Principal被添加到Subject 工资核算程序找不到joeuser Principal ,因此访问被拒绝。 The personnel program was able to match the Brad Principal with the Brad grant statement, so it was successfully added and access was granted.

In the next several sections, we'll try out a few different variations in how we configure the login.config file, then check the results for each new config.

Variation 1: Login configuration

First, let's see what happens if we change the login.config file so that both login modules are required in order for authentication to be a success. The new config is:

JAASExample {
      AlwaysLoginModule required;
      PasswordLoginModule required;
};

And here's the resulting output:

AlwaysLoginModule Login
Username? Brad

Login: AlwaysLoginModule SUCCESS

PasswordLoginModule Login
Username? joeuser
Password? wrongpw

Login: PasswordLoginModule Username Matches
Login: PasswordLoginModule Password Mismatch
Login: PasswordLoginModule FAIL
Abort: AlwaysLoginModule SUCCESS
Logout: AlwaysLoginModule SUCCESS
Abort: PasswordLoginModule FAIL

OVERALL AUTHENTICATION FAILED

When joeuser entered the wrong password, the PasswordLoginModule failed just like it did before. Because this module was required, however, the abort phase ran and the overall authentication failed. No sensitive code was executed.

Variation 2: The power of PAM

This variation is designed to demonstrate the utility of Pluggable Authentication Modules. We go back to the original login.config file, which says that AlwaysLoginModule is required and PasswordLoginModule is optional, and add an NTLoginModule (or any other module appropriate for your platform) to the file. The new module will be required . The modified login.config file should look like this:

JAASExample {
      AlwaysLoginModule required;
      PasswordLoginModule optional;
      com.sun.security.auth.module.NTLoginModule required;
};

Next, run the example. In the output below you'll note that a new authentication method has been added, as well as several nifty new Principal s (and one public credential).

AlwaysLoginModule Login
Username? Brad

Login: AlwaysLoginModule SUCCESS

PasswordLoginModule Login
Username? joeuser
Password? joepw

Login: PasswordLoginModule Username Matches
Login: PasswordLoginModule Password Matches
Login: PasswordLoginModule SUCCESS
Commit: AlwaysLoginModule SUCCESS
Commit: PasswordLoginModule SUCCESS

OVERALL AUTHENTICATION SUCCEEDED

Subject:
Principal: Brad
           Principal: joeuser
           Principal: NTUserPrincipal: Brad
           Principal: NTDomainPrincipal: WORKGROUP
           Principal: NTSidUserPrincipal:
S-1-5-21-2025429265-1580813891-854245398-1004
           Principal: NTSidPrimaryGroupPrincipal: 
S-1-5-21-2025429265-1580418891-85 4245398-513
           Principal: NTSidGroupPrincipal: 
S-1-5-21-2025429265-1580818891-854245398-513
           Principal: NTSidGroupPrincipal: S-1-1-0
           Principal: NTSidGroupPrincipal: S-1-5-32-544
           Principal: NTSidGroupPrincipal: S-1-5-32-545
           Principal: NTSidGroupPrincipal: S-1-5-5-0-49575

           Principal: NTSidGroupPrincipal: S-1-2-0
           Principal: NTSidGroupPrincipal: S-1-5-4
           Principal: NTSidGroupPrincipal: S-1-5-11
           Public Credential: NTNumericCredential: 1240

joeuser has Payroll access

Subject has Personnel access

Logout: AlwaysLoginModule SUCCESS
Logout: PasswordLoginModule SUCCESS

And the cool thing is, we didn't even touch our application code. All of the above changes come from the native OS authentication mechanism. This should give you an inkling of the power of PAM.

Variation 3: Policy file configuration

In this final variation, we'll see what happens when we modify the access control policy. We start by modifying the grant file in the original login.config so that joeuser , not Brad has PersonnelPermission , as shown below:

grant Principal PrincipalImpl "joeuser" {
     permission PersonnelPermission "access";
};

Next, we run the application, entering the wrong password for joeuser . The results are shown below:

AlwaysLoginModule Login
Username? Brad

Login: AlwaysLoginModule SUCCESS

PasswordLoginModule Login
Username? joeuser
Password? wrongpw


Login: PasswordLoginModule Username Matches
Login: PasswordLoginModule Password Mismatch
Login: PasswordLoginModule FAIL
Commit: AlwaysLoginModule SUCCESS
Commit: PasswordLoginModule FAIL

OVERALL AUTHENTICATION SUCCEEDED

Subject:
Principal: Brad

Payroll Access DENIED
Personnel Access DENIED
Logout: AlwaysLoginModule SUCCESS
Logout: PasswordLoginModule SUCCESS

As you can see, only Brad is in the Subject 's Principal set. Both the attempted payroll access and the attempted personnel access have failed. 为什么? The first failed because there is no Principal named joeuser , and the second failed because there is no grant permission statement for Brad .

Don't stop here

In this section, we've pulled all of the JAAS authentication and authorization pieces together to illustrate a complete running JAAS application. We've also played with several variations to get a feel for what's really going on under the hood and how flexible this architecture is for application security.

To expand on what you've learned here, you should continue playing with JAAS and see what happens when you try different login configurations. For example, if you have Kerberos running in your installation, try running the Kerberos login module.

Wrapping up

摘要

This tutorial serves as an introduction to the Java platform authentication and authorization service, known as JAAS. In addition to becoming familiar with all the essential components of JAAS, you've had a hands-on introduction to several login modules and learned the basics of working with JAAS on the command line. You've also had the opportunity to run an actual JAAS application and try out several successful and unsuccessful configurations.

At the close of this tutorial, you should find yourself well poised for continuing exploration of the JAAS programming framework. Combining JAAS's authentication and authorization techniques with the cryptographic technologies discussed in Part 1 of this tutorial will enable you to architect and implement a vast array of application security solutions. To further your learning, you should continue to play with JAAS, exploring a number of different login modules, configurations, and security scenarios.


翻译自: https://www.ibm.com/developerworks/java/tutorials/j-sec2/j-sec2.html

你可能感兴趣的:(java验证身份证合法性_Java安全性,第2部分:身份验证和授权)