草庐IT

Java 认证与授权(JAAS)介绍

且行且码 2023-04-17 原文

JAAS(Java Authentication Authorization Service),即 Java 认证与授权,使用可插拔方式将认证与授权服务和应用程序分离开,提供了灵活和可伸缩的机制来保证客户端或服务器端的 Java 程序;本文主要介绍 JAAS 的基本概念及使用方法。

1、简介

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

Java 安全框架最初集中在保护用户运行潜在的不可信任代码,是基于代码的来源(URL)和谁创建的代码(certificate)来给移动代码进行授权。Java 2 SDK 1.3 引入了JAAS( Java Authentication and Authorization Service),增加了基于用户的访问控制能力,即根据谁在运行代码来进行授权。JAAS 已经整合进了Java 2 SDK 1.4,作为标准的用户认证与授权模型。

JAAS 认证被实现为可插拔的方式,允许应用程序同底层的具体认证技术保持独立,新增或者更新认证方法并不需要更改应用程序本身。应用程序通过实例化 LoginContext 对象开始认证过程,引用配置文件中的具体认证方法,即 LoginModule 对象,来执行认证。

2、核心类及接口

1.1、通用

1.1.1、Subject

认证的主体,可以代表一个人,一个角色一个进程等。认证成功之后可以从 LoginContext 获取 Subject,代表一种身份,用户可以通过这个身份执行一些需要权限才能运行的逻辑。

1.1.2、Principals

可认为是一种权限。一个 Subject 可以包含多个 Principal。用户认证成功之后,把授予的 Principal 加入到和该用户关联的 Subject 中,该用户便具有了这个 Principal 的权限。

1.1.3、Credentials

凭证,包括公共凭证(Public credentials,如:名称或公共密钥)和私有凭证(Private credentials,如:口令或私有密钥)。凭证并不是一个特定的类或借口,它可以是任何对象。凭证中可以包含任何特定安全系统需要的验证信息,例如标签(ticket),密钥或口令。

1.2、Authentication

1.2.1、LoginContext

LoginContext 提供了对 Subject 进行身份验证的基本方法,并提供了一种不依赖于底层身份验证技术的应用程序开发方法。LoginContext 查询配置以确定为应用程序配置的身份验证服务或LoginModule。因此,可以在应用程序下插入不同的 Loginmodule,而不需要对应用程序本身进行任何修改。

1.2.2、LoginModule

登录模块,不同的 LoginModule 对应了不同的认证方式。例如:Krb5LoginModule 使用 Kerberos 作为认证方式,FileLoginModule 使用外部保存的密码文件来认证,UsernameLoginModule 通过用户名密码来认证。

1.2.3、CallbackHandler

在某些情况下,LoginModule 需要与用户通信以获取身份验证信息;LoginModule 使用 CallbackHandler 来实现此目标。应用程序实现 CallbackHandler 接口并将其传递给 LoginContext, LoginContext 将其直接转发给底层 LoginModule。LoginModule 使用 CallbackHandler 来收集来自用户的输入(例如密码或智能卡 pin 号)或向用户提供信息(例如状态信息)。应用重新通过指定 CallbackHandler 来实现与用户交互的各种不同方式。例如,GUI 应用程序CallbackHandler 的实现可能会显示一个窗口来征求用户的输入。另一方面,非 GUI 工具的 CallbackHandler 的实现可能只是简单地提示用户直接从命令行输入。

1.2.4、Callback

 javax.security.auth.callback 包中包含了 Callback 接口及几个实现类。LoginModules 将一个 Callback 数组传递给 CallbackHandler 的 handle 方法。

1.3、Authentication

1.3.1、Policy

policy 类是一个抽象类,用于表示系统范围的访问控制策略。policy 有对应的 policy 文件来配置访问权限。

1.3.2、AuthPermission

AuthPermission 类封装了 JAAS 所需的基本权限;AuthPermission包含一个名称,但不包含操作列表。

1.3.2、PrivateCredentialPermission

PrivateCredentialPermission 类用于保护对 Subject 私有凭证的访问。

