Java安全之认证与授权

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

public class App {

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

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

public class App {

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

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

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

授权的方式是为安全管理器绑定一个授权策略文件。由于我是在eclipse Java工程中直接运行main方法,所以就在工程根目录下新建一个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文件的方式有两种:一、在运行程序的时候加入-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,用于代表程序用户,而一个用户可以多种身份,授权时可以针对某用户的多个身份分别授权

下面看一个认证例子:

package com.xtayfjpk.security.jaas.demo;

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

public class App {

	public static void main(String[] args) {
		System.setProperty("java.security.auth.login.config", "demo.config");
		System.setProperty("java.security.policy", "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.xtayfjpk.security.jaas.demo;

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;

	@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 {
		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;
	}

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

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

	@Override
	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.xtayfjpk.security.jaas.demo;

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.xtayfjpk.security.jaas.demo;

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实现。配置文件内容如下:

demo {
   com.xtayfjpk.security.jaas.demo.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认证权限,所以授权策略文件内容变为:

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

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

grant principal com.xtayfjpk.security.jaas.demo.DemoPrincipal "zhangsan"{
	permission java.util.PropertyPermission "java.home", "read";
};
grant {
	permission javax.security.auth.AuthPermission "createLoginContext.demo";
	permission javax.security.auth.AuthPermission "modifyPrincipals";
	permission javax.security.auth.AuthPermission "doAsPrivileged";
};

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

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

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

时间: 2024-11-09 09:22:14

Java安全之认证与授权的相关文章

Java Web系列:认证和授权基础

1.认证和授权概述 (1)认证:对用户的身份进行验证. .NET基于的RBS(参考1)的认证和授权相关的核心是2个接口System.Security.Principal.IPrincipal和System.Security.Principal.IIdentity.我们自己实现认证过程,通过Thread.CurrentPrincipal来设置和读取认证结果.认证成功后设置认证状态和标识. Java内置了的JAAS(参考2),核心是javax.security.auth.Subject类和javax

BOS项目 第7天(shiro权限框架进行认证和授权)

BOS项目笔记 第7天 今天内容安排: 1.权限概述(认证.授权) 2.常见的权限控制的方式(URL拦截权限控制.方法注解权限控制) 3.权限数据模型(权限表.角色表.用户表.角色权限关系表.用户角色关系表) 4.shiro框架入门 5.将shiro应用到bos项目中进行认证和授权 1. 权限概述 系统提供了很多功能,并不是所有的用户登录系统都可以操作这些功能.我们需要对用户的访问进行控制. 认证:系统提供的用于识别用户身份的功能(通常是登录功能)-----让系统知道你是谁?? 授权:系统提供的

Apache Shiro 认证、授权、加密和会话管理

官方解释 : Apache Shiro(日语"堡垒(Castle)"的意思)是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理功能,可为任何应用提供安全保障 - 从命令行应用.移动应用到大型网络及企业应用. Shiro为解决下列问题(我喜欢称它们为应用安全的四要素)提供了保护应用的API: 认证 - 用户身份识别,常被称为用户"登录": 授权 - 访问控制: 密码加密 - 保护或隐藏数据防止被偷窥: 会话管理 - 每用户相关的时间敏感的状态. Shi

【shiro】(4)---Shiro认证、授权案例讲解

Shiro认证.授权案例讲解 一.认证  1. 认证流程     2.用户密码已经加密.加盐的用户认证 (1)测试类 // 用户登陆和退出,这里我自定了一个realm(开发肯定需要自定义realm获取数据库密码和权限) @Test public void testCustomRealmMd5() { // 创建securityManager工厂,通过ini配置文件创建securityManager工厂 Factory<SecurityManager> factory = new IniSecu

SpringBoot日记——Spring的安全配置-登录认证与授权

安全是每个项目开发中都需要考虑的,比如权限控制,安全认证,防止漏洞攻击等. 比较常见的安全框架有:Apache的shiro.Spring Security等等,相信用shiro的用户群体更多,而security功能更多一些. 那么我们就来看看Spring自己的Security是如何使用的.关于shiro后边一定会有其他文章补充的~. 官方文档-入门链接 Spring Security环境搭建 1).首先我们要有一个可以用来做测试的页面,不然做了权限控制也不知道有没有效果,那么我下边简单的列一个登

微服务的用户认证与授权杂谈(下)

[TOC] AOP实现登录状态检查 在微服务的用户认证与授权杂谈(上)一文中简单介绍了微服务下常见的几种认证授权方案,并且使用JWT编写了一个极简demo来模拟Token的颁发及校验.而本文的目的主要是延续上文来补充几个要点,例如Token如何在多个微服务间进行传递,以及如何利用AOP实现登录态和权限的统一校验. 为了让登录态的检查逻辑能够通用,我们一般会选择使用过滤器.拦截器以及AOP等手段来实现这个功能.而本小节主要是介绍使用AOP实现登录状态检查,因为利用AOP同样可以拦截受保护的资源访问

Shiro 简介(认证、授权、加密、会话管理、与 Web 集成、缓存等)

https://www.w3cschool.cn/shiro/ Shiro 简介 简介 Apache Shiro 是 Java 的一个安全框架.目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了.对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了. 本教程只介绍基本的 Shiro 使用,不会

使用Shiro实现认证和授权(基于SpringBoot)

Apache Shiro是一个功能强大且易于使用的Java安全框架,它为开发人员提供了一种直观,全面的身份验证,授权,加密和会话管理解决方案.下面是在SpringBoot中使用Shiro进行认证和授权的例子,代码如下: pom.xml 导入SpringBoot和Shiro依赖: <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>s

springboot引入shiro实现拦截,权限,认证,授权功能

Shiro核心三大组件 1. Subject Subject表示与系统交互的对象,可以是登录系统的操作用户,也可能是另外一个软件系统. Subject类图 2. SecurityManager SecurityManager是Shiro架构最核心的组件.实际上,SecurityManager就是Shiro框架的控制器,协调其他组件一起完成认证和授权 3. Realms Realm定义了访问数据的方式,用来连接不同的数据源,如:LDAP,关系数据库,配置文件等等. Spingboot整合shiro