java的Jaas授权与鉴权

1.概述

在这里插入图片描述

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

Java Authentication Authorization
Service(JAAS,Java验证和授权API)提供了灵活和可伸缩的机制来保证客户端或服务器端的Java程序。Java早期的安全框架强调的是
通过验证代码的来源和作者,保护用户避免受到下载下来的代码的攻击。JAAS强调的是通过验证谁在运行代码以及他/她的权限来保护系统面受用户的攻击。它
让你能够将一些标准的安全机制,例如Solaris NIS(网络信息服务)、Windows
NT、LDAP(轻量目录存取协议),Kerberos等通过一种通用的,可配置的方式集成到系统中。

2. 概念预览

  1. LoginContextjavax.security.auth.login包里的一个类,它描述了用于验证对象(subjects)的方法。
  2. Subject就是在某个你想去认证和分配访问权限的系统里的一个标识。一个主体(subject)可能是一个用户、一个进程或者是一台机器,它用javax.security.auth.Subject类表示。由于一个Subject可能涉及多个授权(一个网上银行密码和另一个电子邮件系统),
  3. java.security.Principal就被用作在那些关联里的标识。也就是说,该Principal接口是一个能够被用作代表某个实体、公司或者登陆ID的抽象概念。一个Subject可能包含多个Principles.
  4. Principal :当一个Subject认证成功,Principal将会关联到这个SubjectPrincipal表示Subject的身份表示,必须要实现 java.security.Principal 和 java.io.Serializable 接口。Subject section 描述了更新Principal关联到subject的方法。
  5. Credential :并不是主要的Jaas代码,任何类可以表示为Credential,并需要实现Credential的两个接口Refreshable和Destroyable。

3.SecurityManager应用场景

当运行未知的Java程序的时候,该程序可能有恶意代码(删除系统文件、重启系统等),为了防止运行恶意代码对系统产生影响,需要对运行的代码的权限进行控制,这时候就要启用Java安全管理器。

2. 测试

新建一个项目JavaJaasTest,路径为:/Users/lcc/IdeaProjects/JavaJaasTest

2.1 无安全测试

新建测试类 com.security.App1

public class App1 {  

    public static void main(String[] args) {  
        System.out.println(System.getProperty("java.home"));  
    }  
} 

运行结果

/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre

2.2 安全测试

新建测试类 com.security.App2

public class App2 {

    public static void main(String[] args) {
        //安装安全管理器
        System.setSecurityManager(new SecurityManager());

        System.out.println(System.getProperty("java.home"));
    }

}

运行结果

Exception in thread "main" java.security.AccessControlException: access denied ("java.util.PropertyPermission" "java.home" "read")
	at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
	at java.security.AccessController.checkPermission(AccessController.java:884)
	at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
	at java.lang.SecurityManager.checkPropertyAccess(SecurityManager.java:1294)
	at java.lang.System.getProperty(System.java:717)
	at com.security.App2.main(App2.java:15)

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

2.1 绑定授权策略文件

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

grant  {
    permission java.util.PropertyPermission "java.home", "read";
};

该授权的效果是任何用户运行的任何程序都有对java.home的读权限,policy文件的具体格式请参看:

http://docs.oracle.com/javase/7/docs/technotes/guides/security/PolicyFiles.html

为安全管理器绑定policy文件的方式有两种:

  1. 在运行程序的时候加入-Djava.security.policy=demo.policy虚拟机启动参数;
  2. 执行System.setProperty(“java.security.policy”, “demo.policy”);其实两者的效果一样,都是在设置系统属性,其中demo.policy是路径,这里为了简单指定的是相对路径,绝对路径当然也没问题。再次运行程序不再抛出异常,说明程序拥有了对java.home系统属性的读取权限。
public class App3 {

    public static void main(String[] args) {

        System.setProperty("java.security.policy", "demo.policy");
        System.setProperty("java.security.policy", "/Users/lcc/IdeaProjects/JavaJaasTest/src/main/resources/demo.policy");

        //安装安全管理器
        System.setSecurityManager(new SecurityManager());

        System.out.println(System.getProperty("java.home"));
    }

}

这两种方法都是可以得到正确结果的。

3. 其他java权限

在Java中权限有很多,具体可参考

http://docs.oracle.com/javase/7/docs/technotes/guides/security/spec/security-spec.doc3.html#17001

java.security.AllPermission

所有权限的集合

java.util.PropertyPermission

系统/环境属性权限

java.lang.RuntimePermission

运行时权限

java.net.SocketPermission

Socket权限

java.io.FilePermission

文件权限,包括读写,删除,执行

java.io.SerializablePermission

序列化权限

java.lang.reflect.ReflectPermission

反射权限

java.security.UnresolvedPermission

未解析的权限

java.net.NetPermission

网络权限

java.awt.AWTPermission

AWT权限

java.sql.SQLPermission

数据库sql权限

java.security.SecurityPermission

安全控制方面的权限

java.util.logging.LoggingPermission

日志控制权限

javax.net.ssl.SSLPermission

安全连接权限

javax.security.auth.AuthPermission

认证权限