3、配置文件

3.1、JAAS Login Configuration File

JAAS 登录配置文件,主要用于用户的认证;一个配置文件中可以包含多个 entry,一个 entry 的格式如下:

<name used by application to refer to this entry> { 
  <LoginModule> <flag> <LoginModule options>;
  <optional additional LoginModules, flags and options>;
};

一个 entry 的配置主要包括 LoginModule、flag、LoginModule options,一个配置例子如下:

MyLogin {
    com.abc.demo.general.jaas.MyLoginModule required;
};

A、LoginModule

LoginModule 的类型决定了使用何种方式认证,需要配置为全限定名。一个 entry 可以配置多个 LoginModule,认证流程会按照顺序依次执行各个 LoginModule,flag 的值会影响认证流程的走向。

B、flag

flag 有如下取值:

  • Required:此 LoginModule 必须成功;无论成功与否,认证流程都会继续走后面的 LoginModule。
  • Requisite:此 LoginModule 必须成功;如果成功,认证流程会继续走后面的 LoginModule,如果失败,认证流程终止。
  • Sufficient:此 LoginModule 不必成功;如果成功,认证流程终止,如果失败,认证流程会继续走后面的 LoginModule。
  • Optional:此 LoginModule 不必成功;无论成功与否,认证流程都会继续走后面的 LoginModule。

整个认证流程是否成功的判定标准:
1、只有当所有的 Required 和 Requisite LoginModule 成功时,整个认证流程才成功。
2、如果配置了 Sufficient LoginModule 并且认证成功,在它之前所有的 Required 和 Requisite LoginModule 都成功,整个认证流程才算成功。
3、如果没有配置任何 Required 和 Requisite LoginModule,Sufficient 或 Optional LoginModule 至少成功一个,整个认证流程才算成功。

C、options
options 为 LoginModule 接收的认证选项。格式为 0 或多个 key=value,常用于传递认证附属参数。在 LoginModule 中使用一个 Map 来接收并处理这些参数。
 
AAS Login Configuration File 的详细说明可参考官网文档:https://docs.oracle.com/javase/8/docs/technotes/guides/security/jaas/tutorials/LoginConfigFile.html。

3.2、Policy File

policy 文件主要用来配置权限,其格式为:

grant signedBy "signer_names", codeBase "URL",
      principal principal_class_name "principal_name",
      principal principal_class_name "principal_name",
      ... {

    permission permission_class_name "target_name", "action", 
        signedBy "signer_names";
    permission permission_class_name "target_name", "action", 
        signedBy "signer_names";
    ...
};

A、signedBy

针对某个签名者赋予权限。可使用 jarsigner xxx.jar signer_name 为 jar 文件签名。

B、codeBase

用于为某个目录下的用户代码授权。

C、principal

用于为特定的 Principal 授权。

D、permission

permission 部分为赋予的具体权限;permission_class_name 表示权限名称,target_name 表示权限作用的目标,action 表示权限;如:

//读取d盘各级目录所有文件的权限
permission java.io.FilePermission "d:/-", "read";

//读取/设置环境变量的权限
permission java.util.PropertyPermission "os.name", "read";
permission java.util.PropertyPermission "java.security.auth.login.config", "write";

JAAS 支持很多种类型的权限,它们都继承至 java.security.Permission 类,可查看它的子类来确定具体的权限;常用的权限如下:

  • AuthPermission:认证操作权限
  • FilePermission:文件访问权限
  • PropertyPermission:属性访问权限
  • AllPermission:所有权限
  • SocketPermission:网络通信权限
policy 文件的详细说明可参考官网文档:https://docs.oracle.com/javase/8/docs/technotes/guides/security/PolicyFiles.html。

4、实际使用

简单演示下 JAAS 的功能,相关 Java 类文件及配置文件如下:

4.1、定义自己的 Principal

package com.abc.demo.general.jaas;

import java.security.Principal;
import java.util.Objects;

public class MyPrincipal implements Principal {
    private String name;

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

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

