开发者学堂课程【高校精品课-上海交通大学-企业级应用体系架构:Security2 1】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/75/detail/15825
Security2 1
内容介绍
一、举例
二、讲述案例:注册
三、login in modules 的写法
四、代码
五、解决问题
一、举例
如果在 GetPropost.policy 代码中删除 OS(GetPropost.polley 的第二行代码)的属性,GetPropost.policy 的运行结果将会报错。
GetPropost.policy:
grant
{
Per
mission j
ava
.
ut
i
l.Pr
o
pertyPermission"java.version"
,
"rea
d”;
Per
mission j
ava
.
ut
i
l.Pr
o
pertyPermission"
01.name
"
,
"rea
d”;
Per
mission
java.util.PrepertyPermission " java. h
o
me"", "rea
d
"
}
对于 GetPropost.java 的代码是依次进行读取
先读取 java 的版本->操作系统-> java.home,由于操作系统在读取时已经抛出异常(抛出异常:意味着正常的流程不执行),则会被 catch,catch 后会输出 Caught exception ,将异常打印出(下图标红字体的第二行)。所以尽管允许读取 java.home,但由于程序终止,所以并未读取 java.home。
若删除 GetPropost.policy 的第一行代码,则无法读取出任何属性,可观察到读取至 java.version,直接报出异常,显示无法读取 java.version ,按照流程可以观察到在第一行代码就出现了问题,将会到 catch{} 代码,所以之后的代码也无法读取。之所以无法读取代码是受到权限控制,但之前学习的版本在运行过程中可能代码并无问题,而是执行出现问题
点击Getpript->Save->Application->Getpropt
注意:下图中的等号。==(俩个等号)指只用自己自定义的 policy,一个=(等号)是指想用自定义的 policy 叠加至系统的 policy ,而系统的 policy 允许用户读取 jar.home,所以如果将自定义的 policy 注释后仍然会出现。
二、讲述案例:注册
1.将课件里面描述的运行该例的命令(下述命令),复制到运行的配置中,让它工作的目录为 out/product 目录,
java -classpath login.jaraction.jar
-
Djava.security.policy=lAASTest.policy-
Djava.security.auth.login.config=Jaas.config AuthTest
点击运行代码,运行代码后会显示认证成功,运行成功后将允许读取
运行结果:
Authentication suCessful:
Subject=主体:
//主体自带俩个 Principal ,分别为 Username=harry,role=admin
主用户:SimplePrincipal@635a0cd9
主用户:SimplePrincipal@31d27I20
username=harry
role=admin
2.AuthTest.java 中代码的含义是:
(1)先预设好用户名为 harry,密码为 secret,使用 name=”login1” 的配置加载 login in modules
(2)Login1
{
SimpleLogin
.M
od
u
le required p
w
file="password.txt"
;}
Harry|secret|admin
Carl|buessme|HR
login1 在 Jass.config 中定义:
此次认证必须使用 SimpleLogin.Module。SimpleLogin.Module 会自带 required 属性(必须要通过),如果未通过,则该次认证失败。
带有参数 pwfile="password.txt" ,意为在同一个目录(password.txt)下,之前课程讲述的用|线分隔开的三列,第一列用户名,第二列是密码,第三列是角色。
该代码使用 SimpleLogin.Module 进行认证,
pwfile="password.txt"作为参数会传递给 SimpleLogin.Module。
(3)创建好 loginContest(login上下文)后,调用login。
上个课程所画的图:
已经在 Login-Config 中创建好 loginContest,就得知是使用 Login1加载的 LoginModule,在调用 Login时,loginContest 将会调用 LoginModule 来调用 login。
可在 AuthTest.java 中观察到在创建 loginContest 后,有第二个参数 SimpleCallbackHandler.
SimpleCallbackHandler 的作用:
New SimpleCallbackHandler(username, password )是我们所定义的创建一个对象,在下图中,上个课程讲述别人的 login 是用来输入东西,所以给用户发出了一些回调对象的 callbacks(),用户要用 callbacksHandler去处理该对象,callbacksHandler需要被LoginModule 用到。LoginModule 要用给它的 callbacksHandler 处理它希望用户填入的callbacks()。
(4)Context.login()(LoginModule)后如果成功认证,就可看到结果出现 Authentication successful
要将 Subject subject = context.getSubject() 取出,取出后发现 subject 中有对象后,可将 subject 中的 Principals 全部取出(因为经过认证后,已经带有了一些 Principals ),为了演示输入输出所以应新创建名为admin的 UninPrincipals (将Principals的数据取出后)将新的元素追加进 UninPrincipals 中。将下述图示的代码删除也可(演示所用)。
(5)特征动作:
创建一个特征动作 SysProAction,给该特征动作传入了属性,而特征动作在 SysProAction.java 中被定义,特征动作继承自 PrivilegedAction。
在代码 public Object run() 中 run 的作用是读取构造器传入的系统属性,所以在 AuthTest,java 中构造 SysProAction 时传入了“OS.name”,所以 run 会将“OS.name”传递给特征动作,实际上是我们想要获取系统的 OS 属性。
定义SysProAction:
(6)在
Object result=Subject.doAsPrivileged
(subject,action,AccessCentroller.getcontext()) 中
要
上述
认证过
subject
执行
action(要使用 AccessCentroller.getcontext())
。
在执行 action 时是否有权限,就要了解到该代码如何授权,授权时只有 principalClass 的 simpleprincipl”role=admin” 的制定程序的线程的 subject,才能够允许读取系统属性。
所以 AccessCentroller 是检查subject是否有 principl 来观察是否能执行 action。当前的问题是登录的模块是如何把 principl 给它入 subject 中。
三、login in modules 的写法
login in modules 在 js.consist 文件中可以写多个(但上述文件只描述了一个),但在 jass.configa 文件中可写多个 login in modules,在认证时会依次调用每个 login in modules 方法。
1.The following modules are supplied in the
com.sun.security.auth.module package:
-UnixLoginModule
-NTLoginModule
- Krb5LoginModule
- JndiLoginModule
- KeyStoreLoginModule
A login policy consists of a sequence of login modules, each of which is labeled required, sufficient, requisite, or optional:
- The modules are executed in turn, until a sufficient module succeeds, a
requisite module fails, or the end of the module list is reached.
- Authentication is successful if all required and requisite modules
succeed, or if none of them were executed, if at least one sufficient oroptional module succeeds.
例如:在 jass 中 login in modules 的配置有多个模块,将依次去调用
(1)每个模块后都有 required, sufficient, requisite, optional,意为是在java的安全体系中有俩个约定
首先按顺序执行,如果 login in modules 的配置有上述模块构成且按顺序执行,如果有一个 sufficient 模块成功,那么该事件工作(有五个模块,假如第二个模块sufficient(充分)成功,则后续不需要操作);如果某一个requisite模块失败, 则下述也不需操作,因为配置模块必须成功。
如果所有的属性都为required ,则会一直执行到底,在每个模块执行时,都会给一个标记,说明该模块成功还是失败(模块一:成功;模块二:成功;模块三:失败)。应注意如果失败,对于required 模块而言不会立即结束。(模块四:成功;模块五:成功),在全部成功之后,再次观察发现前俩个模块成功,第三个模块失败,最后两个模块成功,总体来说有一个required 模块失败,所以整个认证失败。上述即为我们所讲述的需要所有 required 模块和 requisite 模块都必须成功。
(2)复杂的原因:
假设5个模块
UnixLoginModule //常规的用户名密码
Retinon //扫描视网膜
fp //指纹
Voice //声音
用户在认证时,可能在指纹失败后会去洗手(拼命搓手),想办法过该关,原因是因为知道指纹失败。
但如果并不想让人知道指纹失败,方法是把模块全部变为required ,在过指纹失败后继续往下走,所有都执行结束后,最后结果显示失败,因此用户并不了解在那个环节失败。因此可以使用 required 。
使用sufficient 意味视网膜被仿造的可能性很低,只要视网膜成功,其他模块不需要检验就可跳过。
指纹失败后需要验练声音的原因是不想让用户知道是指纹失败而导致失败,但如果考虑到指纹失败的概率较大的原因是指纹可能不干净,就可将指纹设置为 optional(可选)。如果成功,其他模块也无问题(成功),所以对指纹是否成功没有要求,但如果其他模块不成功,并且其他模块也都为 optional,但是指纹成功,则会让用户通过。
因为上述所讲的细节比较复杂。所以在礼拜一的课堂中说它为可堆栈,可插拔。
可插拔:在任何一个 login in 配置文件里,随便去掉一个模块或加上一个模块都可以。将模块堆在一起之后,可用不同的属性去定义,定义出的效果会不同。
(3)上述所讲,可以了解到依赖配置文件来实现,并未依赖代码。所以Java 在很多程序(框架)中都是类似上述的途径。之后讲述事物冗,可了解到事物属性也是该途径。不仅是 java 的程序,写其他任何程序都应该达到在不改源码的情况下,是否能够自动识别的效果
回想在 spring 中讲 inotation 时讲 Autowried 的效果相同,Autowried 下变量的类型是 Interface 接口类型,spring能找到一个实现类注入进 Autowried 里的原因是:因为它有一系列的规则。例如:它帮助寻找非常相近的名字
注解到接口类型的原因:如果将来Interface接口换一个实现,代码不需要修改,将新的实现替换原来的实现,但使用该实现的代码无需修改,因为这些代码由接口变成。在OOAD 的分析设计里讲述,所有的东西应该依赖于稳定的东西,不稳定依赖于稳定,代码应容易做配置(修改),所以尽量不要修改源码,程序可以通过配置的方式实现达到同样的效果。
2.特征动作:上述所讲的实现 PrivilegedAction 扩展的类(自己写),在此处老师写的为命名类
下述代码是为了让同学们了解 principal 的过程,也可删去。
grant principalClass "principalName"
PrivilegedAction action = new
PrivilegedAction()
{
public Object run(){
{
// run with permissions of subject principals;具体逻辑
...
}
};
Subject.doAsPrivileged(subject,action,null);
//or doAs(subject,action);
重点是在执行的动作中,必须要认证之后的 subject 执行
3. 执行逻辑:
AuthTest.java:
//学习上述逻辑:
import java.security.*;
import javax.security.auth.*;
import javax.security.auth.login.*;
public class AuthTest
{
public static void main(final String[]args)
{
Try
{
System.setSecurityManager(new SecurityManagerO);
LoginContext context = new LoginContext("Login1");
context.login
();
System.out.println("Authentication successful.";
//安装安全管理器后再 Login1 在 Jaas.config中配置的认证模块进行登录;登录成功后会出来下述结果。
Subject subject = context.getSubject
()
;
//获得
subject
System.out.println("subject=" + subject);
PrivilegedAction action = new SysPropAction("user.home");
Object result=Subject.doAsPrivileged(subject,action
,
null);
//用 subject 执行特征动作
System.out.println(result);
context.logout
()
;
}
catch(LoginException e)
{
e.printStackTrace
()
;}
}
}
}
4.SysProAction.java
//在讲述完简单的类之后,我们学习 login in modules 里的代码
import java.security.*;
public class SysPropAction implements PrivilegedAction
//实现特征动作的接口
{
/
**
Constructs an action for looking up a given property.
@
param propertyName the property name (such as "userhome")
*/
public SysPropAction(String
propertyName)
{ this.propertyName = propertyName;}
public Object run
()
//代码很简单,获取系统属性,属性是从 SysPropAction()中传递
{
return System.getProperty(propertyName);
}
private String propertyName
;
}
5.AuthTest.policy
grant codebase "file:login.jar"
//在运行时一部分代码是
login
的 Authtest 类,权限是能够创建
LoginContext
和执行特征动作(
doAsPrivileged
)。
{
Permission
javax.security.
A
uth.AuthPermission
"createLoginContext.Login1";
permission javax.security.auth.AuthPermission
"doAsPrivileged";
};
//在真正执行动作时使用
principal
授权(必须有某一种
principal
),之后的代码都是自定义
grant principal com.sun.security.auth.UnixPrincipal "harry"
{
permission java.util.PropertyPermission "user.*" , "read";
}
- Jaas.config
//执行代码依据
config
Login1
{
com.sun.security.auth.module.UnixLoginModule required;
//使用
某一个用户名
密码是
必须要
实现
,
可观察到在
实际的程序
中在此处加入了自定义 LoginModule 的 pwfile="password.txt" 参数
Login1{SimpleLoginModule required pwfile="password.txt";};并未使用此处代码,此处代码讲述的是一般写法
};
6.举例
(1)
javac AuthTest.java
jar cvf login.jar AuthTest*.class
javac SysPropAction.java
jar cvf action.jar SysPropAction.class
java -classpath login.jar:action.jar
-Djava.security.policy= AuthTest.policy
-Djava.security.auth.login.config=jaas.config
AuthTest
将 AuthTest.java 压缩成 login.jar,对照 login.jar 授权 file:login.jar,此处有创建动作,将特征动作压缩成特征动作的 .jar 包。
执行特征动作,所有的类都在 login.jar:action.jar 中寻找,并且使用自己编写的 AuthTest.policy 以及用系统之前定义的 LoginModule 的 jaas.config ,最终运行 AuthTest 类。
(2)
we can add role-based permissions into a policy file:
grant principal SimplePrincipal "role=admin" {..
}
SimplePrincipal 包括 SimplePrincipal login module和codebase;上述所学的授权是针对 principal(执行代码)的线程观察subject 的principal ,必须具备 SimplePrincipal ,SimplePrincipal 的值为role=admin。
(3)
Our login module looks up users, passwords, and roles in a text file
that contains lines like this:
harry|secret|admin
Carl
|
guessme|HR
密码为 harry|secret|admin;
Carl|guessme|HR 文件中为三列。
(4)
The Login Module checks whether the user name and password
match a user record in the password file. If so, we add two
SimplePrincipal objects to the subject's principal set:
Set<Principal> principals = subject.getPrincipalsQ;
principals.add(new SimplePrincipal("username", username]);
principals.add(new SimplePrincipal(C" role", role]);
期望当 subject 在经过 Login Module 认证成功后,希望把它具有的 principal 的集合里添加两个新的 principal,是上述代码时所学的 username=harry 和 role=admin;之后将会学习上述三行代码应该放至何处。
7.An Example: SimpleLoginModule.
(1)The initialize method receives
The Subject that is being authenticated.
A handler to retrieve login information.
A sharedState map that can be used for communication between login
modules.
An options map that contains name/value pairs that are set in the login configuration.
编写 SimpleLoginModule,在 jass.config 中指定,SimpleLoginModule并且为 requird(必须成功),带有参数 pwfile。
在 SimpleLoginModule 中是扩展的 LoginModule 的类,类中有 initialize 的方法,该方法能够接收下述参数
①Subject :在认证的时候,实际上可能会有多种模块。所以在开始执行代码时 context.login() 时系统已经组装了subject 对象,subject 对象开始的 Principal 集合是空的,没有任 何Principal,之后 subject会被做参数之一传递给第一个模块,第一个模块在认证后会在 subject 里放一些 Principal,并把它当成参数再次传递给第二个模块,依次类推。所以我们现在所观察到的 initialize 里的第一个参数subject是线程对象。不管之前的 subject 如何,当前得到了一个 subject,subject 里可能已经带有一些 Principal 或者该 subjec t是第一个认证模块( Principal 为空的模块),总之得到 subject 的目的是对 subject 进行操作,subject 就是我们要认证的对象。
②Handler:
上述所讲的LoginModule 需要把一些 callback 发送给客户端,客户端要过填入信息之后返回,客户端决定用哪个 handler 处理callback。在本课上述所举的例子中:例如给了一个盘子,让你放入身份证,然后他给你办理机票,用镊子夹身份证还是拿手拿身份证都取决于我。因为 LoginModule 要将对象给 CallbackHandler,CallbackHandler 调用该对象得到处理 callbacks 的结果。
LoginModule 如何将对象传给 CallbackHandler:(注意观察代码)打开Auth.java,在创建LoginContext时,有第二参数New SimpleCallbackhandler(username, password),即可得到参数
所以客户端创建的该对象传递给了 Login 方法,然后 ContextLogin方法会依次传递给每一个 LoginModule。
③sharedState :
上述所谈到的问题(五个模块中前俩个和后俩个成功,第三个失败)
这些信息之间互相如何通信?如果不知道如何通信,就无办法确定整个过程到底是成功还是失败。比如说第三个模块 required 失败,则整个过程失败,即使每个模块与其他4个模块将该用户认证成功,也不会添加任何 Principal(只要有一个失败,不会有任何 PrincipaL),类似于一个人去机场,如果最终未上飞机,就不应该保留登记牌等东西,会被全部收回。所以每个模块互相之间需要有一个状态的沟通。
所有模块有一个共同的 map 表,每个模块都可 map 表中输入,相当于 LoginModule 之间公共的对象,每个模块可以从 map 表里得到信息。
④options :
是键值对,出现于 login configuration 中,所以现在就了解了密码文件如何传入。在 jass.config 中可观察到带有键值对。pwfile=”password.txt”,pwfile=”password.txt”是 LoginModule的options 。
(2)For example, we configure our module as follows:
SimpleLoginModule required pwfile=" password.txt";
即为 jass.config 文件中的代码。
(3)The login module retrieves the pwfile settings from the options map.
(4)The handler is specified when you construct the LoginContext
(5)For example,
LoginContext context = new LoginContext("Login1", new
com.sun.security.auth.callback.DialogCallbackHandlerO);/
/第二个参数
到目前为止我们将完了四个参数为pwfile=”password.txt”,第三个参数是每个模块所共有的。第二参数是在客户端创建LoginContext时所传递进的,第一个参数是系统帮助组装的。
initialize 类似于构造器,刚开始(创建出之后)会被调用。
8.handle 方法:
An Example: SimpleLoginModule.
(1)
The handle method of handlers
public void handle(Callback[] callbacks)
{for (Callback callback : callbacks) {
if (callback instanceof NameCallback) ...
else if (callback instanceof PasswordCallback)
...else . . .
}
}
handle 方法是:
LoginModule 希望得到用户名和密码,所以创建两个 Callbackhandle(Callback[] callbacks),传递给 handle方法。handler 里的 handle 方法是 handle(Callback[] callbacks),当将 callbacks 传递给 handle(Callback[] callbacks) 时,handle(Callback[] callbacks) 就用handle 去处理它。DialogCallbackHandler() 是客户端指定的 handler。客户端也可指定其他的 handler,无论如何都需有一个 handle 方法,而在 LoginModule 里,不管是什么,只要有handle 方法,就调用 handle 方法,把要传递进去的 Callbackhandle 传递进去,以往 LoginModule 里组装,从内存里填写或让用户输入或者是读数据库,都是 handler 要去解决的问题。
(2)
Prepare Callbacks for handler
NameCallback nameCall = new NameCallback("username: ");
PasswordCallback passCall = new PasswordCallback("password: ",false);
callbackHandler.handle(new Callback[] { nameCall, passCall });
所以 LoginModule 创建 callback,比如需要 new Callback()和 PasswordCallback()都是 Callback 的子类,然后就调用刚才从参数得到的 handler 的对象和的 handle 方法,然后把两个 Callback 放到一个 Callback 数组里传递,于是{ nameCall, passCall }中将会被填入数据
四、代码
1.SimbleLoginModule.java
import java.io.*;
import java.lang.reflect.*;
import java.security.*;
import java.util.*;
import javax.security.auth.*;
import javax.security.auth.login.*;
import javax.security.auth.callback.*;
import javax.security.auth.spi.*;
import javax.swing.*;
/**
This login module authenticates users by reading
usernames,passwords,and roles from a text file.
*/
public class SimpleLoginModule implements LoginModule
//观察完整的 Login 模块,SimpleLoginModule 是 Login 模块,必须要实现 LoginModule 接口。
-SimpleLoginModule.java
public void
initializ
e
(Subject subject,
CallbackHandler callbackHandler,
Map<String,?> sharedState,Map<String,?> options)
{
this.subject = subject;
this.callbackHandler'= callbackHandler;
this.sharedState = sharedState;
this.options = options;
}
//使用 initialize传递了四个方法subject,客户端发送的callbackHandler,多个模块互相分享状态 sharedState 的 map 对象,在 jass 配置文件里的 options 。该方法本身并未有什么作用,只是将四个参数赋予给当前的四个元素
public boolean login
()
throws LoginException
if (callbackHandler == null)
throw new LoginException("no handler");
NameCallback nameCall = new NameCallback("username: ");
PasswordCallback passCall = new PasswordCallback("password:" ,false);
try
{
callbackHandler.handle(new Callback[ { nameCall, passCall });
}
// login() 方法,对应有 logout() 方法,login() 方法线判断是否传入 callbackHandler ,如果没有传入,则将无法验证;如果有callbackHandler ,则组装一个 NameCallback 和 PasswordCallback,这俩个 Callback 刚开始是空的,将俩个 Callback 一个 Callback 数组里,调用 Handle 方法,调用结束之后,{ nameCall, passCall }将会带有内容,后续要对内容进行处理。
catch (UnsupportedCallbackException e)
{
LoginException e2 = new LoginException("Unsupported callback");
e2.initCause(e);
throw e2;
}
catch (IOException e)
{
LoginException e2 = new LoginException("I/0 exception in callback");
e2.initCause(e);
throw e2;
}
return checkLogin(nameCall.getName(), passCall.getPassword());
}
//调用自定义 checkLogin(),验证 callbackHandler 处理完之后的俩个 callback 里的 Name 和 Password 是否是我们所希望得到得内容。该逻辑取决于下述代码
/**
Checks whether the authentication information is valid. lf it is, the subject acquires principals for the user name and role.
@param username the user name
@param password a character array containing the password
@return true if the authentication information is valid
*./
private boolean checkLogin(String username,char[]password) throws LoginException
{
try
{
Scanner in = new Scanner(new FileReadér("" + options.get("pwfile"));
while (in.hasNextLine())
{
String[] inputs = in.nextLine(.split("\\|");
if (inputs[0].equals(username) &&Arrays.equals(inputs[1].toCharArray), password))
{ String role = inputs[2];
Set<Principal> principals = subject.getPrincipals();
principals.addnew SimplePrincipal("username", username));
principals.add(new SimplePrincipal("role" ,role));
return true;
}
}
//读取 options 中的 pwfile=”password.txt” 的属性信息,pwfile的键值对可以是多个,所以就会将 password.txt 文件取出;while (in.hasNextLine() 代表不断地遍历每一行,用|将每一行信息分隔开,观察 username 和 inputs[0].equals() 是否相同,password 和 inputs[1].toCharArray() 是否相同,如果相同则就认为该用户是合法用户,将他的角色取出,然后获取 subject 的 Principal 集合。
//如果是第一个模块,那么系统组装的 subject 为空(没有任何东西);如果它是中间的某一个模块,前面已经有一些模块被认证过,该subject 里现在也是空的,但是被认证过的模块都记住了自己是成功还是失败,所以有可能被认证过的模块会在 subject 中放入一些数据(稍后解释)。取出 subject 的 Principals 集合,然后再往里面放两个 SimplePrincipal(username和role),当前的模块经过他的认证带带有 username 和 role 两个属性(Principal)。
如果说用户名密码符合要求,否则该段代码不执行,所以认证通过后就会有这 username 和 role 的 Principal(在通过之后 loginmodule 会给 subject上添加了两个Principal)
in.close();
return false;
}
catch (IOException e)
{
LoginException e2 = new LoginException("Can't open password file");
e2.initCause(e);
throw e2;
}
public boolean logout(){ return true; }
public boolean abort(){ return true; }
public boolean commit(){ return true; }
private Subject subject;
private CallbackHandler callbackHandler;
private Map<String,?>sharedState;
private Map<String,?> options;
}
//在代码中有 logout() 是登出,abort(),commit();三个代码都较为简单,直接返回 true。
Subject,callbackHandler.sharedState,options是options处理的4个参数(成员变量)
2.SimplePrincipal.java
import java.security.*;
/*
*
A
principal with a named value (such as "role=HR"or"username=harry").
*
/
public class SimplePrincipal implements Principal
{
/
**
Constructs a SimplePrincipal to hold a description and a value.
@
param roleName the role name
*
/
public SimplePrincipal(String descr,String value)
{this.descr = descr; this.value = value;
}
/**
Returns the role name of this principa
@return the role name
*/
//在完整体系中有 Principal 接口(可直接实践),在处理 Principal的时,可回想起在上述创建 Principal 时传入了俩个参数(两个字符串),无论哪个Principal都是两个字串,第一个是(“username”,username),username 和它的值,第一个是(“role”,role),role 和它的值。所以 public SimplePrincipal(String descr,String value)中descr 是参数,value 是值。在传递的时候就应记住这俩个成员变量的值。
public String getName(){ return descr+ "=" + value;}
public boolean equals(Object otherObject)
{
if (this == otherObject) return true;
if (otherObject == null) return false;
if (getClass()!= other0bject.getClass()] return false;
SimplePrincipal other = (SimplePrincipal) otherObject;
return getName().equals( other.getName());
public int hashCode(){ return getName().hashCode(); }
private String descr;
private String value;
}
//Principal 的 getName() 会被系统调用,获取 Principal 的名字。{ return descr+ "=" + value;} 表示将会返回一个 username=harry 的字符串,该字符串跟授权文件对应,在代码中和“role=admin”对应,所以系统会调用 Principal 的 getName() 方法,返回的值跟这 java.util.PropertyPermission"*","read” 的内容比较决定该用户是否拥有权限
按照内容比较两个对象是否相等,所以重载 equals 和 hashCod e方法,而 SimplePrincipal 是简单的类。
3.SimpleCallbackHandlerjava
import javax.security.auth.callback.*;
/**
This simple callback handler presents the given user name and password
public class SimpleCallbackHandler implements CallbackHandler
/**
Constructs the callback handler.
@param username the user name
@param password a character array containing the password
public SimpleCallbackHandler(String username, charD password)
this.username = username;
this.password = password;
public void handle(Callback[]
callbacks)
{
for(Callback callback:callbacks)
{
if (callback instanceof NameCallback)
{
((NameCallback) callback).setName(username);
}
else if (callback instanceof PasswordCallback)
{
((PasswordCallback ) callback).setPassword(password);
}
}
}
private String username;
private char[
]
password;
}
//handler 系统有一个名为 CallbackHandler 的接口可直接实现,在上述代码创建 CallbackHandler 时创建了具体的数值(username 被定义了harry和passward被定义了secret),意为在定义SimpleCallbackHandler 时有一个构造器参数,将用户名密码直接复制,handler 方法是在对比第一个是否为 ifcallback ,第二个是否为PasswordCallback,如果都是则在 NameCallback 里将当前的用户名username设置作为用户名,下述都是类似将当前自身的成员属password 的设置作为 PasswordCallback 的 password。当前组装结束之后,PasswordCallback 和 NameCallback 里就带了 name(当时在创建 handler 传递进来用户名密码),所以 SimpleLoginModule.java代码中调用 handler 时,一开始 nameCal l和 passCall 是空的,但是当调用 handle方法把 nameCal l和 passCall 传递时,nameCall 和passCal l里就带有了数据,所以才能在后续代码中 nameCall 和passCall 通过 getName() 和 getPassword() 把用户名密码提取出来,校验它是否是合法用户。在写出了 Handle 后,即可执行整个代码。
4.JAASTest.policy
grant codebase "file:login.jar"
{
permission java.awt.AWTPermission"showWindowWithoutWarningBanner";
Permission javax.security.auth.AuthPermission
"createLoginContext.Login1";
permission javax.security.auth.AuthPermission"doAsPrivileged";
permission javax.security.auth.AuthPermission "modifyPrincipals";
permission java.io.FilePermission"password.txt","read";
};
grant principal SimplePrincipal "role=admin"
{
permission java.util.PropertyPermission "*","read";
};
//policy 模块要对 login 文件赋予权限,包括读取文件的权限,修改Principal 的权限(要在 Principal 中增加元素),执行特征动作,创建 LoginContext 权限,真正在执行特征动作是按照 Principal 执行(代码:grant principal SimplePrincipal "role=admin"),即可执行代码。读取所有的系统属性。
5.Jaas.config
Login1
{
SimpleLoginModule required pwfile="password.txt";};
//Login1 是使用自定义的 SimpleLoginModule ,required 是必须要成功,pwfile 为参数,几部分组合起来则是全部代码。
五.解决问题
1.在运行代码时,可在命令行运行,或在右上角 Auth.Text 处右击 VM options 的代码,执行 Autho.Test 类
使用代码:java -classpath login.jaraction.jar-
Djava.security.policy=
JA
ASTest.policy-
Djava.security.auth.login.config=Jaas.config AuthTest
在该处设置好之后就无需每次运行都设置,使用 Jaas.config,JAASTest.policy,执行压缩好的 action.jar 和 login.jaraction.jar
2.可观察到特性动作是要读取操作系统的名字,操作系统是(Mac OS X)。
无法执行结果,报出类异常:
Shall 的问题,可能由于 MAC 的机器 Shall 没有问题。如果在 Windows 上运行,可能是因为 Shall 的问题导致出错,可在 RunDebug中配置为上述所讲就能够解决该问题就好。而 Shall 超出我们讲课范围
3.该课程的目的,是在想告诉同学们整个认证的过程如何,而上述课程有俩处问题需要解决。
(1)问题一:
public boolean logout
()
{ return true; }
public boolean abort
()
( return true; }
public boolean commit
()
{ return true; }
上述三行代码描述过于简洁,实际上在 Principals.add 中不应直接添加,commit() 能够联想到 throw,意为做出的操作一直到 commit才算数,在此之前都在缓存里。
commit() 意为在可堆栈中前两个模块成功,第三个模块失败,在第一个模块认证成功(在loginmodule中),当发现用户名和密码符合要求的时候,不应直接 loginmodule 里加 Principal,而应该等待整个过程结束后,判断整体是成功还是失败,然后再加 Principal。系统应该在每个 login 方法里生成标记,让 java 虚拟机知道该模块成功或失败,然后在每个模块都成功的情况下才加入 Principal。所以在SimpleLoginModule .java 中只要用密码匹配就加 Principal 的代码写的过于简单,如下:
String[] inputs = in.nextLine(.split("\\|");
if (inputs[0].equals(username) &&Arrays.equals(inputs[1].toCharArray), password))
{ String role = inputs[2];
Set<Principal> principals = subject.getPrincipals();
principals.addnew SimplePrincipal("username", username));
principals.add(new SimplePrincipal("role" ,role));
return true;
解决方法一:
该代码不应该放在原代码中,应该打入一个标记,说明成功,因为public boolean login() throws LoginException 方法并未说明告诉要将Principal 加入,只是让返回一个布尔值,意为是在该模块是成功或失败,所以其实在上述代码所放位置应该记该模块成功还是失败,所以该段代码应放到 commit() 中,commit() 是在系统整个判断这一次认证成功还是失败的时被调用。而 commit() 被调用时才应放入上述代码,模块认证成功再放,即使模块认证成功,但整个流程失败,也不应放入代码,因为流程失败,系统会调用 abort(),所以应将代码放入 commit()。
解决方法二:(简单)
上述代码放在原处,但是在 abort() 和 logout() 中要将下述代码从代码中移除
principals.addnew SimplePrincipal("username", username));
principals.add(new SimplePrincipal("role" ,role));
则在认证之后只要用户名密码匹配,在此处认证逻辑成功,则增加俩个 Principal,如果整体失败,系统调用abort(),则将两个 Principal移除,即可保证不会出现争议失败但已经添加 Principal;在 logout()也是如此,要将该模块的 Principal 移除。
所以可堆栈可堆叠的三个方法不应如此简单,此处我们为了示意而将代码简单化
(2)问题二:
问题二和问题一(可堆栈)相关联相关联,当模块失败后,成功的模块不能加,因为如果第三个模块失败,告知整个认证失败,但是却带了一堆 Principal,因为有4个模块认证成功,不能给该用户授权,因为一旦授权虽然整个过程不成功,但带有一堆 Principal,只是失败的模块没有给他赋值,所以不符合要求,因为 login1 的目的是要经过所有的模块认证,才被认为成功,否则失败。如果按照上诉所说,应该只要有一个模块失败,则该 subject 不应带有任何 Principal;但是一旦成功即添加Principal,不成功则不添加 Principal,最后报错失败,而最终 subject 会带有 Principal 的情况是不被允许的。
模块和模块之间传递成功,失败,或中间的状态想传递,在此设计方案之下需要传递,上述所讲即为 ShareraState 的作用。尽管我们所讲述的例子中由于我们所涉及的模块不够多的原因并未用到,我们只有当前一个模块,无需在不同的Login module中通信,但不同的 Login module 之间有可能通信,只要有多个栈堆叠就需要通信,可能会通信将该模块认证的状态的发给下一个,下一个可能或用到,所以这就是 ShareraState 参数的作用。
观察我们写的 SimpleLoginModule.java 代码过于简单,真正的逻辑应该把4个方法(logout() ,login(),abort(),commit())加在一起,类似在数据库课学的两个阶段提交,第一个阶段判断自己是否可以,投票决定该事物提高还是回滚,
在真正 commit(),或者 abort() 才决定整个过程成功还是失败,所以添加 Principal 和移除 Principal,在 abort() 和 commit() 方法里必须有,而在 logout()里整个用户应该清零时也应该把相应的logout() 移除,完整的 Login module 结束。