您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 质量控制/管理 > Java深度历险(九)Java安全
Java深度历险(九)——Java安全安全性是Java应用程序的非功能性需求的重要组成部分,如同其它的非功能性需求一样,安全性很容易被开发人员所忽略。当然,对于JavaEE的开发人员来说,安全性的话题可能没那么陌生,用户认证和授权可能是绝大部分Web应用都有的功能。类似SpringSecurity这样的框架,也使得开发变得更加简单。本文并不会讨论Web应用的安全性,而是介绍Java安全一些底层和基本的内容。认证用户认证是应用安全性的重要组成部分,其目的是确保应用的使用者具有合法的身份。Java安全中使用术语主体(Subject)来表示访问请求的来源。一个主体可以是任何的实体。一个主体可以有多个不同的身份标识(Principal)。比如一个应用的用户这类主体,就可以有用户名、身份证号码和手机号码等多种身份标识。除了身份标识之外,一个主体还可以有公开或是私有的安全相关的凭证(Credential),包括密码和密钥等。典型的用户认证过程是通过登录操作来完成的。在登录成功之后,一个主体中就具备了相应的身份标识。Java提供了一个可扩展的登录框架,使得应用开发人员可以很容易的定制和扩展与登录相关的逻辑。登录的过程由LoginContext启动。在创建LoginContext的时候需要指定一个登录配置(Configuration)的名称。该登录配置中包含了登录所需的多个LoginModule的信息。每个LoginModule实现了一种登录方式。当调用LoginContext的login方法的时候,所配置的每个LoginModule会被调用来执行登录操作。如果整个登录过程成功,则通过getSubject方法就可以获取到包含了身份标识信息的主体。开发人员可以实现自己的LoginModule来定制不同的登录逻辑。每个LoginModule的登录方式由两个阶段组成。第一个阶段是在login方法的实现中。这个阶段用来进行必要的身份认证,可能需要获取用户的输入,以及通过数据库、网络操作或其它方式来完成认证。当认证成功之后,把必要的信息保存起来。如果认证失败,则抛出相关的异常。第二阶段是在commit或abort方法中。由于一个登录过程可能涉及到多个LoginModule。LoginContext会根据每个LoginModule的认证结果以及相关的配置信息来确定本次登录是否成功。LoginContext用来判断的依据是每个LoginModule对整个登录过程的必要性,分成必需、必要、充分和可选这四种情况。如果登录成功,则每个LoginModule的commit方法会被调用,用来把身份标识关联到主体上。如果登录失败,则LoginModule的abort方法会被调用,用来清除之前保存的认证相关信息。在LoginModule进行认证的过程中,如果需要获取用户的输入,可以通过CallbackHandler和对应的Callback来完成。每个Callback可以用来进行必要的数据传递。典型的启动登录的过程如下:publicSubjectlogin()throwsLoginException{TextInputCallbackHandlercallbackHandler=newTextInputCallbackHandler();LoginContextlc=newLoginContext(SmsApp,callbackHandler);lc.login();returnlc.getSubject();}这里的SmsApp是登录配置的名称,可以在配置文件中找到。该配置文件的内容也很简单。SmsApp{security.login.SmsLoginModulerequired;};这里声明了使用security.login.SmsLoginModule这个登录模块,而且该模块是必需的。配置文件可以通过启动程序时的参数java.security.auth.login.config来指定,或修改JVM的默认设置。下面看看SmsLoginModule的核心方法login和commit。publicbooleanlogin()throwsLoginException{TextInputCallbackphoneInputCallback=newTextInputCallback(Phonenumber:);TextInputCallbacksmsInputCallback=newTextInputCallback(Code:);try{handler.handle(newCallback[]{phoneInputCallback,smsInputCallback});}catch(Exceptione){thrownewLoginException(e.getMessage());}Stringcode=smsInputCallback.getText();booleanisValid=code.length()3;//此处只是简单的进行验证。if(isValid){phoneNumber=phoneInputCallback.getText();}returnisValid;}publicbooleancommit()throwsLoginException{if(phoneNumber!=null){subject.getPrincipals().add(newPhonePrincipal(phoneNumber));returntrue;}returnfalse;}这里使用了两个TextInputCallback来获取用户的输入。当用户输入的编码有效的时候,就把相关的信息记录下来,此处是用户的手机号码。在commit方法中,就把该手机号码作为用户的身份标识与主体关联起来。权限控制在验证了访问请求来源的合法身份之后,另一项工作是验证其是否具有相应的权限。权限由Permission及其子类来表示。每个权限都有一个名称,该名称的含义与权限类型相关。某些权限有与之对应的动作列表。比较典型的是文件操作权限FilePermission,它的名称是文件的路径,而它的动作列表则包括读取、写入和执行等。Permission类中最重要的是implies方法,它定义了权限之间的包含关系,是进行验证的基础。权限控制包括管理和验证两个部分。管理指的是定义应用中的权限控制策略,而验证指的则是在运行时刻根据策略来判断某次请求是否合法。策略可以与主体关联,也可以没有关联。策略由Policy来表示,JDK提供了基于文件存储的基本实现。开发人员也可以提供自己的实现。在应用运行过程中,只可能有一个Policy处于生效的状态。验证部分的具体执行者是AccessController,其中的checkPermission方法用来验证给定的权限是否被允许。在应用中执行相关的访问请求之前,都需要调用checkPermission方法来进行验证。如果验证失败的话,该方法会抛出AccessControlException异常。JVM中内置提供了一些对访问关键部分内容的访问控制检查,不过只有在启动应用的时通过参数-Djava.security.manager启用了安全管理器之后才能生效,并与策略相配合。与访问控制相关的另外一个概念是特权动作。特权动作只关心动作本身所要求的权限是否具备,而并不关心调用者是谁。比如一个写入文件的特权动作,它只要求对该文件有写入权限即可,并不关心是谁要求它执行这样的动作。特权动作根据是否抛出受检异常,分为PrivilegedAction和PrivilegedExceptionAction。这两个接口都只有一个run方法用来执行相关的动作,也可以向调用者返回结果。通过AccessController的doPrivileged方法就可以执行特权动作。Java安全使用了保护域的概念。每个保护域都包含一组类、身份标识和权限,其意义是在当访问请求的来源是这些身份标识的时候,这些类的实例就自动具有给定的这些权限。保护域的权限既可以是固定,也可以根据策略来动态变化。ProtectionDomain类用来表示保护域,它的两个构造方法分别用来支持静态和动态的权限。一般来说,应用程序通常会涉及到系统保护域和应用保护域。不少的方法调用可能会跨越多个保护域的边界。因此,在AccessController进行访问控制验证的时候,需要考虑当前操作的调用上下文,主要指的是方法调用栈上不同方法所属于的不同保护域。这个调用上下文一般是与当前线程绑定在一起的。通过AccessController的getContext方法可以获取到表示调用上下文的AccessControlContext对象,相当于访问控制验证所需的调用栈的一个快照。在有些情况下,会需要传递此对象以方便在其它线程中进行访问控制验证。考虑下面的权限验证代码:Subjectsubject=newSubject();ViewerPrincipalprincipal=newViewerPrincipal(Alex);subject.getPrincipals().add(principal);Subject.doAsPrivileged(subject,newPrivilegedActionObject(){publicObjectrun(){newViewer().view();returnnull;}},null);这里创建了一个新的Subject对象并关联上身份标识。通常来说,这个过程是由登录操作来完成的。通过Subject的doAsPrivileged方法就可以执行一个特权动作。Viewer对象的view方法会使用AccessController来检查是否具有相应的权限。策略配置文件的内容也比较简单,在启动程序的时候通过参数java.security.auth.policy指定文件路径即可。grantPrincipalsecurity.access.ViewerPrincipalAlex{permissionsecurity.access.ViewPermissionCONFIDENTIAL;};//这里把名称为CONFIDENTIAL的ViewPermission授权给了身份标识为Alex的主体。加密、解密与签名构建安全的Java应用离不开加密和解密。Java的密码框架采用了常见的服务提供者架构,以提供所需的可扩展性和互操作性。该密码框架提供了一系列常用的服务,包括加密、数字签名和报文摘要等。这些服务都有服务提供者接口(SPI),服务的实现者只需要实现这些接口,并注册到密码框架中即可。比如加密服务Cipher的SPI接口就是CipherSpi。每个服务都可以有不同的算法来实现。密码框架也提供了相应的工厂方法用来获取到服务的实例。比如想使用采用MD5算法的报文摘要服务,只需要调用MessageDigest.getInstance(MD5)即可。加密和解密过程中并不可少的就是密钥(Key)。加密算法一般分成对称和非对称两种。对称加密算法使用同一个密钥进行加密和解密;而非对称加密算法使用一对公钥和私钥,一个加密的时候,另外一个就用来解密。不同的加密算法,有不同的密钥。对称加密算法使用的是SecretKey,而非对称加密算法则使用PublicKey和PrivateKey。与密钥Key对应的另一个接口是KeySpec,用来描述不同算法的密钥的具体内容。比如一个典型的使用对称加密的方式如下:KeyGeneratorgenerator=KeyGenerator.getInstance(DES);SecretKeykey=generator.generateKey();saveFile(key.data,key.getEncoded());Ciphercipher=Cipher.getInstance(DES);cipher.init(Cipher.ENCRYPT_MODE,key);Stringtext=HelloWorld;byte[]encrypt
本文标题:Java深度历险(九)Java安全
链接地址:https://www.777doc.com/doc-2881158 .html