    @Override
    public int hashCode() {
        return Objects.hashCode(name);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || this.getClass() != obj.getClass()) {
            return false;
        }
        MyPrincipal that = (MyPrincipal) obj;
        return Objects.equals(name, that.getName());
    }

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

4.2、定义自己的 CallbackHandler

package com.abc.demo.general.jaas;

import javax.security.auth.callback.*;
import java.io.IOException;

public class MyCallbackHandler implements CallbackHandler {
    private String username;
    private String password;

    public MyCallbackHandler(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @Override
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        for (Callback callback : callbacks) {
            if (callback instanceof NameCallback) {
                NameCallback nameCallback = (NameCallback) callback;
                nameCallback.setName(username);
            } else if (callback instanceof PasswordCallback) {
                PasswordCallback passwordCallback = (PasswordCallback) callback;
                passwordCallback.setPassword(password.toCharArray());
            } else {
                throw(new UnsupportedCallbackException(callback, "Callback class not supported"));
            }
        }
    }
}

4.3、定义自己的 LoginModule

package com.abc.demo.general.jaas;

import javax.security.auth.Subject;
import javax.security.auth.callback.*;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import java.io.IOException;
import java.security.Principal;
import java.util.Map;

public class MyLoginModule implements LoginModule {
    private Subject subject;
    private CallbackHandler callbackHandler;

    private Principal principal;

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

    /**
     * 登录
     */
    @Override
    public boolean login() throws LoginException {
        try {
            NameCallback nameCallback = new NameCallback("user: ");
            PasswordCallback passwordCallback = new PasswordCallback("password: ", true);
            Callback[] callbacks = new Callback[] { nameCallback, passwordCallback};
            callbackHandler.handle(callbacks);
            String username = nameCallback.getName();
            String password = new String(passwordCallback.getPassword());

            return login(username, password);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (UnsupportedCallbackException e) {
            e.printStackTrace();
        }
        return false;
    }

    private boolean login(String username, String password) {
        System.out.println("username=" + username + ",password=" + password);
        //实际使用中应查数据库验证用户名密码
        if ("abc".equals(username) && "123456".equals(password)) {
            principal = new MyPrincipal(username);
            subject.getPrincipals().add(principal);
            return true;
        }
        return false;
    }

    /**
     * 登录成功后的操作
     */
    @Override
    public boolean commit() throws LoginException {
        return true;
    }

    //中断登录
    @Override
    public boolean abort() throws LoginException {
        return logout();
    }

    @Override
    public boolean logout() throws LoginException {
        if (subject != null && principal != null) {
            subject.getPrincipals().remove(principal);
        }
        return true;
    }
}

4.4、编写认证配置文件(jaas.conf)

MyLogin {
    com.abc.demo.general.jaas.MyLoginModule required;
};

4.5、认证测试