javax.sound.sampled.AudioPermission

音频系统资源的访问权限

4.优化

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

认证服务有点“麻烦”,一般情况下主要都涉及到了LoginContext,LoginModule,CallbackHandler,Principal,后三者还需要开发者自己实现。这里先解释一下这几个类的作用:

  1. LoginContext:认证核心类,也是入口类,用于触发登录认证,具体的登录模块由构造方法name参数指定
  2. LoginModule:登录模块,封装具体的登录认证逻辑,如果认证失败则抛出异常,成为则向Subject中添加一个Principal
  3. CallbackHandler:回调处理器,用于搜集认证信息
  4. Principal:代表程序用户的某一身份,与其密切相关的为Subject,用于代表程序用户,而一个用户可以多种身份,授权时可以针对某用户的多个身份分别授权

下面看一个认证例子:

package com.security;

import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

/**
 * @Author: chuanchuan.lcc
 * @CreateDate: 2018/12/28 PM4:30
 * @Version: 1.0
 * @Description: java类作用描述:
 */
public class App4 {
    public static void main(String[] args) {
        System.setProperty("java.security.auth.login.config", "/Users/lcc/IdeaProjects/JavaJaasTest/src/main/resources/demo.config");
        System.setProperty("java.security.policy", "/Users/lcc/IdeaProjects/JavaJaasTest/src/main/resources/demo.policy");
        System.setSecurityManager(new SecurityManager());

        try {
            //创建登录上下文
            LoginContext context = new LoginContext("demo", new DemoCallbackHander());
            //进行登录,登录不成功则系统退出
            context.login();
        } catch (LoginException le) {
            System.err.println("Cannot create LoginContext. " + le.getMessage());
            System.exit(-1);
        } catch (SecurityException se) {
            System.err.println("Cannot create LoginContext. " + se.getMessage());
            System.exit(-1);
        }


        //访问资源
        System.out.println(System.getProperty("java.home"));
    }
}
package com.security;

import java.io.IOException;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;

public class DemoCallbackHander implements CallbackHandler {

    @Override
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        NameCallback nameCallback = (NameCallback) callbacks[0];
        PasswordCallback passwordCallback = (PasswordCallback) callbacks[1];
        //设置用户名与密码
        nameCallback.setName(getUserFromSomeWhere());
        passwordCallback.setPassword(getPasswordFromSomeWhere().toCharArray());
    }


    //为简单起见用户名与密码写死直接返回,真实情况可以由用户输入等具体获取
    public String getUserFromSomeWhere() {
        return "zhangsan";
    }
    public String getPasswordFromSomeWhere() {
        return "zhangsan";
    }
}

package com.security;

import java.io.IOException;
import java.security.Principal;
import java.util.Iterator;
import java.util.Map;

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.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

public class DemoLoginModule implements LoginModule {

    private Subject subject;
    private CallbackHandler callbackHandler;
    private boolean success = false;
    private String user;
    private String password;


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

    public boolean login() throws LoginException {
        NameCallback nameCallback = new NameCallback("请输入用户名");
        PasswordCallback passwordCallback = new PasswordCallback("请输入密码", false);
        Callback[] callbacks = new Callback[]{nameCallback, passwordCallback};
        try {
            //执行回调,回调过程中获取用户名与密码
            callbackHandler.handle(callbacks);
            //得到用户名与密码
            user = nameCallback.getName();
            password = new String(passwordCallback.getPassword());
        } catch (IOException | UnsupportedCallbackException e) {
            success = false;
            throw new FailedLoginException("用户名或密码获取失败");
        }
        //为简单起见认证条件写死
        if(user.length()>3 && password.length()>3) {
            success = true;//认证成功
        }
        return true;
    }

    public boolean commit() throws LoginException {
        if(!success) {
            return false;
        } else {
            //如果认证成功则得subject中添加一个Principal对象
            //这样某身份用户就认证通过并登录了该应用,即表明了谁在执行该程序
            this.subject.getPrincipals().add(new DemoPrincipal(user));
            return true;
        }
    }


    public boolean abort() throws LoginException {
        logout();
        return true;
    }


    public boolean logout() throws LoginException {
        //退出时将相应的Principal对象从subject中移除
        Iterator<Principal> iter = subject.getPrincipals().iterator();
        while(iter.hasNext()) {
            Principal principal = iter.next();
            if(principal instanceof DemoPrincipal) {
                if(principal.getName().equals(user)) {
                    iter.remove();
                    break;
                }
            }
        }
        return true;
    }

}
package com.security;

import java.security.Principal;

public class DemoPrincipal implements Principal {
    private String name;

    public DemoPrincipal(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }

}

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

/Users/lcc/IdeaProjects/JavaJaasTest/src/main/resources/demo.config

demo {
   com.security.DemoLoginModule required debug=true;
};

其中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认证权限,所以授权策略文件内容变为:

/Users/lcc/IdeaProjects/JavaJaasTest/src/main/resources/demo.policy

grant  {
    permission javax.security.auth.AuthPermission "createLoginContext.demo";
    permission javax.security.auth.AuthPermission "modifyPrincipals";
    permission java.util.PropertyPermission "java.home", "read";
};

