Java安全之认证与授权

来源:互联网 发布:gta5 n卡优化 编辑:程序博客网 时间:2024/06/11 03:06

http://blog.csdn.net/xtayfjpk/article/details/45872161 

 Java平台提供的认证与授权服务(Java Authentication and Authorization Service (JAAS)),能够控制代码对敏感或关键资源的访问,例如文件系统,网络服务,系统属性访问等,加强代码的安全性。主要包含认证与授权两部分,认证的目的在于可靠安全地确定当前是谁在执行代码,代码可以是一个应用,applet,bean,servlet;授权的目的在于确定了当前执行代码的用户有什么权限,资源是否可以进行访问。虽然JAAS表面上分为了两大部分,而实际上两者是密不可分的,下面看一段代码:


[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class App {  
  2.       
  3.     public static void main(String[] args) {  
  4.         System.out.println(System.getProperty("java.home"));  
  5.     }  
  6. }  

非常简单只是输出java.home系统属性,现在肯定是没有任何问题,属性会能正常输出。把上述代码改为如下后:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class App {  
  2.       
  3.     public static void main(String[] args) {  
  4.         //安装安全管理器  
  5.         System.setSecurityManager(new SecurityManager());  
  6.           
  7.         System.out.println(System.getProperty("java.home"));  
  8.     }  
  9. }  

抛出了如下异常:java.security.AccessControlException: access denied ("java.util.PropertyPermission" "java.home" "read"),异常提示没有对java.home的读取权限,系统属性也是一种资源,与文件访问类似;默认情况下对于普通Java应用是没有安装安全管理器,在手动安装安全管理器后,如果没有为应用授权则没有任何权限,所以应用无法访问java.home系统属性。

授权的方式是为安全管理器绑定一个授权策略文件。由于我是在eclipse Java工程中直接运行main方法,所以就在工程根目录下新建一个demo.policy文件,文件内容如下:

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. grant  {  
  2.     permission java.util.PropertyPermission "java.home", "read";  
  3. };  

该授权的效果是任何用户运行的任何程序都有对java.home的读权限,policy文件的具体格式请参看:http://docs.oracle.com/javase/7/docs/technotes/guides/security/PolicyFiles.html
为安全管理器绑定policy文件的方式有两种:一、在运行程序的时候加入-Djava.security.policy=demo.policy虚拟机启动参数;二、执行System.setProperty("java.security.policy", "demo.policy");其实两者的效果一样,都是在设置系统属性,其中demo.policy是路径,这里为了简单指定的是相对路径,绝对路径当然也没问题。再次运行程序不再抛出异常,说明程序拥有了对java.home系统属性的读取权限。在Java中权限有很多,具体可参考:http://docs.oracle.com/javase/7/docs/technotes/guides/security/spec/security-spec.doc3.html#17001

在上述过程中虽然完成了授权,但授权的针对性不强,在程序绑定了该policy文件后,无论是哪个用户执行都将拥有java.home系统属性的读权限,现在我们希望做更加细粒度的权限控制,这里需要用到认证服务了。

认证服务有点“麻烦”,一般情况下主要都涉及到了LoginContext,LoginModule,CallbackHandler,Principal,后三者还需要开发者自己实现。这里先解释一下这几个类的作用:
1.LoginContext:认证核心类,也是入口类,用于触发登录认证,具体的登录模块由构造方法name参数指定
2.LoginModule:登录模块,封装具体的登录认证逻辑,如果认证失败则抛出异常,成为则向Subject中添加一个Principal
3.CallbackHandler:回调处理器,用于搜集认证信息
4.Principal:代表程序用户的某一身份,与其密切相关的为Subject,用于代表程序用户,而一个用户可以多种身份,授权时可以针对某用户的多个身份分别授权


下面看一个认证例子:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.xtayfjpk.security.jaas.demo;  
  2.   
  3. import javax.security.auth.login.LoginContext;  
  4. import javax.security.auth.login.LoginException;  
  5.   
  6. public class App {  
  7.       
  8.     public static void main(String[] args) {  
  9.         System.setProperty("java.security.auth.login.config""demo.config");  
  10.         System.setProperty("java.security.policy""demo.policy");  
  11.         System.setSecurityManager(new SecurityManager());  
  12.           
  13.         try {  
  14.             //创建登录上下文  
  15.             LoginContext context = new LoginContext("demo"new DemoCallbackHander());  
  16.             //进行登录,登录不成功则系统退出  
  17.             context.login();  
  18.         } catch (LoginException le) {  
  19.             System.err.println("Cannot create LoginContext. " + le.getMessage());  
  20.             System.exit(-1);  
  21.         } catch (SecurityException se) {  
  22.             System.err.println("Cannot create LoginContext. " + se.getMessage());  
  23.             System.exit(-1);  
  24.         }  
  25.           
  26.           
  27.         //访问资源  
  28.         System.out.println(System.getProperty("java.home"));  
  29.     }  
  30. }  

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.xtayfjpk.security.jaas.demo;  
  2.   
  3. import java.io.IOException;  
  4. import java.security.Principal;  
  5. import java.util.Iterator;  
  6. import java.util.Map;  
  7.   
  8. import javax.security.auth.Subject;  
  9. import javax.security.auth.callback.Callback;  
  10. import javax.security.auth.callback.CallbackHandler;  
  11. import javax.security.auth.callback.NameCallback;  
  12. import javax.security.auth.callback.PasswordCallback;  
  13. import javax.security.auth.callback.UnsupportedCallbackException;  
  14. import javax.security.auth.login.FailedLoginException;  
  15. import javax.security.auth.login.LoginException;  
  16. import javax.security.auth.spi.LoginModule;  
  17.   
  18. public class DemoLoginModule implements LoginModule {  
  19.     private Subject subject;  
  20.     private CallbackHandler callbackHandler;  
  21.     private boolean success = false;  
  22.     private String user;  
  23.     private String password;  
  24.   
  25.     @Override  
  26.     public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {  
  27.         this.subject = subject;  
  28.         this.callbackHandler = callbackHandler;  
  29.     }  
  30.   
  31.     @Override  
  32.     public boolean login() throws LoginException {  
  33.         NameCallback nameCallback = new NameCallback("请输入用户名");  
  34.         PasswordCallback passwordCallback = new PasswordCallback("请输入密码"false);  
  35.         Callback[] callbacks = new Callback[]{nameCallback, passwordCallback};  
  36.         try {  
  37.             //执行回调,回调过程中获取用户名与密码  
  38.             callbackHandler.handle(callbacks);  
  39.             //得到用户名与密码  
  40.             user = nameCallback.getName();  
  41.             password = new String(passwordCallback.getPassword());  
  42.         } catch (IOException | UnsupportedCallbackException e) {  
  43.             success = false;  
  44.             throw new FailedLoginException("用户名或密码获取失败");  
  45.         }  
  46.         //为简单起见认证条件写死  
  47.         if(user.length()>3 && password.length()>3) {  
  48.             success = true;//认证成功  
  49.         }  
  50.         return true;  
  51.     }  
  52.   
  53.     @Override  
  54.     public boolean commit() throws LoginException {  
  55.         if(!success) {  
  56.             return false;  
  57.         } else {  
  58.             //如果认证成功则得subject中添加一个Principal对象  
  59.             //这样某身份用户就认证通过并登录了该应用,即表明了谁在执行该程序  
  60.             this.subject.getPrincipals().add(new DemoPrincipal(user));  
  61.             return true;  
  62.         }  
  63.     }  
  64.   
  65.     @Override  
  66.     public boolean abort() throws LoginException {  
  67.         logout();  
  68.         return true;  
  69.     }  
  70.   
  71.     @Override  
  72.     public boolean logout() throws LoginException {  
  73.         //退出时将相应的Principal对象从subject中移除  
  74.         Iterator<Principal> iter = subject.getPrincipals().iterator();  
  75.         while(iter.hasNext()) {  
  76.             Principal principal = iter.next();  
  77.             if(principal instanceof DemoPrincipal) {  
  78.                 if(principal.getName().equals(user)) {  
  79.                     iter.remove();  
  80.                     break;  
  81.                 }  
  82.             }  
  83.         }  
  84.         return true;  
  85.     }  
  86.   
  87. }  

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.xtayfjpk.security.jaas.demo;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import javax.security.auth.callback.Callback;  
  6. import javax.security.auth.callback.CallbackHandler;  
  7. import javax.security.auth.callback.NameCallback;  
  8. import javax.security.auth.callback.PasswordCallback;  
  9. import javax.security.auth.callback.UnsupportedCallbackException;  
  10.   
  11. public class DemoCallbackHander implements CallbackHandler {  
  12.   
  13.     @Override  
  14.     public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {  
  15.         NameCallback nameCallback = (NameCallback) callbacks[0];  
  16.         PasswordCallback passwordCallback = (PasswordCallback) callbacks[1];  
  17.         //设置用户名与密码  
  18.         nameCallback.setName(getUserFromSomeWhere());  
  19.         passwordCallback.setPassword(getPasswordFromSomeWhere().toCharArray());  
  20.     }  
  21.       
  22.   
  23.     //为简单起见用户名与密码写死直接返回,真实情况可以由用户输入等具体获取  
  24.     public String getUserFromSomeWhere() {  
  25.         return "zhangsan";  
  26.     }  
  27.     public String getPasswordFromSomeWhere() {  
  28.         return "zhangsan";  
  29.     }  
  30. }  

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.xtayfjpk.security.jaas.demo;  
  2.   
  3. import java.security.Principal;  
  4.   
  5. public class DemoPrincipal implements Principal {  
  6.     private String name;  
  7.       
  8.     public DemoPrincipal(String name) {  
  9.         this.name = name;  
  10.     }  
  11.       
  12.     @Override  
  13.     public String getName() {  
  14.         return this.name;  
  15.     }  
  16.   
  17. }  

使用认证服务时,需要绑定一个认证配置文件,在例子中通过System.setProperty("java.security.auth.login.config", "demo.config");实现,当然也可以设置虚拟属性-Djava.security.auth.login.config=demo.config实现。配置文件内容如下:

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. demo {  
  2.    com.xtayfjpk.security.jaas.demo.DemoLoginModule required debug=true;  
  3. };  

其中demo为配置名称,其内容指定了需要使用到哪登录模块(LoginModule),认证配置文件具体格式请参看:http://docs.oracle.com/javase/6/docs/technotes/guides/security/jgss/tutorials/LoginConfigFile.html

前面说到认证与授权密不可分,这里就可以说明,在创建LoginContext对象时就需要有createLoginContext.demo的认证权限,demo就是认证配置文件中的配置名称,该名称在构造LoginContext对象时指定。由于在DemoLoginModule中修改了Subject的principals集合,还需要有modifyPrincipals认证权限,所以授权策略文件内容变为:

[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. grant  {  
  2.     permission javax.security.auth.AuthPermission "createLoginContext.demo";  
  3.     permission javax.security.auth.AuthPermission "modifyPrincipals";  
  4.     permission java.util.PropertyPermission "java.home", "read";  
  5. };  

再次运行程序,java.home系统属性正常输出,但此时我们还是没有针对某特定用户身份进行授权,这个就需要在授权文件中配置Principal,现在将授权文件改写为:


[plain] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. grant principal com.xtayfjpk.security.jaas.demo.DemoPrincipal "zhangsan"{  
  2.     permission java.util.PropertyPermission "java.home", "read";  
  3. };  
  4. grant {  
  5.     permission javax.security.auth.AuthPermission "createLoginContext.demo";  
  6.     permission javax.security.auth.AuthPermission "modifyPrincipals";  
  7.     permission javax.security.auth.AuthPermission "doAsPrivileged";  
  8. };  

这就意味着只有以名为zhangsan的DemoPrincipal登录应用才会拥有java.home系统属性的读权限,此时读取java.home的代码需要做一定的修改,如下:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. Subject subject = context.getSubject();  
  2. //该方法调用需要"doAsPrivileged"权限  
  3. Subject.doAsPrivileged(subject, new PrivilegedAction<Object>() {  
  4.     @Override  
  5.     public Object run() {  
  6.         System.out.println(System.getProperty("java.home"));  
  7.         return null;  
  8.     }  
  9. }, null);  

因为在Subject中才有Principal信息,这样就可以针对每一种用户身份制定一套权限方案。
0 0
原创粉丝点击