 1 package com.abc.demo.general.jaas;
 2 
 3 import javax.security.auth.Subject;
 4 import javax.security.auth.login.LoginContext;
 5 import javax.security.auth.login.LoginException;
 6 import java.security.PrivilegedAction;
 7 
 8 public class JaasTest {
 9     public static void main(String[] args) throws LoginException {
10         String jaasConf = JaasTest.class.getResource("jaas.conf").getFile();
11         System.setProperty("java.security.auth.login.config", jaasConf);
12 
13         MyCallbackHandler myCallbackHandler = new MyCallbackHandler("abc", "123456");
14         //使用 MyLogin 这个 entry 进行认证
15         LoginContext loginContext = new LoginContext("MyLogin", myCallbackHandler);
16         loginContext.login();
17 
18         Subject subject = loginContext.getSubject();
19         Subject.doAsPrivileged(subject, new PrivilegedAction<Object>() {
20                 @Override
21                 public Object run() {
22                     System.setProperty("demo", "demo value");
23                     System.out.println(System.getProperty("demo"));
24                     return null;
25                 }
26             }, null
27         );
28         loginContext.logout();
29     }
30 }

4.6、授权测试

4.6.1、开启 SecurityManager

默认 Java 没有开启 SecurityManager,程序拥有所有的权限。开启 SecurityManager 的方法是添加 VM 参数:

-Djava.security.manager

开启 SecurityManager 后,访问系统环境变量,访问文件等操作都会被阻止;这是因为安全管理器默认情况下不允许这些操作,我们需要配置 policy 文件,定义需要的权限。

4.6.2、编写 policy 文件(my.policy)

//为任何主体赋权
grant {
    //读取d盘各级目录所有文件的权限
    permission java.io.FilePermission "d:/-", "read";

    //读取/设置环境变量的权限
    permission java.util.PropertyPermission "os.name", "read";
    permission java.util.PropertyPermission "java.security.auth.login.config", "write";

    //加载动态库的权限
    permission java.lang.RuntimePermission "loadLibrary.*";

    //创建LoginContext的权限
    permission javax.security.auth.AuthPermission "createLoginContext";
    //login的时候需要将新建的principal加入subject
    permission javax.security.auth.AuthPermission "modifyPrincipals";
    //执行特权代码的权限
    permission javax.security.auth.AuthPermission "doAsPrivileged";
};

//为name为abc的com.abc.demo.general.jaas.MyPrincipal赋权
grant principal com.abc.demo.general.jaas.MyPrincipal "abc" {
    //系统变量demo的读写权限
    permission java.util.PropertyPermission "demo", "read, write";
};

同样使用 VM 参数方式指定 policy 文件:

-Djava.security.policy=D:\xx\xx\my.policy

4.6.3、结合认证与授权

认证成功后可以获得 Subject,可以指定以该 Subject 的身份来执行相应的代码,通过 Subject.doAsPrivileged() 设置:

Subject.doAsPrivileged(subject, new PrivilegedAction<Object>() {
        @Override
        public Object run() {
            System.setProperty("demo", "demo value");
            System.out.println(System.getProperty("demo"));
            return null;
        }
    }, null
);

应用程序会获取 policy 中配置的该 subject 的所有权限,然后判断是否能执行这些代码;如果没有对应的权限就会报错。

添加如下的 VM 参数,重新运行 4.5 的测试程序:

-Djava.security.manager -Djava.security.policy=D:\workspaceidea\demo\demo\src\main\java\com\abc\demo\general\jaas\my.policy

程序可以正常运行,如果修改 22 行代码中系统属性的名称:

System.setProperty("demo2", "demo value");

则程序就会报错。

 

 

参考:
https://docs.oracle.com/javase/8/docs/technotes/guides/security/jaas/JAASRefGuide.html
https://www.jianshu.com/p/4585ce68b2ab

有关Java 认证与授权(JAAS)介绍的更多相关文章

  1. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  2. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  3. java - 我的模型类或其他类中应该有逻辑吗 - 2

    我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

  4. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  5. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  6. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  7. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

    HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

  8. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  9. java - 为什么 ruby​​ modulo 与 java/other lang 不同? - 2

    我基本上来自Java背景并且努力理解Ruby中的模运算。(5%3)(-5%3)(5%-3)(-5%-3)Java中的上述操作产生,2个-22个-2但在Ruby中,相同的表达式会产生21个-1-2.Ruby在逻辑上有多擅长这个?模块操作在Ruby中是如何实现的?如果将同一个操作定义为一个web服务,两个服务如何匹配逻辑。 最佳答案 在Java中,模运算的结果与被除数的符号相同。在Ruby中,它与除数的符号相同。remainder()在Ruby中与被除数的符号相同。您可能还想引用modulooperation.

  10. java - Ruby 相当于 Java 的 Collections.unmodifiableList 和 Collections.unmodifiableMap - 2

    Java的Collections.unmodifiableList和Collections.unmodifiableMap在Ruby标准API中是否有等价物? 最佳答案 使用freeze应用程序接口(interface):Preventsfurthermodificationstoobj.ARuntimeErrorwillberaisedifmodificationisattempted.Thereisnowaytounfreezeafrozenobject.SeealsoObject#frozen?.Thismethodretur

随机推荐