再次运行程序,java.home系统属性正常输出

5.再次优化

主类修改

package com.security;

import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import java.security.Principal;
import java.security.PrivilegedAction;

/**
 * @Author: chuanchuan.lcc
 * @CreateDate: 2018/12/28 PM4:47
 * @Version: 1.0
 * @Description: java类作用描述:
 */
public class App5 {

    public static void main(String[] args) {
        System.setProperty("java.security.auth.login.config", "/Users/lcc/IdeaProjects/JavaJaasTest/src/main/resources/demo5.config");
        System.setProperty("java.security.policy", "/Users/lcc/IdeaProjects/JavaJaasTest/src/main/resources/demo5.policy");


        System.setSecurityManager(new SecurityManager());
        LoginContext context = null;
        try {
            //创建登录上下文
            context = new LoginContext("Demo", new DemoCallbackHander());
            //进行登录,登录不成功则系统退出
            context.login();
        } catch (LoginException le) {
            System.err.println("Cannot create LoginContext. " + le.getMessage());
            System.exit(-1);
        } catch (SecurityException se) {
            System.err.println("Cannot create LoginContext. " + se.getMessage());
            System.exit(-1);
        }
        
        //访问资源
        Subject subject = context.getSubject();

        System.out.println(subject);

        for (Principal principal : subject.getPrincipals()) {
            System.out.println("\t" + principal.toString());
        }


        DemoPrivilegedAction demoPrivilegedAction = new DemoPrivilegedAction();
        //该方法调用需要"doAsPrivileged"权限
        Subject.doAsPrivileged(subject, new PrivilegedAction<Object>() {
            @Override
            public Object run() {
                System.out.println(System.getProperty("java.home"));
                return null;
            }
        }, null);
    }
}

在运行这段代码时,后台进行了以下的工作。

  1. 当初始化时,LoginContext对象首先在JAAS配置文件中找到myperson
    项,然后更具该项的内容决定该加载哪个LoginModule对象。
  2. 在登录时,LoginContext对象调用每个LoginModule对象的login()
    法。
  3. 每个login()方法进行验证操作或获得一个CallbackHandle对象。
  4. CallbackHandle对象通过使用一个或多个CallBack方法同用户进行交互
    ,获得用户输入。
  5. 向一个新的Subject对象中填入验证信息

配置

# 路径 /Users/lcc/IdeaProjects/JavaJaasTest/src/main/resources/demo5.config

Demo {
   com.security.DemoLoginModule required debug=true;
};

配置

# 路径 /Users/lcc/IdeaProjects/JavaJaasTest/src/main/resources/demo5.policy


grant  {
    permission javax.security.auth.AuthPermission "createLoginContext.Demo";
    permission javax.security.auth.AuthPermission "modifyPrincipals";
    permission javax.security.auth.AuthPermission "doAsPrivileged";
};
// JAAS 授权,授权给指定的用户或身份
grant Principal com.security.DemoPrincipal "zhangsan" {
   permission java.util.PropertyPermission "java.home", "read";
};


报错

Exception in thread "main" java.security.AccessControlException: access denied ("java.util.PropertyPermission" "java.home" "read")
	at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
	at java.security.AccessController.checkPermission(AccessController.java:884)
	at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
	at java.lang.SecurityManager.checkPropertyAccess(SecurityManager.java:1294)
	at java.lang.System.getProperty(System.java:717)
	at com.security.App5$1.run(App5.java:46)
	at java.security.AccessController.doPrivileged(Native Method)
	at javax.security.auth.Subject.doAsPrivileged(Subject.java:483)
	at com.security.App5.main(App5.java:43)

这个原因是因为com.security.DemoPrincipal需要覆写·equalshashCode方法,修改类

package com.security;

import java.security.Principal;

public class DemoPrincipal implements Principal {
    private String name;

    public DemoPrincipal(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return this.name;
    }

    public boolean equals(Object o) {
        if (o == null)
            return false;

        if (this == o)
            return true;

        if (!(o instanceof DemoPrincipal))
            return false;
        DemoPrincipal that = (DemoPrincipal) o;

        if (this.getName().equals(that.getName()))
            return true;
        return false;
    }

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

    @Override
    public String toString() {
        return "DemoPrincipal{" +
                "name='" + name + '\'' +
                '}';
    }
}

然后运行成功

主体: 
	主用户: DemoPrincipal{name='zhangsan'}

	DemoPrincipal{name='zhangsan'}
/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre

错误集锦

1.1

Cannot create LoginContext. access denied ("javax.security.auth.AuthPermission" "createLoginContext.myperson")

1.2

javax.security.auth.login.LoginException: 没有为CountFiles配置 LoginModules

参考:
https://blog.csdn.net/xiaolangfanhua/article/details/52835920
https://www.ibm.com/developerworks/cn/java/j-javaauth/
https://www.programcreek.com/java-api-examples/index.php?api=java.security.PrivilegedExceptionAction

发布了1057 篇原创文章 · 获赞 439 · 访问量 130万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 酷酷鲨 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览