java 安全 认证和授权,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";

};

一、在运行程序的时候加入-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,后三者还需要开发者自己实现。这里先解释一下这几个类的作用:

LoginContext:认证核心类,也是入口类,用于触发登录认证,具体的登录模块由构造方法name参数指定

LoginModule:登录模块,封装具体的登录认证逻辑,如果认证失败则抛出异常,成为则向Subject中添加一个Principal

CallbackHandler:回调处理器,用于搜集认证信息

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

前面说到认证与授权密不可分,这里就可以说明,在创建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() {

@Override

public Object run() {

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

return null;

}

}, null);

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

------------------ END ---------------------

及时获取更多精彩文章,请扫码关注如下公众号《Java精讲》:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值