java安全 ——JAAS(Java 认证和授权服务)开发指南
【0】README
2)传统的JAVA安全机制没有提供必要的架构支持传统的认证和授权;在J2SE里的安全是基于公钥密码体系和代码签名。也就是说,认证是基于在JVM里执行代码的思想,并且没有对资源请求提供策略。而且授权也是基于这样的概念--代码试图去使用一个计算机资源。Java认证和授权服务(JAAS)也就被设计成去应付这些缺点。
3)JAAS使用基于用户的的访问控制增加了这个已经存在的基于代码的访问控制机制,也提高了认证能力。这样你赋予的权限不仅是什么代码在运行,而且可以是谁在运行它。这篇文章:
- 讨论基于代码的认证的缺点
- 提供JAAS的概览
- 展示了一个加强的代码的认证例子
- 展示了一个加强的代码的授权例子
- 给予了在开发认证和授权服务时调用成功的感觉。
- 提供了能够适应与你所拥有的应用程序的简单代码
- 提供了一个开始学习JAAS的指导
4)目的
传统的操作系统(例如UNIX)通过多种challenge-response机制对主体或实体进行认证。用户名和密码组合是属于最常见的了。这种技术也被用来使用HTTP基本认证方案保护资源。无论怎样,challenge可以做得更复杂:例如,可以加密信息或者依赖与特定信息的持有者(比如妈咪的小名或者一个所选问题的答案)。然后基于这种challenge类型的响应必须是有效的。
此外,大多数操作系统在一个实体或主体上的和资源列表上的基本授权是授予实体或主体去使用。例如,当一个用户尝试读或者写一个文件时,授权机制就会去验证当前正在执行的主体是否有权限去访问这个资源。
5)JAAS概览
JAAS分为2个部分:认证(authentication)和授权(authorization). 也就是说,认证和授权两个都可以使用。
- 对于认证用户可以安全地决定谁正在执行代码,即使代码是一个单独的应用程序、一个applet、一个企业级JavaBean或者一个servlet。
- 对于用户的授权,可以确认他们是否有必要的权限去执行他们的操作。
认证是基于Plugable Authentication Modules(PAMs)(可插入认证模型),它使用一个框架,用于客户端和服务端。,认证部分是一个使用存在于J2SE中策略文件的认证方案的扩展。在一个可插入的方式中执行认证使Java应用程序可以去依赖于底层的安全机制。这样做有个优点,就是新的或者修正的认证机制可以方便地插入,而不用对应用程序本身做修改。
授权是一个基于存在的策略文件的机制的扩展,基于策略文件的机制被用作指定一个应用程序(或可执行代码)能或者不能做什么操作。它是基于保护域的。也就是说,这种机制授权基于代码来自哪里,而不是基于谁在执行代码。用JAAS permissions或者访问控制不仅能控制在运行什么代码,也能控制谁正在运行它。
注意:JAAS在J2SE 1.3.x是作为一个可选的包,但是到J2SE 1.4 已经被完全整合了。
java.security.Policy API已经被更新了,能够在策略文件里支持基于主体的查询和基于主体的授权,等下你就可以看到
---------------------------------------------------------------------------------------------------------------------
【1】使用JAAS认证(Authentication)
0)客户端通过一个LoginContext对象与JAAS相互作用,这个LoginContext对象提供了一种开发应用程序的方式,它不依赖于底层的认证技术。
4)LoginContext对象调用负责实现和执行认证的LoginModules。LoginModule接口(在javax.security.auth.spi 包里)必须让认证技术的提供者去实现,并且能够被应用程序指定提供一个特定认证类型。
4.2)Configuaration: 被某个特定的应用程序用作指定认证技术或者LoginModule。因此,不同的LoginModules能够被应用到某个应用程序而不用对这个应用程序做任何的代码修改。
2.1)简单代码1展示了一个简单的JAAS客户端。
简单代码1:MyClient.java
package com.corejava.chapter9.authentication;import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;// LoginContext:是javax.security.auth.login包里的一个类,它描述了用于验证对象(subjects)的方法。
// LoginContext对象调用负责实现和执行认证的LoginModules。
public class MyClient
{public static void main(String[] args){LoginContext context = null;try{// 在配置文件example.conf里,实体名"WeatherLogin"就是被MyClient.java用作关联这个实体的名字。context = new LoginContext("WeatherLogin", new MyCallbackHandler());} catch (LoginException le){System.err.println("LoginContext cannot be created. "+ le.getMessage());System.exit(-1);} catch (SecurityException se){System.err.println("LoginContext cannot be created. "+ se.getMessage());}try{context.login();} catch (LoginException le){System.out.println("Authentication failed. " + le.getMessage());System.exit(-1);}System.out.println("authentication succeeded.");System.exit(-1);}
}<strong>
</strong>
一个基于JAAS的应用程序实现了CallbackHandler接口,因此它能够提示用户去输入特定的认证信息,比如用户名或者密码,或者显示错误或者警告信息。底层安全服务可能要求通过传递单个的callbacks到回调处理程序。基于传递的callbacks,回调处理程序决定怎样去获取和显示信息。例如,如果底层服务需要一个用户名和密码认证一个用户,它可以使用NameCallback和PasswordCallback. 其他的callbacks类都在javax.security.auth.callback里,包括:
- ChoiceCallback (显示一个选择列表)
- ConfirmationCallback (询问 YES/NO, OK/CANCEL)
- LanguageCallback (用作区域化的Locale)
- TextInputCallback (获取普通的文本信息)
- TextOutputCallback (显示信息,警告和错误信息)
实现CallbackHandler接口意味着你需要实现handler方法,以获取或者显示在提供的callbacks里要求的信息。简单代码2是一个简单的实现。注意在这里我使用NameCallback与用户关联。
简单代码2:MyCallbackHandler.java
package com.corejava.chapter9.authentication;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.UnsupportedCallbackException;// 一个基于JAAS的应用程序实现了CallbackHandler接口,
// 因此它能够提示用户去输入特定的认证信息,比如用户名或者密码,或者显示错误或者警告信息。
// 基于传递的callbacks,回调处理程序决定怎样去获取和显示信息。
public class MyCallbackHandler implements CallbackHandler
{@Overridepublic void handle(Callback[] callbacks) throws IOException,UnsupportedCallbackException{for (int i = 0; i < callbacks.length; i++){if(callbacks[i] instanceof NameCallback){NameCallback nc = (NameCallback)callbacks[0];System.err.println(nc.getPrompt());System.err.flush();String name = (new BufferedReader(new InputStreamReader(System.in))).readLine();nc.setName(name);}else{throw new UnsupportedCallbackException(callbacks[i], "callback handler not support");}}}}<strong>
</strong>
无论怎样,简单代码3展示了一个LoginModule简单的实现. 这个例子是非常简单的,因为他仅仅有一个认证字符串和一个Principal "SunnyDay", 两个都是硬编码。如果去login,系统将显示"What is the weather like today?", 如果答案是"Sunny", 用户就能通过。
- initialize: 这个方法的目的就是用有关的信息去实例化这个LoginModule。如果login成功,在这个方法里的Subject就被用在存储Principals和Credentials. 注意这个方法有一个能被用作输入认证信息的CallbackHandler。在这个例子里,我没有用CallbackHandler. CallbackHandler是有用的,因为它从被用作特定输入设备里分离了服务提供者。
- login: 请求LoginModule去认证Subject. 注意此时Principal还没有被指定。
- commit: 如果LoginContext的认证全部成功就调用这个方法。
- abort: 通知其他LoginModule供应者或LoginModule模型认证已经失败了。整个login将失败。
- logout: 通过从Subject里移除Principals和Credentials注销Subject。
简单代码3:WeatherLoginModule.java
package com.corejava.chapter9.authentication;import java.util.Map;
import java.util.Set;import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;// 以下代码展示了一个LoginModule简单的实现. 这个例子是非常简单的,
// 因为他仅仅有一个认证字符串和一个Principal(特征) "SunnyDay", 两个都是硬编码。
// 如果去login,系统将显示"What is the weather like today?", 如果答案是"Sunny", 用户就能通过。
public class WeatherLoginModule implements LoginModule
{private Subject subject;private ExamplePrincipal entity;private CallbackHandler callbackhandler;private static final int NOT = 0;private static final int OK = 1;private static final int COMMIT = 2;private int status;// initialize: 这个方法的目的就是用有关的信息去实例化这个LoginModule。// 如果login成功,在这个方法里的Subject就被用在存储Principals和Credentials. // 注意这个方法有一个能被用作输入认证信息的CallbackHandler。在这个例子里,我没有用CallbackHandler. // CallbackHandler是有用的,因为它从被用作特定输入设备里分离了服务提供者。public void initialize(Subject subject, CallbackHandlercallbackhandler, Map state, Map options){status = NOT;entity = null;this.subject = subject;this.callbackhandler = callbackhandler;}// login: 请求LoginModule去认证Subject. 注意此时Principal还没有被指定。public boolean login() throws LoginException{if (callbackhandler == null){throw new LoginException("No callback handler is available");}Callback callbacks[] = new Callback[1];callbacks[0] = new NameCallback("What is the weather like today?");String name = null;try{// 调用 MyCallbackHandler.java 中的 handle 方法进行处理// 以读入用户输入的认证信息(如 username)callbackhandler.handle(callbacks); name = ((NameCallback) callbacks[0]).getName();} catch (java.io.IOException ioe){throw new LoginException(ioe.toString());} catch (UnsupportedCallbackException ce){throw new LoginException("Error: " + ce.getCallback().toString());}if (name.equals("Sunny")){entity = new ExamplePrincipal("SunnyDay");status = OK;return true;} else{return false;}}// commit: 如果LoginContext的认证全部成功就调用这个方法。public boolean commit() throws LoginException{if (status == NOT){return false;}if (subject == null){return false;}Set entities = subject.getPrincipals();if (!entities.contains(entity)){entities.add(entity);}status = COMMIT;return true;}// abort: 通知其他LoginModule供应者或LoginModule模型认证已经失败了。整个login将失败。public boolean abort() throws LoginException{if ((subject != null) && (entity != null)){Set entities = subject.getPrincipals();if (entities.contains(entity)){entities.remove(entity);}}subject = null;entity = null;status = NOT;return true;}// logout: 通过从Subject里移除Principals和Credentials注销Subject。public boolean logout() throws LoginException{subject.getPrincipals().remove(entity);status = NOT;subject = null;return true;}}<strong>
</strong>
正如你从简单代码3里所看到的,一个ExamplePrincipal类正在被使用。这个类是Principal接口的一个实现。
简单代码4展示了Principal接口的一个实现。
简单代码4:ExamplePrincipal.java
package com.corejava.chapter9.authentication;import java.security.Principal;// ExamplePrincipal 展示了Principal(主体特征)接口的一个实现。
public class ExamplePrincipal implements Principal
{private final String name;public ExamplePrincipal(String name){if (name == null){throw new IllegalArgumentException("Null name");}this.name = name;}public String getName(){return name;}public String toString(){return "ExamplePrinciapl: " + name;}public boolean equals(Object obj){if (obj == null)return false;if (obj == this)return true;if (!(obj instanceof ExamplePrincipal))return false;ExamplePrincipal another = (ExamplePrincipal) obj;return name.equals(another.getName());}public int hasCode(){return name.hashCode();}
}
正如我前面提及的,LoginContext通过读取Configuration去决定那个LoginModule将被使用。login配置文件可以是一个文件或者是数据库。当前Sun Microsystems默认的实现是一个文件。一个login配置文件包含一个或者多个实体,这些实体指出了那些认证技术应该被拥有应用程序。简单代码5展示了一个login配置文件。
简单代码5:example.conf
WeatherLogin
{com.corejava.chapter9.authentication.WeatherLoginModule required;
};
在这个配置文件里,实体名"WeatherLogin"就是被MyClient.java用作关联这个实体的名字。这里的这个实体指出WeatherLoginModule应该被用作执行认证。为了使整个认证成功,这个模型(module)的认证必须是成功的。用户输入了正确的信息就是成功的。
运行例子程序
- 在根目录下创建一个"auth"的目录;
- 把MyClient.java, WeatherLoginModule.java, ExamplePrincipal.java, example.conf复制到这个目录下;
- 编译所有的java文件:javac *.java;
- 使用下面的命令(指定了login配置文件)运行客户端, prompt> java -Djava.security.auth.login.config=example.conf MyClient
你应该可以看到下面的输出(粗体文本是用户输入的)。What is the weather like today? gloomy
Authentication failed. Login Failure: all modules ignored在用正确的输入运行客户端:
What is the weather like today? Sunny
Authentication succeeded
- initialize: 这个方法的目的就是用有关的信息去实例化这个LoginModule。如果login成功,在这个方法里的Subject就被用在存储Principals和Credentials. 注意这个方法有一个能被用作输入认证信息的CallbackHandler。在这个例子里,我没有用CallbackHandler. CallbackHandler是有用的,因为它从被用作特定输入设备里分离了服务提供者。
- login: 请求LoginModule去认证Subject. 注意此时Principal还没有被指定。
- commit: 如果LoginContext的认证全部成功就调用这个方法。
- abort: 通知其他LoginModule供应者或LoginModule模型认证已经失败了。整个login将失败。
- logout: 通过从Subject里移除Principals和Credentials注销Subject。
UnixLoginModuleNTLoginModuleKrb5LoginModuleJndiLoginModuleKeyStoreLoginModule |
【3】安全管理器
在上面的例子里,默认是没有运行在安全管理器下的,因此所有操作都是允许的。为了保护资源,可以使用下面的命令去运行在安全管理器下:
- C:\sun\code\auth>java -Djava.security.manager//
- -Djava.security.auth.login.config=example.conf MyClient
- LoginContext cannot be created. access denied//
- (javax.security.auth.AuthPermission createLoginContext.WeatherLogin)
- Exception in thread "main" java.lang.NullPointerException
- at MyClient.main(MyClient.java:17)
你可以看到抛出了一个异常。默认的安全管理器不允许任何操作,因此login上下文没有被创建。要允许这些操作,你必须创建一个安全策略(安全策略是一个赋予代码能不能执行的权限的文本文件。简单代码6展示了一个简单的安全策略。目标createLoginContext使MyClient能够实例化一个login上下文。目标modifyPrincipals允许WeatherLoginModule用一个Principal去构造一个Subject.
简单代码6:policy.txt
grant codebase "file:./*"
{ permission javax.security.auth.AuthPermission "createLoginContext"; permission javax.security.auth.AuthPermission "modifyPrincipals";
};
现在你就能够使用下面的命令来运行了,注意双等号(==)是用来覆盖默认安全策略的。
prompt>java -Djava.security.manager -Djava.security.policy==policy.txt
-Djava.security.auth.login.config==example.conf MyClient
What is the weather like today? Sunny
Authentication succeeded
4.1)JAAS授权继承了以代码为中心的JAVA安全体系结构(它使用一个安全策略指定什么样的访问权限授予执行中的代码。例如,在简单代码6的安全策略里,所有当前目录的的代码都被授权;不管代码有没有签名,或者谁在运行这些代码。JAAS也继承了以用户为中心(user-centric)的访问控制。许可权的赋予不仅是正在运行什么代码,而且也看谁在运行它。很快你将看到许可权能够在策略文件里被赋予去指定principals。
4.2)为了使用JAAS授权:
- 用户必须是我在上面认证过的;
- Subject的doAs(或者doAsPrivileged)方法必须被调用,它在反过来调用run方法(包含作为主体去执行的)。
基于这个,ExamplePrincipal.java, WeatherLoginModule.java和 example.conf文件仍然是同一个。
简单代码7: MyClient.java
package com.corejava.chapter9.authorization;import java.security.PrivilegedAction;import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;import com.corejava.chapter9.authentication.MyCallbackHandler;public class MyClient
{public static void main(String argv[]) { LoginContext ctx = null; try { ctx = new LoginContext("WeatherLogin", new MyCallbackHandler()); } catch(LoginException le) { System.err.println("LoginContext cannot be created. "+ le.getMessage()); System.exit(-1); } catch(SecurityException se) { System.err.println("LoginContext cannot be created. "+ se.getMessage()); }try { ctx.login(); } catch(LoginException le) { System.out.println("Authentication failed"); System.exit(-1); } System.out.println("Authentication succeeded"); // 为了使客户端能够赋予用户权限,一旦认证成功(用户已经被认证了),// 认证了的主体就使用通过Subject subject=ctx.getSubject()来获取。Subject subject = ctx.getSubject(); System.out.println(subject.toString());PrivilegedAction action = new MyAction(); // Subject的doAs(或者doAsPrivileged)方法必须被调用,它在反过来调用run方法(包含作为主体去执行的)。Subject.doAsPrivileged(subject, action, null); try { ctx.logout(); } catch(LoginException le) { System.out.println("Logout: " + le.getMessage()); } }
}<strong>
</strong>
你可以看到,我们传递了一个action给doAsPrivileged,action就是用户被授权去执行的行为。简单代码8展示了MyAction.java, 它通过提供run方法的代码实现了PrivilegedAction接口,run方法包含了所有将用基于principal的认证检查的代码。现在,当执行doAsPrivileged方法时,它就会反过来调用在PrivilegedAction里的方法,去实例化主体其他方面剩余代码的执行。
在这个例子里,action就是去检查文件"test.txt"存在于当前的目录。
简单代码8:MyAction.java
package com.corejava.chapter9.authorization;import java.io.File;
import java.security.PrivilegedAction;public class MyAction implements PrivilegedAction
{@Overridepublic Object run(){System.out.println("this is the 1st authorization task after passing authentication"); File file = new File("com/corejava/chapter9/authorization/test.txt"); if(file.exists()) { System.out.println("The file exists in the current working directory"); }else { System.out.println("The file does not exist in the current working directory"); } return null; }
}<strong>
</strong>
现在我需要更新policy.txt文件。简单代码9展示了这些更改。
- 为了Subject类的doAsPrivileged方法,你需要有一个有doAsPrivileged目标的java.security.auth.AuthPermission.
- 添加了一个基于principal的授权;它声明了一个"SunnyDay"的pincipal被运行去读这个"test.txt"文件。
简单代码9:policy.txt
grant codebase "file:./*"
{ permission javax.security.auth.AuthPermission "createLoginContext"; permission javax.security.auth.AuthPermission "modifyPrincipals"; permission javax.security.auth.AuthPermission "doAsPrivileged";
}; grant codebase "file:./*", Principal ExamplePrincipal "SunnyDay"
{ permission java.io.FilePermission "com/corejava/chapter9/authorization/test.txt", "read";
};
现在当你运行这个应用程序,它将首先执行认证。如果认证失败,应用程序就停止,否则它将继续去检查这个文件。看下面简单的运行:
prompt>java -Djava.security.manager -Djava.security.policy==policy.txt//
-Djava.security.auth.login.config==example.conf MyClient
What is the weather like today? sunny
Authentication failed
prompt>java -Djava.security.manager -Djava.security.policy==policy.txt//
-Djava.security.auth.login.config==example.conf MyClient
What is the weather like today? Sunny
Authentication succeeded
The file does not exist in the current working directory
E:\bench-cluster\cloud-data-preprocess\CoreJavaAdvanced\src>java -Djava.security.manager -Djava.secu
JAAS是一个能够认证和强制执行用户访问控制的服务的API集合。JAAS认证使用可插入的方式,它不依赖与底层的认证技术;JAAS授权使用以用户为中心的访问控制和认证能力提高了以原本以代码为中心的JAVA安全体系。
java安全 ——JAAS(Java 认证和授权服务)开发指南相关推荐
- Java生鲜电商平台-SpringCloud微服务开发中的数据架构设计实战精讲
Java生鲜电商平台-SpringCloud微服务开发中的数据架构设计实战精讲 Java生鲜电商平台: 微服务是当前非常流行的技术框架,通过服务的小型化.原子化以及分布式架构的弹性伸缩和高可用性, ...
- anychat java开发文档_AnyChat视频云平台开发指南
本帖最后由 佰锐科技-刘冬明 于 2017-9-13 11:59 编辑 简介: AnyChat视频云平台提供线上的音视频通信.多应用接入.即时通讯.全景录像.智能排队,直播.点播等服务.为您定制个性化 ...
- QtJava笔记-Qt与Java进行SSL双向认证(Qt服务端,Java客户端)
程序运行截图如下: Qt作为服务端,Java作为客户端. 这里的服务端是用的p12证书,客户端使用的是jks. 具体的生成方式看以前的博文. QSSLServer.h #ifndef QSSLSERV ...
- Java 单体服务开发指南
文章目录 一.代码组织模式 1.多仓库 2.单体仓库 二.编程规约(参考<阿里 Java 开发手册>) 1.命名风格 2.常量定义 3.代码格式 4.OOP 规约 5.日期时间 6.集合处 ...
- Spring Security Oauth2 授权服务开发
2019独角兽企业重金招聘Python工程师标准>>> 集成开发环境 ·开发工具:Eclipse/Myeclipse/IntelliJ IDEA 任选其一 ·运行环境:jdk1.7及 ...
- 网关协议——OpenID Connect(身份认证+OAuth2授权)入门指南
OpenID Connect 如果要谈单点登录和身份认证,就不得不谈OpenID Connect (OIDC).最典型的使用实例就是使用Google账户登录其他应用,这一经典的协议模式,为其他厂商的第 ...
- Java微服务开发指南-Java环境下的微服务
本文涉及的内容,能让你学到什么? 本书适用于开发微服务的Java开发人员和架构师.我们在开始介绍微服务架构前,先讲述一些抽象的基本概念.不幸的是,使用新技术并不能神奇地解决分布式系统问题.但是我们通过 ...
- Java微服务开发指南 -- Java环境下的微服务
Java环境下的微服务 本文涉及的内容,能让你学到什么? 本书适用于开发微服务的Java开发人员和架构师.我们在开始介绍微服务架构前,先讲述一些抽象的基本概念.不幸的是,使用新技术并不能神奇地解决分布 ...
- Java微服务开发指南-使用Dropwizard构建微服务
Dropwizard的历史要早于Spring Boot和WildFly Swarm,它最早是在2011.12发布的v0.1.0版本,在本文编写的过程中,它已经发布了v0.9.2版本,而v1.0.0版本 ...
最新文章
- 单机redis 主从实例
- java redis 故障切换_java使用Redis6–sentinel单点故障主从自动切换
- Python编程高手之路——第三章:数据类型
- Python程序设计语言基础02:Python基本图形绘制
- 思科网络设备模拟器GNS3与SecureCRT关联
- es 创建索引 指定id_简单操作elasticsearch(es版本7.6)
- 感受晋味新年俗 你的高铁票可享景区门票优惠
- 姿态估计mmpose一手体验 Ⅱ - 使用它!
- 10年日语营业转行IT从深圳到日本东京圈工作生活2019copy
- Process Hacker 简单介绍
- 微信的常用设备 只能看到android,安卓手机撤回的微信图片可以查看啦,赶紧学起来...
- 塞尔达:旷野之息个人对比上古卷轴V:天际
- R语言读取 文件 中文乱码,R语言画图 中文不显示
- 话费API 洗车API接口源码分享
- ubuntu16.04下basler工业相机的配置与使用
- Android源码——小苏闹钟
- Dell XPS13 新安装ubuntu16.04LTS 没有显卡没有声卡解决方案
- 基于Metronic的Bootstrap开发框架经验总结(1)-框架总览及菜单模块的处理
- 基于nodejs+vue+elementUI在线跑腿系统 含在线地图功能(前后端分离)#毕业设计
- markdown修改文字背景色
热门文章
- H - Prince and Princess 计蒜客 - 42402
- [SDOI2011]消耗战
- CF741C Arpa’s overnight party and Mehrdad’s si
- 牛客题霸 [表达式求值] C++题解/答案
- 牛客练习赛60 ~ 斩杀线计算大师
- 【学习笔记】我命由天不由我之随机化庇佑 —— 爬山法 和 模拟退火法
- [CQOI2017] 老C的键盘(树形dp + 组合数)
- CF1037H Security(SAM)
- P3247-[HNOI2016]最小公倍数【分块,并查集】
- Codeforces1142D