你现在看的是http://sunose.blogspot.com
测试环境windowsxp 中文版本sp1 及Redhat 7.1 Jboss 3.2.2 with Tomcat bundle 4.1.27
1.配置Jboss login-config.xml加入以下内容:
<application-policy name="AdminRealm"> 这个将要用在web Moduler里
<authentication>
<login-module code="org.jboss.security.auth.spi.LdapLoginModule" flag="required">
<module-option name="java.naming.factory.initial">com.sun.jndi.ldap.LdapCtxFactory</module-option>
<module-option name="java.naming.provider.url">ldap://localhost:389/</module-option>
<module-option name="java.naming.security.authentication">simple</module-option>
<module-option name="principalDNPrefix">cn=</module-option>
<module-option name="principalDNSuffix">,dc=sunose,dc=com</module-option>
<module-option name="rolesCtxDN">o=roles,dc=sunose,dc=com</module-option>
<module-option name="roleAttributeID">description</module-option>
<!--module-option name="userRolesCtxDNAttributeName">o=roles,dc=sunose,dc=com</module-option-->
<module-option name="uidAttributeID">sn</module-option>
<module-option name="matchOnUserDN">false</module-option>
<module-option name="unauthenticatedIdentity">nobody</module-option>
</login-module>
</authentication>
</application-policy>
2.开发CallbackHander类.或直接使用Jboss自带的:SecurityAssociationHandler
org.jboss.security.auth.callback.SecurityAssociationHandler放在jboss_home\all\lib\jbosssx.jar 文件中.
package org.sunose;
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 java.io.IOException;
public class LoginCallbackHandler implements CallbackHandler {
public LoginCallbackHandler()
{
this.username = null;
this.password = null;
}
/**
* The username to be provided when prompted.
*/
private String username;
/**
* The password to be provided when prompted.
*/
private String password;
public LoginCallbackHandler(String pusername, String ppassword)
{
setUsername(pusername);
setPassword(ppassword);
}
public void setUsername(String username)
{
if (username == null)
{
this.username = "";
return;
}
this.username = username;
}
public void setPassword(String password)
{
if (password == null)
{
this.password = "";
return;
}
this.password = password;
}
public void handle(Callback[] callbacks)
throws IOException, UnsupportedCallbackException
{
// Loop over all Callbacks
for (int i = 0; i < callbacks.length; i++)
{
Callback cb = callbacks[i];
if (cb instanceof NameCallback)
{
System.out.println("set username"+username);
((NameCallback)cb).setName(username);
}
else if (cb instanceof PasswordCallback)
{
// JAAS specifies that the password is a char[]
System.out.println("set password"+password);
((PasswordCallback)cb).setPassword(password.toCharArray());
}
else
{
throw new UnsupportedCallbackException(
cb, "Unrecognized Callback");
}
}
}
}
3.配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>jaascallback</display-name>
<servlet>
<servlet-name>loginservice</servlet-name>
<servlet-class>org.sunose.LoginService</servlet-class>
<security-role-ref>
<role-name>AdminRealm</role-name>
<role-link>AdminRealm</role-link>
</security-role-ref>
</servlet>
<servlet-mapping>
<servlet-name>loginservice</servlet-name>
<url-pattern>/loginservice</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<security-constraint>
<web-resource-collection>
<web-resource-name>EntireApplication</web-resource-name>
<url-pattern>/*.jsp</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>AdminRealm</role-name>
</auth-constraint>
<user-data-constraint>
<description>no description</description>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<realm-name>AdminRealm</realm-name>
<form-login-config>
<form-login-page>/index.jsp</form-login-page>
<form-error-page>/error.html</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>AdminRealm</role-name>
</security-role>
</web-app>
4.配置jboss-web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-web PUBLIC "-//JBoss//DTD Web Application 2.3V2//EN" "http://www.jboss.org/j2ee/dtd/jboss-web_3_2.dtd">
<jboss-web>
<security-domain>AdminRealm</security-domain>
<context-root>jaascallback</context-root>
</jboss-web>
5.开发servlet.
package org.sunose;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
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 java.io.IOException;
import javax.security.auth.login.*;
import javax.security.auth.*;
import org.jboss.security.auth.callback.*;
import org.jboss.security.SimplePrincipal;
import java.security.*;
public class LoginService extends HttpServlet {
private static final String CONTENT_TYPE = "text/html; charset=GBK";
//Initialize global variables
public void init() throws ServletException {
}
//Process the HTTP Get request
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
HttpServletRequest re=request;
String cusr=(String)re.getParameter("username");
String cpass=(String)re.getParameter("password");
if(cusr==null)cusr=" ";
if(cpass == null)cpass=" ";
LoginContext lc=null;
Enumeration ttt=re.getHeaderNames();
while(ttt.hasMoreElements())
{
String temp=(String)ttt.nextElement();
System.out.println(temp+"="+re.getHeader(temp));
}
System.out.println(cusr+"/"+cpass);
try
{
lc = new LoginContext("AdminRealm",new SecurityAssociationHandler(new SimplePrincipal(cusr),cpass));
//关键之重:LoginContext(String name...)必须同在Jboss login-config.xml定义的<application-policy name=...>相同.
lc.login();
}catch(Exception e)
{
System.out.println(e.getMessage());
response.getWriter().write(e.getMessage());
response.getWriter().write(":(");
return;
}
Subject gets = lc.getSubject();
Set p= gets.getPrincipals();
Iterator pi=p.iterator();
while( pi.hasNext())
{
System.out.println("Principle="+pi.next());
}
Principal t1=request.getUserPrincipal();
if(t1 !=null)System.out.println("UserPrincipal="+t1.getName());
System.out.println(request.isUserInRole("AdminRealm"));
System.out.println(request.isUserInRole("Admin"));
System.out.println(request.isUserInRole("manager"));
System.out.println(request.isUserInRole("members:AdminRealm"));
System.out.println("-------------start---------");
System.out.println(request.getAuthType());
System.out.println(request.getUserPrincipal());
System.out.println(request.getRemoteUser());
System.out.println("-------------over---------");
response.sendRedirect("check/frame.jsp");
}
//Process the HTTP Post request
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
//Clean up resources
public void destroy() {
}
}
6.开发login.jsp
<%@ page contentType="text/html; charset=GBK" %>
<html>
<head>
<title>
index
</title>
</head>
<body bgcolor="#ffffff">
<h1>
JBuilder Generated JSP
</h1>
<form method="post" action="loginservice">
<br><br>
username:<input type="text " name="username" >
password:<input type="password" name="password" maxlentgh=12>
<input type="submit" name="Submit" value="Submit">
<input type="reset" value="Reset">
</form>
</body>
</html>
7.配置OpenLdap:
根据login-config.xml中的有关配置,填写有关entiry属性。
建立test.ldif
# Organization for Test Corporation 组织
dn: dc=test,dc=com
objectClass: dcObject
objectClass: organization
dc: test
o: Test Corporation
description: The Test Corporation
# Organizational Role for Directory Manager in group 角色
dn: cn=mana312gers817,dc=test,dc=com
objectClass: organizationalRole
cn: mana312gers817
description: Directory Manager of mana312gers817 group
使用:slapadd -l test.ldif 输入LDAP数据库(Ldap Server必须先启动)
8.发步。测试.
星期四, 二月 26, 2004
星期三, 二月 25, 2004
jaas解决JAAS多次login
Re: [jBoss-User] jaas
In your case the client of JBoss is a multi-threaded server where the identity of a
client
can change with each servlet request. I have added a configuration option to the
ClientLoginModule that allows you to put it in the mode where it uses thread local
storage for the principal and credentials that are established during a login.
Configure the module with the multi-threaded option set to true as here:
other {
// Put your login modules that work without jBoss here
// jBoss LoginModule
org.jboss.security.ClientLoginModule required multi-threaded=true;
// Put your login modules that need jBoss here
};
and then in your servlet request handler you need to establish the client identity:
protected void doRequest(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String username = ...; // Obtain username & password from request properties
char[] password = ...;
LoginContext lc = null;
try
{
lc = new LoginContext("other", new CallbackHandler()
{
public void handle(Callback[] callbacks) throws
IOException, ServletException
{
for(int i = 0; i < callbacks.length; i++)
{
if (callbacks[i] instanceof NameCallback)
{
NameCallback n = (NameCallback) callbacks[i];
n.setName(username);
}
else if(callbacks[i] instanceof PasswordCallback)
{
PasswordCallback p = (PasswordCallback) callbacks[i];
p.setPassword(password);
}
else
{
throw new ServletException("Unrecognized Callback:
"+callbacks[i]);
}
}
}
}
);
lc.login();
// Work as username...
}
catch(LoginException e)
{
throw new ServletException(e.getMessage());
}
finally
{
if( lc != null )
lc.logout();
}
}
You could also just use the SecurityAssociation class directly:
import org.jboss.security.SecurityAssociation;
public void init(ServletConfig config) throws ServletException
{ // Use thread local storage for username,password
SecurityAssociation.setServer();
}
protected void doRequest(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String username = ...; // Obtain username & password from request properties
char[] password = ...;
try
{
SecurityAssociation.setPrincipal(new SimplePrincipal(username));
SecurityAssociation.setCredential(password);
// Work as username...
}
finally
{
SecurityAssociation.setPrincipal(null);
SecurityAssociation.setCredential(null);
}
}
害怕丢了,放到自己包里比较安心。:D
In your case the client of JBoss is a multi-threaded server where the identity of a
client
can change with each servlet request. I have added a configuration option to the
ClientLoginModule that allows you to put it in the mode where it uses thread local
storage for the principal and credentials that are established during a login.
Configure the module with the multi-threaded option set to true as here:
other {
// Put your login modules that work without jBoss here
// jBoss LoginModule
org.jboss.security.ClientLoginModule required multi-threaded=true;
// Put your login modules that need jBoss here
};
and then in your servlet request handler you need to establish the client identity:
protected void doRequest(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String username = ...; // Obtain username & password from request properties
char[] password = ...;
LoginContext lc = null;
try
{
lc = new LoginContext("other", new CallbackHandler()
{
public void handle(Callback[] callbacks) throws
IOException, ServletException
{
for(int i = 0; i < callbacks.length; i++)
{
if (callbacks[i] instanceof NameCallback)
{
NameCallback n = (NameCallback) callbacks[i];
n.setName(username);
}
else if(callbacks[i] instanceof PasswordCallback)
{
PasswordCallback p = (PasswordCallback) callbacks[i];
p.setPassword(password);
}
else
{
throw new ServletException("Unrecognized Callback:
"+callbacks[i]);
}
}
}
}
);
lc.login();
// Work as username...
}
catch(LoginException e)
{
throw new ServletException(e.getMessage());
}
finally
{
if( lc != null )
lc.logout();
}
}
You could also just use the SecurityAssociation class directly:
import org.jboss.security.SecurityAssociation;
public void init(ServletConfig config) throws ServletException
{ // Use thread local storage for username,password
SecurityAssociation.setServer();
}
protected void doRequest(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
String username = ...; // Obtain username & password from request properties
char[] password = ...;
try
{
SecurityAssociation.setPrincipal(new SimplePrincipal(username));
SecurityAssociation.setCredential(password);
// Work as username...
}
finally
{
SecurityAssociation.setPrincipal(null);
SecurityAssociation.setCredential(null);
}
}
害怕丢了,放到自己包里比较安心。:D
Access Management White Paper详细介绍访问控制的一些概念。
Access Management White Paper
Access Management White Paper
Introduction
What is Access Management?
Authentication
Authorization
Auditing
Administration
Confidentiality
Notification
The Case for Access Management
The Role of Access Management Systems
Security Policies
Single Sign-on
User Repositories
Harvesting User Data
Management Delegation
Notification
Performance and Reliability
Business Enablement
Cafésoft Access Management System
Conclusion
Introduction
In the early days of Internet application development, the basic security mechanisms provided by each web server was sufficient to secure a site's document and cgi-bin resources. Today, business requirements have matured to demand enterprise-scalable functionality for Internet and intranet applications (also called network applications).
A modern business may deploy dozens of network applications across multiple hosts or domains, and even between partner sites in different organizations. These sites often leverage application servers with built-in programmatic platforms such as Sun Microsystems' J2EE and Microsoft's Active Server Pages. To make this more complex, businesses desire to use the same security infrastructure for network applications as they do to address their wireless, legacy, database, and other application security requirements. And, users continue to demand greater satisfaction through ease-of-use, better performance, and new features personalized for their use!
Successful companies recognize that their security infrastructures must address these challenges. They are aware of the types of attacks that hackers can launch against their servers, and they plan appropriate defenses. However, the expanding role of network applications has increased the complexity and cost of implementing and maintaining strong security.
This white paper is written for both computing business managers and information technology professionals. The content provides a structure by which the requirements for a modern, network application security infrastructure can be measured and suggests a security platform solution to address those requirements.
What is Access Management?
Unlike firewalls and intrusion detection products, which are largely transparent to application developers, application security directly impacts design decisions. Without a unified strategy, developers re-implement custom security for each network application, which results in a variety of scalability and maintenance problems.
To combat this problem, various solutions have emerged, including access management products. As defined by the Gartner Group, ?Access Management products are solutions that provide a unified mechanism to manage the authentication of users (including single sign-on) and implement business rules determining user access to applications and data.”
Some access management solutions are limited to use with web technology; others are more general purpose. For convenience, this document will address network application security. However, a flexible access management system should be able to secure anything attached to the network such as wireless devices, legacy applications, databases, network appliances, set-top boxes, and more.
To better understand the value that an access management system provides, a brief review of the following security concepts is helpful:
Authentication - verifying users are who they claim to be
Authorization - granting users access to resources (also called entitlements)
Auditing - recording who did what and when
Administration - managing users and entitlements
Confidentiality - protecting data from unauthorized eyes
Notification - actively communicating security events
Although this paper does not address network level security (which is implemented by routers, firewalls, etc.), good practice must consider its use in conjunction with application-level security.
Authentication
As in the physical world, network application security starts with the identification of the players. Verifying the identity is what authentication is all about. Joe must be able to prove he's really Joe before the bank teller gives him money. In the physical world and online, the parties involved must answer these questions:
Who are you?
To what community do you belong?
Are you still a trusted member?
How can you prove your identity?
In other words, anyone who wishes to engage in trade must both establish his identity and present a credential as proof of that identity. That may sound easy, but it can be a complex management and implementation issue. The life cycle and presentation of credentials must be managed, and credentials must be made accessible to the parties that rely on them. Depending upon business requirements, applications in the same suite may have different authentication requirements ranging from usernames and passwords, to digital certificates, to biometric identifiers. Furthermore, as network application use grows, threats increase making authentication more important.
Authorization
Authentication is merely the tip of the iceberg. It's not enough to know that Joe is Joe. One must also manage which resources and data Joe (and thousands of other Joes) can view, download, update, and change. For example, an online trading company may grant a user “Power Trader” status based on his account balance. In this case, Joe Power Trader might be visually presented with options that standard users don't even see. For good reason, authorization is often also called entitlement.
Within operating system security, groups, access-control lists, and protections clearly define who can do what. Operating systems such as Windows NT implement authorization in the kernel, which results in offloading the responsibility for application security from developers. However, if one tries to move these applications across domains or implement custom business rules, operating systems falter. This requirement of network applications has somewhat diminished the role of an operating system. Today, more and more user authorization data is moving into operating system independent directories and other databases.
Auditing
Auditing is not just the simple business case of logging access attempts or security events to remember what the good guys did, and when they did it. It also represents the process of intelligent, active event monitoring to ensure that the bad guys are found out, and to debug system and operational problems. Additionally, it is an opportunity to profile users by tracking them throughout the session to determine patterns and preferences, which can be used to enhance return visits and increase results.
Some businesses will only care about security exception events, while others may want detailed usage reports to feed into a reporting or billing system. The ability to control the level of logging and generate useful reports is an important part of network application security requirements.
Administration
Most of the issues discussed in this paper deal with technological challenges. Yet operational challenges are where much of the ongoing application security effort and cost are buried. Operational challenges are core to any application administration discussion, but are often overlooked during the application development process. How many developers who are implementing security for an individual application will stop to think about the workflow required to add new users and assign them to roles that map to application features? Or, how about considering the cost of assisting users who forget their passwords or lose a digital certificate (a problem that's exacerbated when users are required to remember multiple user identities)?
A bigger puzzle is created when network applications have user communities that span departmental and organizational lines. To avoid bottlenecks, how does one delegate user administration to trusted personnel across his own organization, or to partners outside of the firewall? Then there's the content side. Can one effectively delegate the ability to put new content online, or modify entitlements to existing content?
Though all of these criteria should be considered, they may not be required by every application at each stage of development. However, unless these issues are clearly thought through, the application security model is incomplete, and administration issues can quickly incur overwhelming costs and staff time.
Confidentiality
The Internet poses unique confidentiality issues that businesses must address to minimize risk. For example, Joe will submit information via the web only if he is confident that his financial data or medical history are kept secret. High-value business-to-business commerce demands even stronger protection. The key to confidentiality is data encryption.
Strictly speaking, encryption is the process of turning readable text into cipher text. Encryption can be a one-way process, often called a hash, where the algorithm used to create the cipher text yields the same, unique results for given inputs and prohibits deciphering by anyone, even the originator. Encryption can also be a two-way process where values are converted to cipher text and then deciphered by the receiver, provided he has received the required keys. Both of these techniques are a fundamental part of network application security.
Generally, hashes are used for saving passwords. When Joe creates a password, a hash of the password is saved to the database and the real password is tossed away. Upon login, the password Joe enters is hashed using the same algorithm. The new hash is compared with the database value and, if there's a match, Joe is authenticated. This process prevents anyone, even system administrators, from viewing the real password.
Public Key Infrastructure (PKI) cryptography and digital signature technology, applied via Secure Sockets Layer (SSL) digital certificates, provide the data integrity and privacy for securing connections used by network applications. SSL encrypts all data exchanged between web servers and users using a unique session key, which is transparently provided to the user as cipher text, along with the server's public key to do the deciphering. These layers of protection ensure that data cannot be viewed if intercepted by the bad guys.
Notification
As users interact with a network application, many security events will be generated. These events can include authentication requests, access requests, session tracking, access denials, errors, and more. Through auditing, these events are captured into logs. But what if one wants to do something intelligent with a security event immediately? This is where notification comes into play. For example, a corporate help desk administrator may want to receive an email notification any time a user fails three times while attempting to login. Or, a business program manager may want to be notified when user accounts go idle for over three months. Or, a security manager may want to programmatically inform the firewall to deny all access to a site for an IP address with suspicious behavior. Effective use of notification enhances the business processes and responsiveness of any organization.
The Case for Access Management
The complexity of modern, multi-tier applications exposes them to hacks if they are not properly configured and administered. To illustrate, consider a typical application development approach.
Developers frequently use what might be called “front-end” security. When users login to an application, a session is created, which may reside on the client or middle tier. As users access functions, usernames are available to authorize access and to log user activity. However, the architecture lacks a way to pass authenticated usernames to back-end resources like a database. Instead, all share a single database credential. In effect, the back-end trusts that:
the front-end authorizes access to the back-end
front-end security cannot be compromised or bypassed
Even if this trust is well placed, the back-end loses the ability to associate users with transactions. Consequently, Joe may be granted access to more services and information than he should, and back-end service logs cannot provide the context for meaningful security audits.
Obviously, security becomes increasingly complex as the number of elements and boundaries within a network application increase (web server, application server, messaging server, database server, etc). Achieving application security across platforms requires that both security products and platform elements provide integration points.
Application security is a horizontal requirement across multiple applications, platforms, and infrastructure. In general, there's no business reason why Joe should need multiple usernames. Hence, the end goal of application security should always include single sign-on (SSO). The objective of SSO is to allow users to access all applications from one login.
SSO is a boon to any organization, decreasing both complexity and costs. A side benefit of SSO is the centralization of all security logging and events. Rather than distributing logs across multiple applications and systems, a single consolidated history of security events can be intelligently mined. If someone is trying to breech multiple applications, administrators have a chance to find out.
The Role of Access Management Systems
It would be a lofty goal for any enterprise to implement all of the application security elements discussed to this point. Fortunately, most organizations can meet their business requirements by leveraging third party security solutions. The remainder of this paper discusses how a centralized access management system provides a security platform to address an organization’s network application security requirements.
Security Policies
What's a security policy? It's simply the set of business rules that govern the use of resources at a site. Drive a few million hits a day through an advanced web site, and a policy engine to process the rules becomes a necessity (though lower volume sites will also benefit). The policy engine is at the core of an access management system. It alone answers the question “Can I do . . . ?” The answer is simply yes or no, grant or deny. Yet the core value of an access management solution is defined by the policy engine logic, which should be packaged to flexibly integrate into a site and implement custom business rules.
Figure 1.0 - Proxy Network Topology
Typically, a scalable policy server hosts the engine's decision-making power. The policy server is network aware and provides a consistent security toolbox across network applications. Consequently, it must integrate with the network application infrastructure. There are two general approaches to this architectural problem. The first is fairly generic, inserting the policy server as a proxy between the users and the resources (see figure 1.0). The second approach is to integrate agents into web servers and other hosts to act as a front-line defense and propagate policy decisions back to the centralized policy server (see figure 2.0). The former is preferred for ease-of-deployment, the latter for scalability, flexibility, and performance.
Figure 2.0 - Agent Network Topology
Single Sign-on
Because the web's protocol, HTTP, is stateless, it can't “remember” a user's login. Without some way to remember, secure applications would have to re-authenticate the user for every single resource on every single page. A number of clever solutions to this problem are in use today. The most common makes use of browser-based cookies.
Cookies, though often maligned as an invasion of privacy, are quite useful and secure when properly used. When a user requests a secure resource, his browser is redirected to an authentication server, which requests that the user logs in. Upon successful login, the authentication server sends the browser a secure cookie containing authentication information, and redirects the browser back to the originally requested resource.
The browser cookie is encrypted, time-stamped to expire when the browser session ends, and digitally signed by the authentication server. Within the same domain, multiple servers and applications can share the same cookie authentication information. But, much like in real life, it's hard to share a cookie. By design, cookies are not permitted to be accessed by servers from different domains. To get around this cookie limitation (or security feature depending upon the point of view), access management systems use various techniques that involve a series of HTTP redirects. Hence, a good access management system provides secure single sign-on within, and across, multiple applications, servers, and domains.
User Repositories
Security policies, user profiles, and configuration data must be stored in a database or file. For security policies and configuration data, expressing information in a format somewhat proprietary to the access management system is usually not an issue. In fact, a clean policy server architecture will ensure that the list of security policies will be short enough to be cached into high-performance memory!
Unfortunately, this is definitely not the case for user profile information, where integration requirements are diverse, and repositories can occupy gigabytes of storage. For example, some organizations may have large amounts of user data in multiple and even heterogeneous repositories, including LDAP servers, NT domains, and SQL databases. An access management system with a proprietary user repository can add to this complexity.
An access management system should provide high-performance database lookups, along with the flexibility to integrate with existing enterprise repositories. To this end, flexible integration with existing industry standard user repositories, such as LDAP, NT domains, and SQL databases, is a must. Also, the access management system should have tools and templates for developers to write their own drivers for accessing user data in legacy repositories.
Harvesting User Data
Although dynamic information will usually be obtained and manipulated directly by the application, there are circumstances where the access management system may intervene for the developer. First, is to provide the programmer with tools to control access based on some sort of user profile data. For example, “show the free first class upgrade page only if the user has at least 50,000 air miles this year.” In this case, the logic is implemented by business rules in the policy server for potential access by a suite of applications.
Second, is to enable the programmer to fetch user data that has already been harvested by the authentication server during login. For example, it may be determined during login that the user has 50,000 air miles. The application developer can then make fine-grained decisions from this profile data to display more options or cosmetically enhance a page with “gold_customer_logo.gif”. In this case, the programmer will use either API calls or have the data returned via HTTP headers. In some cases, this shortcut saves the application developer from making database calls.
Management Delegation
Management of user profiles and policies may not always be as centralized as the policies themselves! In a corporate intranet, for example, different departments may want control over their own security policies and user administration. In fact, even though they may not want control, it may be a business requirement to avoid workflow bottlenecks. Or, organizations may have internal users and customers in the same repository, with responsibility for administration given to various personnel in customer support and human resources. In either case, the capability for an access management system to delegate both security policy and user profile administration, is critical.
The degree to which an access management system provides delegation may somewhat be a function of how it manages repositories. Most should provide excellent control over the security policy repository, but only those with highly coupled user repositories will provide fine-grained delegation of user profile data down to the attribute level. Others will provide management user interfaces that layer onto existing repositories.
Notification
The access management system can be an integral part of the audit and reporting structure of a web site, and it can also help maintain security by supporting break-in detection and other notification functions. With break-in detection, multiple incorrect passwords in a short period of time will disable an account, lock it out for a set period, or send notification to a security monitoring station. A notification service should run in a separate thread and be able to register itself to receive security events, rather than by making a decision from reviewing the contents of log files, which can be processor intensive.
Notifications can be based on frequency and time of day, and fall into categories that generate different actions such as a page, an email, a sound or dialog box, an SNMP trap, and others. The choice of notification can vary based on schedule (e.g., notify the network manager during the day, but the NOC at night). Notifications are not just for security problems, but also server problems. For example, an overload notification might trigger when an operation eats up CPU time, or when a system server or agent fails.
Performance and Reliability
Perhaps the most important topic is saved for last, because there isn't that much to say about it. Access management systems must be fast; blazingly fast. And they simply can't go down. Performance and high availability is mission critical, and is provided through features such as load sharing, load balancing, replication, and failover.
Redundancy is a key architectural element of an access management system. Virtually every component can be duplicated: policy servers, authentication servers, web agents, and back-end directories. The policy server database can be redundant and is multi-master replicated, which allows for multiple copies to be kept current around the network. Load sharing or failover, or both, across the user directory and the policy database are critical.
An agent topology is preferred to the proxy mechanism previously discussed. Agents scale the load naturally, while proxies can become bottlenecks. Agents contact the main policy server to download a list of available policy servers. Queries to each policy server are tracked for performance, with preference for the best performing policy server. If a policy server goes down, the agent will automatically choose the next available policy server. If an agent goes down, others are already available in the server farm.
Business Enablement
In addition to lowering the cost and complexity of existing network application security issues, access management systems are also a business enabling technology. For example, centralized access management solutions enable:
business partners a platform upon which security is implemented and administration delegated between organizations;
organizations to secure applications, systems, databases, devices, and more that were previously not-secure, or that were a security island;
a cross-platform infrastructure upon which new authentication methods (such as smart cards or a new biometric device) can be centrally implemented and delivered to users;
developers and administrators to use tools to test business rules and ensure there are no security holes;
single sign-on, both within an organization and across organizational boundaries;
a development paradigm where security is configured by administrators at application deployment, rather than during development.
In each of these cases, a centralized access management system provides the benefit of enabling organizations to securely create new ways of transacting with employees, customers, and partners.
Access Management White Paper
Introduction
What is Access Management?
Authentication
Authorization
Auditing
Administration
Confidentiality
Notification
The Case for Access Management
The Role of Access Management Systems
Security Policies
Single Sign-on
User Repositories
Harvesting User Data
Management Delegation
Notification
Performance and Reliability
Business Enablement
Cafésoft Access Management System
Conclusion
Introduction
In the early days of Internet application development, the basic security mechanisms provided by each web server was sufficient to secure a site's document and cgi-bin resources. Today, business requirements have matured to demand enterprise-scalable functionality for Internet and intranet applications (also called network applications).
A modern business may deploy dozens of network applications across multiple hosts or domains, and even between partner sites in different organizations. These sites often leverage application servers with built-in programmatic platforms such as Sun Microsystems' J2EE and Microsoft's Active Server Pages. To make this more complex, businesses desire to use the same security infrastructure for network applications as they do to address their wireless, legacy, database, and other application security requirements. And, users continue to demand greater satisfaction through ease-of-use, better performance, and new features personalized for their use!
Successful companies recognize that their security infrastructures must address these challenges. They are aware of the types of attacks that hackers can launch against their servers, and they plan appropriate defenses. However, the expanding role of network applications has increased the complexity and cost of implementing and maintaining strong security.
This white paper is written for both computing business managers and information technology professionals. The content provides a structure by which the requirements for a modern, network application security infrastructure can be measured and suggests a security platform solution to address those requirements.
What is Access Management?
Unlike firewalls and intrusion detection products, which are largely transparent to application developers, application security directly impacts design decisions. Without a unified strategy, developers re-implement custom security for each network application, which results in a variety of scalability and maintenance problems.
To combat this problem, various solutions have emerged, including access management products. As defined by the Gartner Group, ?Access Management products are solutions that provide a unified mechanism to manage the authentication of users (including single sign-on) and implement business rules determining user access to applications and data.”
Some access management solutions are limited to use with web technology; others are more general purpose. For convenience, this document will address network application security. However, a flexible access management system should be able to secure anything attached to the network such as wireless devices, legacy applications, databases, network appliances, set-top boxes, and more.
To better understand the value that an access management system provides, a brief review of the following security concepts is helpful:
Authentication - verifying users are who they claim to be
Authorization - granting users access to resources (also called entitlements)
Auditing - recording who did what and when
Administration - managing users and entitlements
Confidentiality - protecting data from unauthorized eyes
Notification - actively communicating security events
Although this paper does not address network level security (which is implemented by routers, firewalls, etc.), good practice must consider its use in conjunction with application-level security.
Authentication
As in the physical world, network application security starts with the identification of the players. Verifying the identity is what authentication is all about. Joe must be able to prove he's really Joe before the bank teller gives him money. In the physical world and online, the parties involved must answer these questions:
Who are you?
To what community do you belong?
Are you still a trusted member?
How can you prove your identity?
In other words, anyone who wishes to engage in trade must both establish his identity and present a credential as proof of that identity. That may sound easy, but it can be a complex management and implementation issue. The life cycle and presentation of credentials must be managed, and credentials must be made accessible to the parties that rely on them. Depending upon business requirements, applications in the same suite may have different authentication requirements ranging from usernames and passwords, to digital certificates, to biometric identifiers. Furthermore, as network application use grows, threats increase making authentication more important.
Authorization
Authentication is merely the tip of the iceberg. It's not enough to know that Joe is Joe. One must also manage which resources and data Joe (and thousands of other Joes) can view, download, update, and change. For example, an online trading company may grant a user “Power Trader” status based on his account balance. In this case, Joe Power Trader might be visually presented with options that standard users don't even see. For good reason, authorization is often also called entitlement.
Within operating system security, groups, access-control lists, and protections clearly define who can do what. Operating systems such as Windows NT implement authorization in the kernel, which results in offloading the responsibility for application security from developers. However, if one tries to move these applications across domains or implement custom business rules, operating systems falter. This requirement of network applications has somewhat diminished the role of an operating system. Today, more and more user authorization data is moving into operating system independent directories and other databases.
Auditing
Auditing is not just the simple business case of logging access attempts or security events to remember what the good guys did, and when they did it. It also represents the process of intelligent, active event monitoring to ensure that the bad guys are found out, and to debug system and operational problems. Additionally, it is an opportunity to profile users by tracking them throughout the session to determine patterns and preferences, which can be used to enhance return visits and increase results.
Some businesses will only care about security exception events, while others may want detailed usage reports to feed into a reporting or billing system. The ability to control the level of logging and generate useful reports is an important part of network application security requirements.
Administration
Most of the issues discussed in this paper deal with technological challenges. Yet operational challenges are where much of the ongoing application security effort and cost are buried. Operational challenges are core to any application administration discussion, but are often overlooked during the application development process. How many developers who are implementing security for an individual application will stop to think about the workflow required to add new users and assign them to roles that map to application features? Or, how about considering the cost of assisting users who forget their passwords or lose a digital certificate (a problem that's exacerbated when users are required to remember multiple user identities)?
A bigger puzzle is created when network applications have user communities that span departmental and organizational lines. To avoid bottlenecks, how does one delegate user administration to trusted personnel across his own organization, or to partners outside of the firewall? Then there's the content side. Can one effectively delegate the ability to put new content online, or modify entitlements to existing content?
Though all of these criteria should be considered, they may not be required by every application at each stage of development. However, unless these issues are clearly thought through, the application security model is incomplete, and administration issues can quickly incur overwhelming costs and staff time.
Confidentiality
The Internet poses unique confidentiality issues that businesses must address to minimize risk. For example, Joe will submit information via the web only if he is confident that his financial data or medical history are kept secret. High-value business-to-business commerce demands even stronger protection. The key to confidentiality is data encryption.
Strictly speaking, encryption is the process of turning readable text into cipher text. Encryption can be a one-way process, often called a hash, where the algorithm used to create the cipher text yields the same, unique results for given inputs and prohibits deciphering by anyone, even the originator. Encryption can also be a two-way process where values are converted to cipher text and then deciphered by the receiver, provided he has received the required keys. Both of these techniques are a fundamental part of network application security.
Generally, hashes are used for saving passwords. When Joe creates a password, a hash of the password is saved to the database and the real password is tossed away. Upon login, the password Joe enters is hashed using the same algorithm. The new hash is compared with the database value and, if there's a match, Joe is authenticated. This process prevents anyone, even system administrators, from viewing the real password.
Public Key Infrastructure (PKI) cryptography and digital signature technology, applied via Secure Sockets Layer (SSL) digital certificates, provide the data integrity and privacy for securing connections used by network applications. SSL encrypts all data exchanged between web servers and users using a unique session key, which is transparently provided to the user as cipher text, along with the server's public key to do the deciphering. These layers of protection ensure that data cannot be viewed if intercepted by the bad guys.
Notification
As users interact with a network application, many security events will be generated. These events can include authentication requests, access requests, session tracking, access denials, errors, and more. Through auditing, these events are captured into logs. But what if one wants to do something intelligent with a security event immediately? This is where notification comes into play. For example, a corporate help desk administrator may want to receive an email notification any time a user fails three times while attempting to login. Or, a business program manager may want to be notified when user accounts go idle for over three months. Or, a security manager may want to programmatically inform the firewall to deny all access to a site for an IP address with suspicious behavior. Effective use of notification enhances the business processes and responsiveness of any organization.
The Case for Access Management
The complexity of modern, multi-tier applications exposes them to hacks if they are not properly configured and administered. To illustrate, consider a typical application development approach.
Developers frequently use what might be called “front-end” security. When users login to an application, a session is created, which may reside on the client or middle tier. As users access functions, usernames are available to authorize access and to log user activity. However, the architecture lacks a way to pass authenticated usernames to back-end resources like a database. Instead, all share a single database credential. In effect, the back-end trusts that:
the front-end authorizes access to the back-end
front-end security cannot be compromised or bypassed
Even if this trust is well placed, the back-end loses the ability to associate users with transactions. Consequently, Joe may be granted access to more services and information than he should, and back-end service logs cannot provide the context for meaningful security audits.
Obviously, security becomes increasingly complex as the number of elements and boundaries within a network application increase (web server, application server, messaging server, database server, etc). Achieving application security across platforms requires that both security products and platform elements provide integration points.
Application security is a horizontal requirement across multiple applications, platforms, and infrastructure. In general, there's no business reason why Joe should need multiple usernames. Hence, the end goal of application security should always include single sign-on (SSO). The objective of SSO is to allow users to access all applications from one login.
SSO is a boon to any organization, decreasing both complexity and costs. A side benefit of SSO is the centralization of all security logging and events. Rather than distributing logs across multiple applications and systems, a single consolidated history of security events can be intelligently mined. If someone is trying to breech multiple applications, administrators have a chance to find out.
The Role of Access Management Systems
It would be a lofty goal for any enterprise to implement all of the application security elements discussed to this point. Fortunately, most organizations can meet their business requirements by leveraging third party security solutions. The remainder of this paper discusses how a centralized access management system provides a security platform to address an organization’s network application security requirements.
Security Policies
What's a security policy? It's simply the set of business rules that govern the use of resources at a site. Drive a few million hits a day through an advanced web site, and a policy engine to process the rules becomes a necessity (though lower volume sites will also benefit). The policy engine is at the core of an access management system. It alone answers the question “Can I do . . . ?” The answer is simply yes or no, grant or deny. Yet the core value of an access management solution is defined by the policy engine logic, which should be packaged to flexibly integrate into a site and implement custom business rules.
Figure 1.0 - Proxy Network Topology
Typically, a scalable policy server hosts the engine's decision-making power. The policy server is network aware and provides a consistent security toolbox across network applications. Consequently, it must integrate with the network application infrastructure. There are two general approaches to this architectural problem. The first is fairly generic, inserting the policy server as a proxy between the users and the resources (see figure 1.0). The second approach is to integrate agents into web servers and other hosts to act as a front-line defense and propagate policy decisions back to the centralized policy server (see figure 2.0). The former is preferred for ease-of-deployment, the latter for scalability, flexibility, and performance.
Figure 2.0 - Agent Network Topology
Single Sign-on
Because the web's protocol, HTTP, is stateless, it can't “remember” a user's login. Without some way to remember, secure applications would have to re-authenticate the user for every single resource on every single page. A number of clever solutions to this problem are in use today. The most common makes use of browser-based cookies.
Cookies, though often maligned as an invasion of privacy, are quite useful and secure when properly used. When a user requests a secure resource, his browser is redirected to an authentication server, which requests that the user logs in. Upon successful login, the authentication server sends the browser a secure cookie containing authentication information, and redirects the browser back to the originally requested resource.
The browser cookie is encrypted, time-stamped to expire when the browser session ends, and digitally signed by the authentication server. Within the same domain, multiple servers and applications can share the same cookie authentication information. But, much like in real life, it's hard to share a cookie. By design, cookies are not permitted to be accessed by servers from different domains. To get around this cookie limitation (or security feature depending upon the point of view), access management systems use various techniques that involve a series of HTTP redirects. Hence, a good access management system provides secure single sign-on within, and across, multiple applications, servers, and domains.
User Repositories
Security policies, user profiles, and configuration data must be stored in a database or file. For security policies and configuration data, expressing information in a format somewhat proprietary to the access management system is usually not an issue. In fact, a clean policy server architecture will ensure that the list of security policies will be short enough to be cached into high-performance memory!
Unfortunately, this is definitely not the case for user profile information, where integration requirements are diverse, and repositories can occupy gigabytes of storage. For example, some organizations may have large amounts of user data in multiple and even heterogeneous repositories, including LDAP servers, NT domains, and SQL databases. An access management system with a proprietary user repository can add to this complexity.
An access management system should provide high-performance database lookups, along with the flexibility to integrate with existing enterprise repositories. To this end, flexible integration with existing industry standard user repositories, such as LDAP, NT domains, and SQL databases, is a must. Also, the access management system should have tools and templates for developers to write their own drivers for accessing user data in legacy repositories.
Harvesting User Data
Although dynamic information will usually be obtained and manipulated directly by the application, there are circumstances where the access management system may intervene for the developer. First, is to provide the programmer with tools to control access based on some sort of user profile data. For example, “show the free first class upgrade page only if the user has at least 50,000 air miles this year.” In this case, the logic is implemented by business rules in the policy server for potential access by a suite of applications.
Second, is to enable the programmer to fetch user data that has already been harvested by the authentication server during login. For example, it may be determined during login that the user has 50,000 air miles. The application developer can then make fine-grained decisions from this profile data to display more options or cosmetically enhance a page with “gold_customer_logo.gif”. In this case, the programmer will use either API calls or have the data returned via HTTP headers. In some cases, this shortcut saves the application developer from making database calls.
Management Delegation
Management of user profiles and policies may not always be as centralized as the policies themselves! In a corporate intranet, for example, different departments may want control over their own security policies and user administration. In fact, even though they may not want control, it may be a business requirement to avoid workflow bottlenecks. Or, organizations may have internal users and customers in the same repository, with responsibility for administration given to various personnel in customer support and human resources. In either case, the capability for an access management system to delegate both security policy and user profile administration, is critical.
The degree to which an access management system provides delegation may somewhat be a function of how it manages repositories. Most should provide excellent control over the security policy repository, but only those with highly coupled user repositories will provide fine-grained delegation of user profile data down to the attribute level. Others will provide management user interfaces that layer onto existing repositories.
Notification
The access management system can be an integral part of the audit and reporting structure of a web site, and it can also help maintain security by supporting break-in detection and other notification functions. With break-in detection, multiple incorrect passwords in a short period of time will disable an account, lock it out for a set period, or send notification to a security monitoring station. A notification service should run in a separate thread and be able to register itself to receive security events, rather than by making a decision from reviewing the contents of log files, which can be processor intensive.
Notifications can be based on frequency and time of day, and fall into categories that generate different actions such as a page, an email, a sound or dialog box, an SNMP trap, and others. The choice of notification can vary based on schedule (e.g., notify the network manager during the day, but the NOC at night). Notifications are not just for security problems, but also server problems. For example, an overload notification might trigger when an operation eats up CPU time, or when a system server or agent fails.
Performance and Reliability
Perhaps the most important topic is saved for last, because there isn't that much to say about it. Access management systems must be fast; blazingly fast. And they simply can't go down. Performance and high availability is mission critical, and is provided through features such as load sharing, load balancing, replication, and failover.
Redundancy is a key architectural element of an access management system. Virtually every component can be duplicated: policy servers, authentication servers, web agents, and back-end directories. The policy server database can be redundant and is multi-master replicated, which allows for multiple copies to be kept current around the network. Load sharing or failover, or both, across the user directory and the policy database are critical.
An agent topology is preferred to the proxy mechanism previously discussed. Agents scale the load naturally, while proxies can become bottlenecks. Agents contact the main policy server to download a list of available policy servers. Queries to each policy server are tracked for performance, with preference for the best performing policy server. If a policy server goes down, the agent will automatically choose the next available policy server. If an agent goes down, others are already available in the server farm.
Business Enablement
In addition to lowering the cost and complexity of existing network application security issues, access management systems are also a business enabling technology. For example, centralized access management solutions enable:
business partners a platform upon which security is implemented and administration delegated between organizations;
organizations to secure applications, systems, databases, devices, and more that were previously not-secure, or that were a security island;
a cross-platform infrastructure upon which new authentication methods (such as smart cards or a new biometric device) can be centrally implemented and delivered to users;
developers and administrators to use tools to test business rules and ensure there are no security holes;
single sign-on, both within an organization and across organizational boundaries;
a development paradigm where security is configured by administrators at application deployment, rather than during development.
In each of these cases, a centralized access management system provides the benefit of enabling organizations to securely create new ways of transacting with employees, customers, and partners.
Jboss Base JAAS 实现LDAP 验证(最终)
1.配置config/login-config.xml加入:
<application-policy name="AdminRealm">
<authentication>
<login-module code="org.jboss.security.auth.spi.LdapLoginModule" flag="required">
<module-option name="java.naming.factory.initial">com.sun.jndi.ldap.LdapCtxFactory</module-option>
<module-option name="java.naming.provider.url">ldap://localhost:389/</module-option>
<module-option name="java.naming.security.authentication">simple</module-option>
<module-option name="principalDNPrefix">cn=</module-option>
<module-option name="principalDNSuffix">,dc=sunose,dc=com</module-option>
<module-option name="rolesCtxDN">ou=Roles,o=sunose.com</module-option>
<module-option name="roleAttributeID">description</module-option>
<module-option name="uidAttributeID">sn</module-option>
<module-option name="matchOnUserDN">false</module-option>
</login-module>
</authentication>
</application-policy>
2.配置使用的web Module web.xml 加入:
<security-constraint>
<display-name>
<![CDATA[Constraints of the Administration Console's Security Environment]]>
</display-name>
<web-resource-collection>
<web-resource-name>Protected Web Resources</web-resource-name>
<url-pattern>/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>Administrator</role-name>
</auth-constraint>
<user-data-constraint>
<description>no description</description>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/index.jsp</form-login-page>
<form-error-page>/error.html</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>Administrator</role-name>
</security-role>
3.修改jboss-web.xml加入:
<jboss-web>
<security-domain>AdminRealm</security-domain><!--注意这里的domain同步骤1中login-config.xml配置的application-policy保持一致-->
<context-root>payroll</context-root>
</jboss-web>
4.开发CallbackHandler Class,如下(基于Servlet,如果是其他方式比如client写法有不同)
static class AppCallbackHandler implements CallbackHandler {
private String pusername;
private char[] ppassword;
public AppCallbackHandler(String username, char[] password)
{
this.pusername = username;
this.ppassword = password;
}
public void handle(Callback[] callbacks) throws java.io.IOException, UnsupportedCallbackException
{
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
System.out.println("handle " + i + ": callbacks["+i+"] instanceof NameCallback");
NameCallback nc = (NameCallback)callbacks[i];
System.out.println("setting username to Carrier");
nc.setName(pusername);
}
else if (callbacks[i] instanceof PasswordCallback)
{
System.out.println("handle " + i + ": callbacks["+i+"] instanceof PasswordCallback");
PasswordCallback pc = (PasswordCallback)callbacks[i];
System.out.println("setting password to Carrier");
pc.setPassword(ppassword);
}
else {
System.out.println("handle: unrecognized callback " + callbacks[i].getClass().getName());
throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback");
}
}
}
}
5.在Servlet中使用LoginContext来实现authentication.(认证).
....在合适位置加入下列代码.
LoginContext lc = null;
try {
lc = new LoginContext("AdminRealm", new AppCallbackHandler(username,password.toCharArray()));
System.out.println("Ready for Login");
lc.login();
}catch (LoginException le)
{
System.err.println("Cannot create LoginContext. " + le.getMessage());
} catch (SecurityException se) {
System.err.println("Cannot create LoginContext. " + se.getMessage());
}
6.发布打包,既可.
<application-policy name="AdminRealm">
<authentication>
<login-module code="org.jboss.security.auth.spi.LdapLoginModule" flag="required">
<module-option name="java.naming.factory.initial">com.sun.jndi.ldap.LdapCtxFactory</module-option>
<module-option name="java.naming.provider.url">ldap://localhost:389/</module-option>
<module-option name="java.naming.security.authentication">simple</module-option>
<module-option name="principalDNPrefix">cn=</module-option>
<module-option name="principalDNSuffix">,dc=sunose,dc=com</module-option>
<module-option name="rolesCtxDN">ou=Roles,o=sunose.com</module-option>
<module-option name="roleAttributeID">description</module-option>
<module-option name="uidAttributeID">sn</module-option>
<module-option name="matchOnUserDN">false</module-option>
</login-module>
</authentication>
</application-policy>
2.配置使用的web Module web.xml 加入:
<security-constraint>
<display-name>
<![CDATA[Constraints of the Administration Console's Security Environment]]>
</display-name>
<web-resource-collection>
<web-resource-name>Protected Web Resources</web-resource-name>
<url-pattern>/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>Administrator</role-name>
</auth-constraint>
<user-data-constraint>
<description>no description</description>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/index.jsp</form-login-page>
<form-error-page>/error.html</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>Administrator</role-name>
</security-role>
3.修改jboss-web.xml加入:
<jboss-web>
<security-domain>AdminRealm</security-domain><!--注意这里的domain同步骤1中login-config.xml配置的application-policy保持一致-->
<context-root>payroll</context-root>
</jboss-web>
4.开发CallbackHandler Class,如下(基于Servlet,如果是其他方式比如client写法有不同)
static class AppCallbackHandler implements CallbackHandler {
private String pusername;
private char[] ppassword;
public AppCallbackHandler(String username, char[] password)
{
this.pusername = username;
this.ppassword = password;
}
public void handle(Callback[] callbacks) throws java.io.IOException, UnsupportedCallbackException
{
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
System.out.println("handle " + i + ": callbacks["+i+"] instanceof NameCallback");
NameCallback nc = (NameCallback)callbacks[i];
System.out.println("setting username to Carrier");
nc.setName(pusername);
}
else if (callbacks[i] instanceof PasswordCallback)
{
System.out.println("handle " + i + ": callbacks["+i+"] instanceof PasswordCallback");
PasswordCallback pc = (PasswordCallback)callbacks[i];
System.out.println("setting password to Carrier");
pc.setPassword(ppassword);
}
else {
System.out.println("handle: unrecognized callback " + callbacks[i].getClass().getName());
throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback");
}
}
}
}
5.在Servlet中使用LoginContext来实现authentication.(认证).
....在合适位置加入下列代码.
LoginContext lc = null;
try {
lc = new LoginContext("AdminRealm", new AppCallbackHandler(username,password.toCharArray()));
System.out.println("Ready for Login");
lc.login();
}catch (LoginException le)
{
System.err.println("Cannot create LoginContext. " + le.getMessage());
} catch (SecurityException se) {
System.err.println("Cannot create LoginContext. " + se.getMessage());
}
6.发布打包,既可.
星期二, 二月 24, 2004
保护系统安全:一个识别用户的三向解决方案
保护系统安全:一个识别用户的三向解决方案
系统安全问题是由发现通讯连接另一端的用户身份开始的。在本文中, Joseph Sinclair 讨论了三种常见的识别用户的方法,突出了它们的长处和短处(各自的及合并的),并分别提供了一些示例。
砰,砰,砰,“谁在那儿?”
这是一个在所有安全行为中占核心地位的简单问题。对于要求访问您敏感而珍贵的数据的用户,不确定他们的身份就无法让您放心。系统的安全性是以建立那种信任的系统能力为依据的。
如何确定信任谁?由于世界上确有不道德的人存在,您又如何确定发出请求的人是否如他们自己所说的那样呢?
跟着我们来讨论三种确定用户身份的方法吧 -- 其中两种是老的(在计算机业术语中)、“可信赖的”朋友,另外一个则建立在新技术的基础上。
Java 身份认证与授权服务
Java 平台的以用户为中心的安全措施与 Java 身份认证与授权服务 API(JAAS) 是同时推出的。使用 Java 技术创建有效的身份认证系统是从这个重要的 API 开始的。
JAAS 提供了一个服务统一接口,其方式与 JMS 为消息传递服务提供统一 API 的方式或 JDBC 为各种数据库驱动器提供统一 API 的方式相同。由于 JAAS 是一个 API,各种服务供应商能自由地使用任何可用技术来执行那个 API。
JAAS 是一个可插入、可叠放的 API,这意味着在系统配置期间能够添加、组合和修改身份认证服务,而无须在系统执行期间实行硬件编码。这一方法允许系统的身份认证架构根据需要进行修改和完善,并不用对系统的身份认证架构进行内部重建或修改。因此,如果分析表明系统需要加强安全措施,那么知识性问题将被提升为所有权问题,而它还能进一步提升为特性问题。但这一切只在必要时,且必要性变得明显的时候才实施。
除了身份认证 API 以外, JAAS 还提供了一个授权 API。 JAAS 通过使用户(JAAS 说法称作为“主体”)保留一个或多个身份的方法提供授权支持,其中的每个身份都能列在资源的访问控制表 (ACL) 中。这样,系统就能检查主体的身份以及资源的 ACL 了。如果找到匹配信息,就允许指定的访问。如果未能找到匹配信息,就能拒绝访问。
JAAS 还通过证书接口提供了单独注册能力。证书可能含有多种附加信息对象,包括代表性的安全上下文信息或诸如 Kerberos 或 Public Key Infrastructures 等外部系统的访问证书。证书允许用户通过 JAAS 供应者得到身份认证,并同时得到访问多种企业资源的身份认证。
我强烈建议任何企业级的 Java 开发人员研究一下完整的 JAAS 规范(请参阅参考资料)并发掘那些已经在他们厂商的平台上的可用解决方案。对应用程序安全使用 JAAS 接口能显著减小实现有效安全层所需的努力,并同时增加系统的安全性。
建立身份
乍看起来,建立一个请求者的身份非常简单。它是建立在回答以下三个基本问题中的一个或几个的基础上的:
您知道些什么?知识是进入安全系统中的传统密匙。普遍存在的用户名和密码提示是最为普通的身份问题;即使是计算机间通讯时,通常也是从验证一些对以前达成共识信息的知识开始的。这个身份识别的 密码短语方法远比计算机历史久远 -- 它是身份识别的“社会”方法。
您有些什么?较低级的密匙,从古时候起就是安全措施的基础,是一种 所有权问题的形式。任何人拥有钥匙的人都能打开那个锁。现在, ATM 机和信用卡也实现了类似的目的。这是身份识别的“物质”方法。(这个问题从简单的身份识别扩展延伸到了几个其他的安全领域。)
您的特性是什么?一般来说,用相对不变的物理特性作为标识符( 生物测定学)是最古老的标识符形式;它来自于语言前的社会。举例说明:在新生儿学会说话以前(或甚至在学会集中视线或理解所有权的概念之前),它就能通过声音来认出母亲了 -- 这是个身份识别的“生物”方法。在技术方面,生物测定学是种相对新型的身份识别的方法。尽管,要使其便于运用需做大量工作,因为它可能是身份识别最可靠的途径了 -- 要改变或伪造物理特性异常困难。
在所有繁多的形式与格式中,这三个问题能协助您检验人们的身份是否达到了所期望的任何级别的可信度。特定的问题或者提出的问题和询问的方式决定了骗过系统的难度以及辨别身份的可靠性。
让我们来仔细研究每个问题。
密码是…
口令、暗示、暗号和密码。所有这些都用来回答:“您知道些什么?”由于这个问题必须公开,因此使得它最常与安全联系起来。
在共享知识的基础上用许多方式向用户询问:
最普通的方法就是让用户重复共享内容,无论是一个口令、一个秘密短语还是其他某些隐藏的信息。
接下来最常用的方法是不应答不含有秘密知识的任何请求。
另一个方法是让受保护的实体本身的存在成为共享的机密。
这些问题中的每一个都有自己的长处和缺点;在不同情况下,每个都可能是适用的。
简单询问
对用户的简单询问是知识性询问的最常用方法。它的简单和显而易见保证了没有人会试图访问受保护的实体,而不知道它确实是受限制的;它同时减轻了创建和维护系统的工作人员的负担。
这种方法并不能很好地抵御攻击。别有用心的用户能监视通讯过程,以期捕获输入应答信息的其他用户,尝试任意应答直到起作用,或者用其他各种技术来猜测或获取合法的应答。
因此,在一个敏感的系统中,这决不应该作为唯一的身份识别方法。其他的身份识别方法,包括其他的知识性询问方法,应该用作二级认证。虽然普遍存在的口令提示是这种知识性询问的最好示例,但还有其他选项,包括一些在安全系统之间的 Diffie-Hellman 密钥一致排序期间提出的问题。
拒绝应答
拒绝应答任何不包含共享机密的请求是建立知识性询问的另一种常用方法。这种办法牵涉到预先建立请求的格式、来源和/或目标,或一些其它的特性。
如果收到的请求不符合期望的特征,则可以忽略、拒绝,或重新发送要求。商家对商家的交易经常采用这种方法,因为他们各自都能建立稳定的源 IP,并商定固定的和模糊的格式(对人来说是模糊的)。
然而虽说这是个相当有效的知识性询问方式,它仍有一个致命弱点。一旦共享机密被攻击者推断出来,安全漏洞就格外地难以察觉。
秘密信号交换的使用 -- 秘密社团中常用的方式 -- 是此类询问中的一个示例。另一个示例是在许多 B2B 系统中使用受限制的源地址。
隐藏存在物
隐藏受保护实体的存在,通常称为模糊安全法,在物理领域十分常用。此方法建立在这样的基础上,即您若不知道有某样东西的存在就很难找到它。
这是一项有效的技术,然而单有此项技术几乎是不够的。谨慎的设计人员至少要在允许访问受保护的实体前添加一项身份检查,以防止有人无意地进入。在电子系统中,附加的身份识别更为重要,因为 使用自动搜索工具寻找以前的未知区域相对更为常用。
Java 平台上的知识性询问
Java 平台为知识性询问提供了一些有趣的支持,同时也提供了一些有趣的询问。
所提供的支持包括了各种各样的加密功能,这些功能既能产生高度复杂的共享机密,又能在传输中保护那些机密。通过 JNDI API 能获得附加的支持(请参阅参考资料)。该 API 提供了对目录服务的访问权,目录服务是为知识性询问存放共享机密的最有效的地方。
Java 平台也为依赖知识性询问的系统提供了一些问题: Jini 网络倡议(请参阅参考资料)为自动发现网络上的系统提供设备;可移动代码的出现为未遂的窃贼提供了更多的途径;并且大范围的 Java 平台为设备添加了 10 年前难以想象的智能。
所有的这些能力,在解决了以前无法处理的难题的同时也使得阻止攻击者获得共享机密变得更为困难了。
“社会”安全性的总结
知识性询问的每种方法都有长处和短处。而所有的都有着某种共同特性。
知识很难强制性获得,通常需要额外的时间和资源来获得不知道的东西。知识很容易共享 -- 及改变;如果秘密被泄漏了,就能很快加以改变以防止更严重的漏洞出现。
知识性询问的最大弱点是知识容易被复制。机密会在漏洞不为人知、且不损害任何人的情况下被攻击者获取。在这种情况下,攻击者可能在一段时期内访问系统而并不为人所察觉。知识性询问还特别容易受人为因素影响,这些人为因素会大大降低系统安全性。
由于以上原因,我建议对于任何有重要价值的系统都至少采取一个附加的身份识别方法。在这种情况下最简单、最常用的方法是向用户询问有关他所拥有的东西。
插入锁孔,转动
钥匙、身份证件、信用卡 -- 所有这些物体都能用来回答这样一个问题“您有些什么?”
知识性询问既巧妙又有效,但它们受到一些严重缺点的困扰。标记询问能有效地弥补这些问题。
标记询问的定义特征是拥有某些物理对象。最常用的标记是一个简单密匙。
密匙的构造设计使得任何特定的锁对于正确密匙以外的密匙几乎不可能作出应答。这是个要点 -- 标记询问是一项可能性练习。您必须在标记的成本和复杂性与标记被复制或与其他非法标记相匹配的复杂性中作出平衡。
现在有多种可用的标记,但其中的每一个标记都在一到二个规则上运行 -- 基本储存或活动应答.
基本标记
基本标记 -- 也叫做哑标记 -- 是最常用的标记类型之一。从门钥匙到信用卡的所有东西都被归入这个类别。它们的核心属性就是都简单地以某种形式编码数据。
密匙用脊或齿的模式编码数据;信用卡将它编码在磁条上。这种简易性非常有价值,因为它使得这些标记变得便宜(通常远低于 1 美元),这将它们稳固地安置在了大众市场领域。由于基本标记仅编码数据,所以另一方面,他们易受赝品和回避技术的影响。
对于一个技术熟练的专业人员来说,除了最复杂的锁,要避开其他锁都是件简单的事,而一旦知道了信用卡的帐号,要进入就更容易了。您若需要防止这类的滥用,就需要更为坚固的标记。
活动标记
活动标记 -- 又叫做智能标记 -- 比基本标记要复杂得多。活动标记集成了一些内嵌智能形式。它们更难仿造,并且比基本标记更难以避开。
智能标记能通过经常改变一些所提供的无用价值(通常称为nonce)来验证其合法性,这样虽然不易于复制,但便于检验。
密码智能卡,公共密匙基本设备中常用的东西,能通过对 nonce 作数字签名的方法来达到目的。这使得受保护的系统能验证该标记是否其声称的那个实体。由于活动标记常利用极其独特的内部特性来修改 nonce -- 且由于多数活动标记在其封装的完整性受损失会自毁 -- 因此活动标记很难伪造。
由于活动标记时常变换 nonce,所以也极难避开,特别是当 nonce 随着每个询问变化时。这个难以避开的特点也就是为什么如今的一些豪华型汽车把活动标记作为点火开关的内嵌程序以防车贼。如果点火开关无法得到发动机计算机的正确身份识别,发动机就不会运转。
活动标记的一个有趣的特性就是它们能永久性地安装在服务器中。只要这样的一个标记安装完毕,它就能利用其辨认人们身份的有用功能一样来在商家对商家的交易中区别此系统与其它系统。
Java 平台上的所有权询问
Java 平台或许比其他平台提供了更多对所有权询问的支持。这种支持的最重要部分体现在 JavaCard 的规范中。
JavaCard 规范描述了一个智能卡中的 Java 技术平台。这种对智能卡的完全支持使得 Java 开发者们能够将相同熟悉平台中大多数可用的有效标记作为其企业应用程序来使用。
从 Java 平台得到的更多支持是由 Java 加密系统扩展版 (JCE) 、开放卡结构中的 Java 技术以及 Jini 网络科技(请参阅参考资料)提供的,用来支持卡设备的自动发现。所有主要读卡机生产厂商,连同多数智能卡生产厂商都提供对 Java 平台和 JavaCard 技术(请参阅参考资料)的支持。提供如此广泛支持的原因是 JavaCard 技术的强大功能和多应用程序灵活性以及它在保护高危险性系统中的相应优点。
“物质”安全性的总结
所有的标记询问都有一个主要问题。它们只检验用户是否拥有相关的标记。因为一个别有用心的用户完全有可能获得另一个用户的标记,因此仍有可能认错用户。
这个问题由于活动标记防止伪造或复制的特性以及时常加入的知识性询问而得到一定的缓解,但是,问题还是存在的。
如能保证拥有标记的个人事实上就标记所分配的那个唯一的个体,那么这将是个不同凡响的安全措施。 ID 卡、美国驾驶执照和一部分信用卡用照片来实现这项功能。
尽管,计算机需要的不只是一张照片,新型的生物测定学还是提供了一个解决方案。
显示出来!
指纹、声纹、虹膜样式、脸部几何形状、视网膜扫描 -- 所有这些都是回答“您的特性是什么?”的方法。
生物测定学是测量个体独特的物理特性的科学。这些技术为实现梦寐以求的安全水平带来了希望 -- 一个比人们互相辨认更为准确的计算机系统。这个理想状态尚未实现,但如今可用的系统能利用物理特性询问戏剧性地增强安全性。
从生物到数字
进行物理特性询问有不同的方法,但所有这些询问的共同思路都是将原始的测量 -- 常为脸部图像、指纹的纹路、视网膜中的毛细血管向量 -- 简化为数字模板。然后系统就试图在储存着的模板中寻找与请求者相匹配的模板,直到找到匹配模板或者所有模板都检查完毕。
平衡行为
每个生物测量系统都具有两种有关无法拒绝冒名者及无法辨认用户的可能性概率。这些被称为 误接受率 (FAR) 和 误拒绝率 (FRR) 的特征必须得到平衡,以满足解决方案的可用性和安全性需要。
误接受率说明了系统错误认出某人并允许其以不同身份进行访问的发生频率。误拒绝率说明了系统无法辨认用户并拒绝有合法访问系统权利的用户进入的发生频率。
许多系统允许这两项值的平衡,两者相对。通过调整系统相对内部储存的模板进行检查的仔细程度, FAR 能有所减少,同时 FRR 会有相应的增加;或者 FAR 有所增加以减少 FRR 的发生。该调整系统灵敏度的能力非常重要,因为 FRR 比例高会降低系统的可用性;而 FAR 的比例高会降低安全性。如果 FRR 和 FAR 不够,那么唯一的选择通常是挑选一个其它特性。
正确的生物测定学
信不信由你,一度被认为独特的生物特性可能并不独特。每个特性都会在一定人数内发生一定程度的重现。
相对更为独特的生物测定学之一为脸部的几何形状。然而,大多数人曾经发现有两个人的脸部非常相似,以至于很难区分他们;能构成人脸的构造方式是有限的。
每个个体的特性对于一定数量的人来说可能是相同的。据了解,一个人的指纹在每 100,000 人里是独特的。虹膜的式样可能在每 1 千万人里是独特的。
在选择生物测定学作为系统安全标准时,必须把实际特性的独特性考虑在内。
特性询问中的 Java 技术
在特性询问中明显没有用到 Java 技术。生物测量技术相对新、并且未经试验的特性正是一部分原因所在。另一个原因是生物测量技术供应商尚需确定一个与生物测量技术硬件实现互动的标准方法,这在 Java 平台提供一个语言接口以前是十分必要的。
这种情况下有着一线亮光,这就是,许多供应商提供了一套以最小的困难实现与其设备交互的 Java 种类,那些设备允许创建用于 Java 应用程序的生物测定学身份认证服务。
“生物”安全性的总结
特性询问通常被视为三种主要身份识别的方法中最准确、最可靠的方法。但它们并不是完美的。如果您看到过这样的间谍片,片中的坏蛋(也有时是好人)为了骗过安全系统而砍下一只手或剜出一只眼睛的话,您就会同意生物测定学系统也会受骗。(事实上,有些指纹系统用一张高质量的照片就能骗过。)
所以,建议安全系统设计人员在使用任何安全措施时都能考虑在成本、人为因素和系统操作允许的情况下尽可能多地运用附加询问。
如果用户不是人
我曾关注过每个询问的类型,通常都假设需要身份识别的用户是人,而不是其它计算机系统。情况不总是这样的。
计算机同样可以通过不安全的链接同其他计算机进行通讯。当辨认其它计算机系统时,使用强大的安全措施的自由度相对较大,因为人的因素被排除了。
计算机系统能够:
记忆知识性询问的最复杂答案
具有永久安装的、格外复杂的活动标记
拥有能通过某种网络连接测试到的独特物理特性
这些独特特性可以包括网络收发器的相位频率特性或它们内部晶体振荡器的频率变化。(并且,尽管这些特性听起来非常严格,每个特性在用来对人们身份识别时都具有相同的优势和弱点。)
对计算机系统进行身份认证时的最大优势是,系统个数不多意味着您能使用更为复杂的技术而不必为单位成本作太多考虑。
另一个优势就是计算机永远都不会抱怨其可用性,因此能没有顾虑地提出多个问题,还能调整特征询问,将 FAR 最大化而不用害怕因此产生的高 FRR 会导致用户停止使用该系统。
三者一致
在处理高危险性系统时,单个身份询问或单个询问类型通常都不够安全。如果系统对安全性的需要超过了单个询问类型所能提供的安全性,就有必要使用多种询问。图 1 对各种安全措施进行了排列。
图 1. 所有方法由优到劣的排列
两个最常用的多询问双重保护都含有知识性询问 -- 使用了特性询问和标记询问。(为什么知识因素会成为普遍思路?很简单 -- 无论开展、执行还是使用都最为方便。)这两项结合确实改进了系统安全,然而它们仍不是最安全的。
最安全的结合是使用所有三种询问。其次的就是将标记和特性询问结合起来,它需将特性储存在活动标记中并使标记在进行进入安全系统的身份认证前先验证特性;在对身份认证询问的应答中加入内部生物测定学模板能使其更为安全。用一个标记来对生物测定学进行身份认证在如今的活动标记中仍未成为可能,但它在未来的 2 代产品中会逐渐成为可能。
最后的(关键)字
有些用户无法为所有的询问类型提供答案。如果让您的系统供尽可能多的合法用户访问很重要的话,那么使用多个询问而只需答对一个问题的方法也就很重要。
例如,有人因没有手指而无法回答指纹询问。对于这些人来说,明智的做法是允许进行标记询问,这样他们仍旧能够使用此系统。有些用户因为如受伤或年轻的原因无法记住太复杂的共享机密。对于这些用户来说,特性或标记询问就能使他们不用记忆复杂的共享机密而同样能使用该系统。
关键的概念是设计者必须考虑到那些无法使用经选择的系统的人,然后决定究竟是系统的安全重要还是那些人访问系统的需要重要,或者是否需要为他们建立另一种询问类型。
使用 Java 2 平台的身份认证
Java 2 平台为访问各种可用的身份识别的方法提供了最为全面一套 API。 Java 平台更为和各种身份认证系统的交互提供了一个服务 API,即 Java 身份认证和授权服务 (JAAS) API。用这个 API,您能作为服务供应商执行高安全性的身份认证,并允许 JAAS 为希望使用那种身份认证方法的应用程序提供通用接口。
Java 密码系统扩充版提供了许多方法以便使用知识性询问在产生共享机密的同时降低那些机密为攻击者所获取的机率。
JavaCard 标准版提供了 API 以及一个特别为在活动标记中运行所设计的环境,同时使设计人员能在一个活动标记中为多安全系统同时提供身份辨认、信息和其他各种服务。
它们还不是些支持特性询问的 API,但大多数生物测定学供应商提供 Java 语言接口。
系统安全问题是由发现通讯连接另一端的用户身份开始的。在本文中, Joseph Sinclair 讨论了三种常见的识别用户的方法,突出了它们的长处和短处(各自的及合并的),并分别提供了一些示例。
砰,砰,砰,“谁在那儿?”
这是一个在所有安全行为中占核心地位的简单问题。对于要求访问您敏感而珍贵的数据的用户,不确定他们的身份就无法让您放心。系统的安全性是以建立那种信任的系统能力为依据的。
如何确定信任谁?由于世界上确有不道德的人存在,您又如何确定发出请求的人是否如他们自己所说的那样呢?
跟着我们来讨论三种确定用户身份的方法吧 -- 其中两种是老的(在计算机业术语中)、“可信赖的”朋友,另外一个则建立在新技术的基础上。
Java 身份认证与授权服务
Java 平台的以用户为中心的安全措施与 Java 身份认证与授权服务 API(JAAS) 是同时推出的。使用 Java 技术创建有效的身份认证系统是从这个重要的 API 开始的。
JAAS 提供了一个服务统一接口,其方式与 JMS 为消息传递服务提供统一 API 的方式或 JDBC 为各种数据库驱动器提供统一 API 的方式相同。由于 JAAS 是一个 API,各种服务供应商能自由地使用任何可用技术来执行那个 API。
JAAS 是一个可插入、可叠放的 API,这意味着在系统配置期间能够添加、组合和修改身份认证服务,而无须在系统执行期间实行硬件编码。这一方法允许系统的身份认证架构根据需要进行修改和完善,并不用对系统的身份认证架构进行内部重建或修改。因此,如果分析表明系统需要加强安全措施,那么知识性问题将被提升为所有权问题,而它还能进一步提升为特性问题。但这一切只在必要时,且必要性变得明显的时候才实施。
除了身份认证 API 以外, JAAS 还提供了一个授权 API。 JAAS 通过使用户(JAAS 说法称作为“主体”)保留一个或多个身份的方法提供授权支持,其中的每个身份都能列在资源的访问控制表 (ACL) 中。这样,系统就能检查主体的身份以及资源的 ACL 了。如果找到匹配信息,就允许指定的访问。如果未能找到匹配信息,就能拒绝访问。
JAAS 还通过证书接口提供了单独注册能力。证书可能含有多种附加信息对象,包括代表性的安全上下文信息或诸如 Kerberos 或 Public Key Infrastructures 等外部系统的访问证书。证书允许用户通过 JAAS 供应者得到身份认证,并同时得到访问多种企业资源的身份认证。
我强烈建议任何企业级的 Java 开发人员研究一下完整的 JAAS 规范(请参阅参考资料)并发掘那些已经在他们厂商的平台上的可用解决方案。对应用程序安全使用 JAAS 接口能显著减小实现有效安全层所需的努力,并同时增加系统的安全性。
建立身份
乍看起来,建立一个请求者的身份非常简单。它是建立在回答以下三个基本问题中的一个或几个的基础上的:
您知道些什么?知识是进入安全系统中的传统密匙。普遍存在的用户名和密码提示是最为普通的身份问题;即使是计算机间通讯时,通常也是从验证一些对以前达成共识信息的知识开始的。这个身份识别的 密码短语方法远比计算机历史久远 -- 它是身份识别的“社会”方法。
您有些什么?较低级的密匙,从古时候起就是安全措施的基础,是一种 所有权问题的形式。任何人拥有钥匙的人都能打开那个锁。现在, ATM 机和信用卡也实现了类似的目的。这是身份识别的“物质”方法。(这个问题从简单的身份识别扩展延伸到了几个其他的安全领域。)
您的特性是什么?一般来说,用相对不变的物理特性作为标识符( 生物测定学)是最古老的标识符形式;它来自于语言前的社会。举例说明:在新生儿学会说话以前(或甚至在学会集中视线或理解所有权的概念之前),它就能通过声音来认出母亲了 -- 这是个身份识别的“生物”方法。在技术方面,生物测定学是种相对新型的身份识别的方法。尽管,要使其便于运用需做大量工作,因为它可能是身份识别最可靠的途径了 -- 要改变或伪造物理特性异常困难。
在所有繁多的形式与格式中,这三个问题能协助您检验人们的身份是否达到了所期望的任何级别的可信度。特定的问题或者提出的问题和询问的方式决定了骗过系统的难度以及辨别身份的可靠性。
让我们来仔细研究每个问题。
密码是…
口令、暗示、暗号和密码。所有这些都用来回答:“您知道些什么?”由于这个问题必须公开,因此使得它最常与安全联系起来。
在共享知识的基础上用许多方式向用户询问:
最普通的方法就是让用户重复共享内容,无论是一个口令、一个秘密短语还是其他某些隐藏的信息。
接下来最常用的方法是不应答不含有秘密知识的任何请求。
另一个方法是让受保护的实体本身的存在成为共享的机密。
这些问题中的每一个都有自己的长处和缺点;在不同情况下,每个都可能是适用的。
简单询问
对用户的简单询问是知识性询问的最常用方法。它的简单和显而易见保证了没有人会试图访问受保护的实体,而不知道它确实是受限制的;它同时减轻了创建和维护系统的工作人员的负担。
这种方法并不能很好地抵御攻击。别有用心的用户能监视通讯过程,以期捕获输入应答信息的其他用户,尝试任意应答直到起作用,或者用其他各种技术来猜测或获取合法的应答。
因此,在一个敏感的系统中,这决不应该作为唯一的身份识别方法。其他的身份识别方法,包括其他的知识性询问方法,应该用作二级认证。虽然普遍存在的口令提示是这种知识性询问的最好示例,但还有其他选项,包括一些在安全系统之间的 Diffie-Hellman 密钥一致排序期间提出的问题。
拒绝应答
拒绝应答任何不包含共享机密的请求是建立知识性询问的另一种常用方法。这种办法牵涉到预先建立请求的格式、来源和/或目标,或一些其它的特性。
如果收到的请求不符合期望的特征,则可以忽略、拒绝,或重新发送要求。商家对商家的交易经常采用这种方法,因为他们各自都能建立稳定的源 IP,并商定固定的和模糊的格式(对人来说是模糊的)。
然而虽说这是个相当有效的知识性询问方式,它仍有一个致命弱点。一旦共享机密被攻击者推断出来,安全漏洞就格外地难以察觉。
秘密信号交换的使用 -- 秘密社团中常用的方式 -- 是此类询问中的一个示例。另一个示例是在许多 B2B 系统中使用受限制的源地址。
隐藏存在物
隐藏受保护实体的存在,通常称为模糊安全法,在物理领域十分常用。此方法建立在这样的基础上,即您若不知道有某样东西的存在就很难找到它。
这是一项有效的技术,然而单有此项技术几乎是不够的。谨慎的设计人员至少要在允许访问受保护的实体前添加一项身份检查,以防止有人无意地进入。在电子系统中,附加的身份识别更为重要,因为 使用自动搜索工具寻找以前的未知区域相对更为常用。
Java 平台上的知识性询问
Java 平台为知识性询问提供了一些有趣的支持,同时也提供了一些有趣的询问。
所提供的支持包括了各种各样的加密功能,这些功能既能产生高度复杂的共享机密,又能在传输中保护那些机密。通过 JNDI API 能获得附加的支持(请参阅参考资料)。该 API 提供了对目录服务的访问权,目录服务是为知识性询问存放共享机密的最有效的地方。
Java 平台也为依赖知识性询问的系统提供了一些问题: Jini 网络倡议(请参阅参考资料)为自动发现网络上的系统提供设备;可移动代码的出现为未遂的窃贼提供了更多的途径;并且大范围的 Java 平台为设备添加了 10 年前难以想象的智能。
所有的这些能力,在解决了以前无法处理的难题的同时也使得阻止攻击者获得共享机密变得更为困难了。
“社会”安全性的总结
知识性询问的每种方法都有长处和短处。而所有的都有着某种共同特性。
知识很难强制性获得,通常需要额外的时间和资源来获得不知道的东西。知识很容易共享 -- 及改变;如果秘密被泄漏了,就能很快加以改变以防止更严重的漏洞出现。
知识性询问的最大弱点是知识容易被复制。机密会在漏洞不为人知、且不损害任何人的情况下被攻击者获取。在这种情况下,攻击者可能在一段时期内访问系统而并不为人所察觉。知识性询问还特别容易受人为因素影响,这些人为因素会大大降低系统安全性。
由于以上原因,我建议对于任何有重要价值的系统都至少采取一个附加的身份识别方法。在这种情况下最简单、最常用的方法是向用户询问有关他所拥有的东西。
插入锁孔,转动
钥匙、身份证件、信用卡 -- 所有这些物体都能用来回答这样一个问题“您有些什么?”
知识性询问既巧妙又有效,但它们受到一些严重缺点的困扰。标记询问能有效地弥补这些问题。
标记询问的定义特征是拥有某些物理对象。最常用的标记是一个简单密匙。
密匙的构造设计使得任何特定的锁对于正确密匙以外的密匙几乎不可能作出应答。这是个要点 -- 标记询问是一项可能性练习。您必须在标记的成本和复杂性与标记被复制或与其他非法标记相匹配的复杂性中作出平衡。
现在有多种可用的标记,但其中的每一个标记都在一到二个规则上运行 -- 基本储存或活动应答.
基本标记
基本标记 -- 也叫做哑标记 -- 是最常用的标记类型之一。从门钥匙到信用卡的所有东西都被归入这个类别。它们的核心属性就是都简单地以某种形式编码数据。
密匙用脊或齿的模式编码数据;信用卡将它编码在磁条上。这种简易性非常有价值,因为它使得这些标记变得便宜(通常远低于 1 美元),这将它们稳固地安置在了大众市场领域。由于基本标记仅编码数据,所以另一方面,他们易受赝品和回避技术的影响。
对于一个技术熟练的专业人员来说,除了最复杂的锁,要避开其他锁都是件简单的事,而一旦知道了信用卡的帐号,要进入就更容易了。您若需要防止这类的滥用,就需要更为坚固的标记。
活动标记
活动标记 -- 又叫做智能标记 -- 比基本标记要复杂得多。活动标记集成了一些内嵌智能形式。它们更难仿造,并且比基本标记更难以避开。
智能标记能通过经常改变一些所提供的无用价值(通常称为nonce)来验证其合法性,这样虽然不易于复制,但便于检验。
密码智能卡,公共密匙基本设备中常用的东西,能通过对 nonce 作数字签名的方法来达到目的。这使得受保护的系统能验证该标记是否其声称的那个实体。由于活动标记常利用极其独特的内部特性来修改 nonce -- 且由于多数活动标记在其封装的完整性受损失会自毁 -- 因此活动标记很难伪造。
由于活动标记时常变换 nonce,所以也极难避开,特别是当 nonce 随着每个询问变化时。这个难以避开的特点也就是为什么如今的一些豪华型汽车把活动标记作为点火开关的内嵌程序以防车贼。如果点火开关无法得到发动机计算机的正确身份识别,发动机就不会运转。
活动标记的一个有趣的特性就是它们能永久性地安装在服务器中。只要这样的一个标记安装完毕,它就能利用其辨认人们身份的有用功能一样来在商家对商家的交易中区别此系统与其它系统。
Java 平台上的所有权询问
Java 平台或许比其他平台提供了更多对所有权询问的支持。这种支持的最重要部分体现在 JavaCard 的规范中。
JavaCard 规范描述了一个智能卡中的 Java 技术平台。这种对智能卡的完全支持使得 Java 开发者们能够将相同熟悉平台中大多数可用的有效标记作为其企业应用程序来使用。
从 Java 平台得到的更多支持是由 Java 加密系统扩展版 (JCE) 、开放卡结构中的 Java 技术以及 Jini 网络科技(请参阅参考资料)提供的,用来支持卡设备的自动发现。所有主要读卡机生产厂商,连同多数智能卡生产厂商都提供对 Java 平台和 JavaCard 技术(请参阅参考资料)的支持。提供如此广泛支持的原因是 JavaCard 技术的强大功能和多应用程序灵活性以及它在保护高危险性系统中的相应优点。
“物质”安全性的总结
所有的标记询问都有一个主要问题。它们只检验用户是否拥有相关的标记。因为一个别有用心的用户完全有可能获得另一个用户的标记,因此仍有可能认错用户。
这个问题由于活动标记防止伪造或复制的特性以及时常加入的知识性询问而得到一定的缓解,但是,问题还是存在的。
如能保证拥有标记的个人事实上就标记所分配的那个唯一的个体,那么这将是个不同凡响的安全措施。 ID 卡、美国驾驶执照和一部分信用卡用照片来实现这项功能。
尽管,计算机需要的不只是一张照片,新型的生物测定学还是提供了一个解决方案。
显示出来!
指纹、声纹、虹膜样式、脸部几何形状、视网膜扫描 -- 所有这些都是回答“您的特性是什么?”的方法。
生物测定学是测量个体独特的物理特性的科学。这些技术为实现梦寐以求的安全水平带来了希望 -- 一个比人们互相辨认更为准确的计算机系统。这个理想状态尚未实现,但如今可用的系统能利用物理特性询问戏剧性地增强安全性。
从生物到数字
进行物理特性询问有不同的方法,但所有这些询问的共同思路都是将原始的测量 -- 常为脸部图像、指纹的纹路、视网膜中的毛细血管向量 -- 简化为数字模板。然后系统就试图在储存着的模板中寻找与请求者相匹配的模板,直到找到匹配模板或者所有模板都检查完毕。
平衡行为
每个生物测量系统都具有两种有关无法拒绝冒名者及无法辨认用户的可能性概率。这些被称为 误接受率 (FAR) 和 误拒绝率 (FRR) 的特征必须得到平衡,以满足解决方案的可用性和安全性需要。
误接受率说明了系统错误认出某人并允许其以不同身份进行访问的发生频率。误拒绝率说明了系统无法辨认用户并拒绝有合法访问系统权利的用户进入的发生频率。
许多系统允许这两项值的平衡,两者相对。通过调整系统相对内部储存的模板进行检查的仔细程度, FAR 能有所减少,同时 FRR 会有相应的增加;或者 FAR 有所增加以减少 FRR 的发生。该调整系统灵敏度的能力非常重要,因为 FRR 比例高会降低系统的可用性;而 FAR 的比例高会降低安全性。如果 FRR 和 FAR 不够,那么唯一的选择通常是挑选一个其它特性。
正确的生物测定学
信不信由你,一度被认为独特的生物特性可能并不独特。每个特性都会在一定人数内发生一定程度的重现。
相对更为独特的生物测定学之一为脸部的几何形状。然而,大多数人曾经发现有两个人的脸部非常相似,以至于很难区分他们;能构成人脸的构造方式是有限的。
每个个体的特性对于一定数量的人来说可能是相同的。据了解,一个人的指纹在每 100,000 人里是独特的。虹膜的式样可能在每 1 千万人里是独特的。
在选择生物测定学作为系统安全标准时,必须把实际特性的独特性考虑在内。
特性询问中的 Java 技术
在特性询问中明显没有用到 Java 技术。生物测量技术相对新、并且未经试验的特性正是一部分原因所在。另一个原因是生物测量技术供应商尚需确定一个与生物测量技术硬件实现互动的标准方法,这在 Java 平台提供一个语言接口以前是十分必要的。
这种情况下有着一线亮光,这就是,许多供应商提供了一套以最小的困难实现与其设备交互的 Java 种类,那些设备允许创建用于 Java 应用程序的生物测定学身份认证服务。
“生物”安全性的总结
特性询问通常被视为三种主要身份识别的方法中最准确、最可靠的方法。但它们并不是完美的。如果您看到过这样的间谍片,片中的坏蛋(也有时是好人)为了骗过安全系统而砍下一只手或剜出一只眼睛的话,您就会同意生物测定学系统也会受骗。(事实上,有些指纹系统用一张高质量的照片就能骗过。)
所以,建议安全系统设计人员在使用任何安全措施时都能考虑在成本、人为因素和系统操作允许的情况下尽可能多地运用附加询问。
如果用户不是人
我曾关注过每个询问的类型,通常都假设需要身份识别的用户是人,而不是其它计算机系统。情况不总是这样的。
计算机同样可以通过不安全的链接同其他计算机进行通讯。当辨认其它计算机系统时,使用强大的安全措施的自由度相对较大,因为人的因素被排除了。
计算机系统能够:
记忆知识性询问的最复杂答案
具有永久安装的、格外复杂的活动标记
拥有能通过某种网络连接测试到的独特物理特性
这些独特特性可以包括网络收发器的相位频率特性或它们内部晶体振荡器的频率变化。(并且,尽管这些特性听起来非常严格,每个特性在用来对人们身份识别时都具有相同的优势和弱点。)
对计算机系统进行身份认证时的最大优势是,系统个数不多意味着您能使用更为复杂的技术而不必为单位成本作太多考虑。
另一个优势就是计算机永远都不会抱怨其可用性,因此能没有顾虑地提出多个问题,还能调整特征询问,将 FAR 最大化而不用害怕因此产生的高 FRR 会导致用户停止使用该系统。
三者一致
在处理高危险性系统时,单个身份询问或单个询问类型通常都不够安全。如果系统对安全性的需要超过了单个询问类型所能提供的安全性,就有必要使用多种询问。图 1 对各种安全措施进行了排列。
图 1. 所有方法由优到劣的排列
两个最常用的多询问双重保护都含有知识性询问 -- 使用了特性询问和标记询问。(为什么知识因素会成为普遍思路?很简单 -- 无论开展、执行还是使用都最为方便。)这两项结合确实改进了系统安全,然而它们仍不是最安全的。
最安全的结合是使用所有三种询问。其次的就是将标记和特性询问结合起来,它需将特性储存在活动标记中并使标记在进行进入安全系统的身份认证前先验证特性;在对身份认证询问的应答中加入内部生物测定学模板能使其更为安全。用一个标记来对生物测定学进行身份认证在如今的活动标记中仍未成为可能,但它在未来的 2 代产品中会逐渐成为可能。
最后的(关键)字
有些用户无法为所有的询问类型提供答案。如果让您的系统供尽可能多的合法用户访问很重要的话,那么使用多个询问而只需答对一个问题的方法也就很重要。
例如,有人因没有手指而无法回答指纹询问。对于这些人来说,明智的做法是允许进行标记询问,这样他们仍旧能够使用此系统。有些用户因为如受伤或年轻的原因无法记住太复杂的共享机密。对于这些用户来说,特性或标记询问就能使他们不用记忆复杂的共享机密而同样能使用该系统。
关键的概念是设计者必须考虑到那些无法使用经选择的系统的人,然后决定究竟是系统的安全重要还是那些人访问系统的需要重要,或者是否需要为他们建立另一种询问类型。
使用 Java 2 平台的身份认证
Java 2 平台为访问各种可用的身份识别的方法提供了最为全面一套 API。 Java 平台更为和各种身份认证系统的交互提供了一个服务 API,即 Java 身份认证和授权服务 (JAAS) API。用这个 API,您能作为服务供应商执行高安全性的身份认证,并允许 JAAS 为希望使用那种身份认证方法的应用程序提供通用接口。
Java 密码系统扩充版提供了许多方法以便使用知识性询问在产生共享机密的同时降低那些机密为攻击者所获取的机率。
JavaCard 标准版提供了 API 以及一个特别为在活动标记中运行所设计的环境,同时使设计人员能在一个活动标记中为多安全系统同时提供身份辨认、信息和其他各种服务。
它们还不是些支持特性询问的 API,但大多数生物测定学供应商提供 Java 语言接口。
用 JAAS 和 JSSE 实现Java 安全性
J用 JAAS 和 JSSE 实现Java 安全性
对 J2EE Web 应用程序安全体系结构的简要介绍
级别:入门
Kyle Gabhart (kyle@gabhart.com)
顾问,Gabhart Communications
2003 年 12 月
在 J2EE 探索者 的这期文章中,企业 Java 开发人员和教育家 Kyle Gabhart 介绍了 Java 认证和授权服务(Java Authentication and Authorization Service,JAAS)和 Java 安全套接字扩展(Java Secure Socket Extension,JSSE)。跟随作者去发现如何结合这两个 API 以提供 J2EE Web 应用程序安全框架的核心功能:认证、授权和传输层安全。请在本文对应的 讨论论坛 上与作者及其他读者分享您对本文的心得(您也可以通过单击文章顶部或底部的讨论来访问该论坛)。
从早期所谓的 Java 沙箱到 JDK 1.4 引入的健壮的、全功能的安全体系结构,安全性一直是 Java 平台的基本组件。从那时到现在,Java 语言的设计者收到了来自团体的大量关于安全的 Java 应用程序(或者企业环境)可以做什么和不做什么的意见,他们自己也添加了若干技巧。
可以说随着 J2EE Web 应用程序安全体系结构的引入,我们不断从近 10 年的反复试验有所收获,事实也表明了这一点。J2EE 安全框架由三个 API 组成:Java 认证和授权服务(JAAS)、Java 安全套接字扩展(JSSE)和 Java 加密扩展(Java Cryptography Extension,JCE)。虽然 JCE 是一个有意思和重要的 API,但是它与我们所关注的安全 Web 应用程序开发的“三大项”??认证、授权和传输??并不特别相关。所以在本月的专栏中我们将集中讲述 JAAS 和 JSSE。
JAAS 和 JSSE 概述
JAAS 提供了一种灵活的、说明性的机制,用于对用户进行认证并验证他们访问安全资源的能力。JSSE 定义了通过安全套接字层(SSL)进行安全 Web 通信的一种全 Java 的机制。通过结合这两种技术,可以使我们的应用程序:
验证用户就是他或者她所宣称的那个人(认证)。
保证允许他或者她访问所要求的资源(授权)。
通过安全网络连接进行完整的信息交换(传输)。
现在,我们来看每一个基础的功能组件。
用 JAAS 进行认证
JAAS 建立在一种称为可插入的认证模块(Pluggable Authentication Module,PAM)的安全体系结构之上。PAM 的体系结构是 模块化 的,这意味着它设计为可以通过交换模块,支持从一个安全协议组件无缝地转换到另一个协议组件。这个框架中定义良好的接口使得无需改变或者干扰任何现有的登录服务就可以加入多种认证技术和授权机制。PAM 体系结构可以集成范围广泛的认证技术,包括 RSA、DCE、Kerberos 以及 S/Key,因而 JAAS 也可以集成这些技术。此外,这个框架与基于智能卡的认证系统和 LDAP 认证兼容。
就像许多 Java 2 平台技术一样,JAAS API 定义了应用程序代码与将要执行业务逻辑的物理实现之间干净的抽象。这个抽象层不用重新编译现有的应用程序代码就可以作为登录模块的运行时替代。特别是,应用程序写到 LoginContext API,而认证技术提供程序则写到 LoginModule 接口。在运行时,LoginContext 将读取配置文件以确定应使用哪一个(一些)登录模块对访问特定应用程序的用户进行认证。
JAAS 所使用的认证方案以两种非常重要的实体为基础:principal 和 subject。实际被认证的人或者服务称为 subject。principal 是一个惟一的实体,比如个人或者组的名字、帐号、社会安全号或者类似的惟一标识。为了惟一标识一个 subject(这是认证的关键部分),一个或者多个 principal 必须与这个 subject 相关联。最后,一个 subject 可能拥有安全相关的属性,称为 凭证(credential)。凭证可以是从简单的密码到复杂的加密密钥的任何东西。
应用程序通过实例化一个 LoginContext 对象开始认证过程。LoginContext 查询一个配置文件以确定进行认证所使用的一种(或者多种)认证技术以及相应的一个(或者多个)LoginModule。一个非常简单的 LoginModule 可能会提示输入用户名和密码并对它们进行验证。高级一点的可能会使用现有的操作系统登录身份进行身份验证。理论上,甚至可以将一个 JAAS LoginModule 构建成与指纹识别器或者虹膜扫描仪交互。
用 JAAS 进行授权
认证只是 Java 安全框架任务的一半。当用户的身份被确认后,必须对他或者她的访问权限进行检查。只有确认了适当的权限后,用户才可以访问安全的系统或者资源。
换一种说法,验证了用户或者服务的身份后,就创建一个 Subject 对象来表示经过验证的实体。然后 JAAS 将这个对象传递给任何为保护对敏感系统或资源的访问而建立的授权组件。
要确定授权,可以向 Java 2 Security Manager 提供 Subject 及其 Principals,以及 Subject 要执行的特权操作(读/写到文件系统、数据库访问,等等)。Security Manager 会咨询与 Principals 和权限相关联的策略文件。 如果一个 Subject 的 Principals 具有执行指定操作的权限,那么就对这个 Subject 授权并允许操作,否则就会拒绝这项操作并抛出一个 SecurityException。
补充安全方面的知识
要全面、实际地了解 Java 安全模型,请参阅 Brad Rubin 的两部分教程:
Java安全性第一部分 密码学基础
Java安全性 第二部分 认证与授权
用 JSSE 进行安全传输
有了 JAAS ,我们就可以识别访问系统的用户并限制他们只能访问授权使用的那部分系统。虽然 JAAS 是迈向安全 Web 应用程序坚实的第一步,但是如果没有安全传输,那么应用程序安全性仍然是不完整的。
这里,我们仍然是以明文形式??即 HTTP、TCP/IP、FTP等??传递安全信息(包括认证信息)。所以我们需要保证数据在传输时不会被未授权的人访问。我们还需要保证数据在到达之前,没有在传输过程中修改过,不管这种修改是有意的还是无意的。我们可以利用安全套接字层(SSL)和传输层安全性(Transport Layer Security,TLS)协议实现这两种功能。
SSL 和 TLS 不是特定于 Java 的协议,它们是为维护通过套接字的数据的完整性和私密性而设计的网络层协议。Java 安全套接字扩展(JSSE)利用 SSL/TLS 可以进行安全的 Internet 通信,它提供了一个具有完整功能的应用程序框架??一个 Java 版本的 SSL 和 TLS 协议,这些功能包括数据加密、服务器认证、消息完整性,等等。使用 JSSE,我们可以定义运行任意应用程序协议??包括 HTTP、TCP/IP、FTP,甚至 Telnet??的客户机与服务器之间的安全套接字连接。从数据加密的角度看,JSSE 结合了许多与 JCE 中使用的同样的概念和算法。不过更重要的是,在简单流套接字 API 背后,它会在必要时自动使用它们。
要利用 JSSE API,我们只需要做简单的几件事。首先我们需要获得 JSSE 提供程序(请参阅 参考资料)。其次,我们需要从一个 JSSE 套接字工厂而不是直接从 java.net.Socket 类获得套接字。客户端代码从 SSLSocketFactory 获取套接字,而服务器端代码从 SSLServerSocketFactory 获取套接字。通过从这些工厂获取套接字,我们就可以利用 JSSE 提供程序提供的框架,而不是像 java.net 包允许我们所作的那样,简单地创建标准的、不安全的套接字。
有关 JSSE 的更多细节,请参阅 参考资料。
对 J2EE Web 应用程序安全体系结构的简要介绍
级别:入门
Kyle Gabhart (kyle@gabhart.com)
顾问,Gabhart Communications
2003 年 12 月
在 J2EE 探索者 的这期文章中,企业 Java 开发人员和教育家 Kyle Gabhart 介绍了 Java 认证和授权服务(Java Authentication and Authorization Service,JAAS)和 Java 安全套接字扩展(Java Secure Socket Extension,JSSE)。跟随作者去发现如何结合这两个 API 以提供 J2EE Web 应用程序安全框架的核心功能:认证、授权和传输层安全。请在本文对应的 讨论论坛 上与作者及其他读者分享您对本文的心得(您也可以通过单击文章顶部或底部的讨论来访问该论坛)。
从早期所谓的 Java 沙箱到 JDK 1.4 引入的健壮的、全功能的安全体系结构,安全性一直是 Java 平台的基本组件。从那时到现在,Java 语言的设计者收到了来自团体的大量关于安全的 Java 应用程序(或者企业环境)可以做什么和不做什么的意见,他们自己也添加了若干技巧。
可以说随着 J2EE Web 应用程序安全体系结构的引入,我们不断从近 10 年的反复试验有所收获,事实也表明了这一点。J2EE 安全框架由三个 API 组成:Java 认证和授权服务(JAAS)、Java 安全套接字扩展(JSSE)和 Java 加密扩展(Java Cryptography Extension,JCE)。虽然 JCE 是一个有意思和重要的 API,但是它与我们所关注的安全 Web 应用程序开发的“三大项”??认证、授权和传输??并不特别相关。所以在本月的专栏中我们将集中讲述 JAAS 和 JSSE。
JAAS 和 JSSE 概述
JAAS 提供了一种灵活的、说明性的机制,用于对用户进行认证并验证他们访问安全资源的能力。JSSE 定义了通过安全套接字层(SSL)进行安全 Web 通信的一种全 Java 的机制。通过结合这两种技术,可以使我们的应用程序:
验证用户就是他或者她所宣称的那个人(认证)。
保证允许他或者她访问所要求的资源(授权)。
通过安全网络连接进行完整的信息交换(传输)。
现在,我们来看每一个基础的功能组件。
用 JAAS 进行认证
JAAS 建立在一种称为可插入的认证模块(Pluggable Authentication Module,PAM)的安全体系结构之上。PAM 的体系结构是 模块化 的,这意味着它设计为可以通过交换模块,支持从一个安全协议组件无缝地转换到另一个协议组件。这个框架中定义良好的接口使得无需改变或者干扰任何现有的登录服务就可以加入多种认证技术和授权机制。PAM 体系结构可以集成范围广泛的认证技术,包括 RSA、DCE、Kerberos 以及 S/Key,因而 JAAS 也可以集成这些技术。此外,这个框架与基于智能卡的认证系统和 LDAP 认证兼容。
就像许多 Java 2 平台技术一样,JAAS API 定义了应用程序代码与将要执行业务逻辑的物理实现之间干净的抽象。这个抽象层不用重新编译现有的应用程序代码就可以作为登录模块的运行时替代。特别是,应用程序写到 LoginContext API,而认证技术提供程序则写到 LoginModule 接口。在运行时,LoginContext 将读取配置文件以确定应使用哪一个(一些)登录模块对访问特定应用程序的用户进行认证。
JAAS 所使用的认证方案以两种非常重要的实体为基础:principal 和 subject。实际被认证的人或者服务称为 subject。principal 是一个惟一的实体,比如个人或者组的名字、帐号、社会安全号或者类似的惟一标识。为了惟一标识一个 subject(这是认证的关键部分),一个或者多个 principal 必须与这个 subject 相关联。最后,一个 subject 可能拥有安全相关的属性,称为 凭证(credential)。凭证可以是从简单的密码到复杂的加密密钥的任何东西。
应用程序通过实例化一个 LoginContext 对象开始认证过程。LoginContext 查询一个配置文件以确定进行认证所使用的一种(或者多种)认证技术以及相应的一个(或者多个)LoginModule。一个非常简单的 LoginModule 可能会提示输入用户名和密码并对它们进行验证。高级一点的可能会使用现有的操作系统登录身份进行身份验证。理论上,甚至可以将一个 JAAS LoginModule 构建成与指纹识别器或者虹膜扫描仪交互。
用 JAAS 进行授权
认证只是 Java 安全框架任务的一半。当用户的身份被确认后,必须对他或者她的访问权限进行检查。只有确认了适当的权限后,用户才可以访问安全的系统或者资源。
换一种说法,验证了用户或者服务的身份后,就创建一个 Subject 对象来表示经过验证的实体。然后 JAAS 将这个对象传递给任何为保护对敏感系统或资源的访问而建立的授权组件。
要确定授权,可以向 Java 2 Security Manager 提供 Subject 及其 Principals,以及 Subject 要执行的特权操作(读/写到文件系统、数据库访问,等等)。Security Manager 会咨询与 Principals 和权限相关联的策略文件。 如果一个 Subject 的 Principals 具有执行指定操作的权限,那么就对这个 Subject 授权并允许操作,否则就会拒绝这项操作并抛出一个 SecurityException。
补充安全方面的知识
要全面、实际地了解 Java 安全模型,请参阅 Brad Rubin 的两部分教程:
Java安全性第一部分 密码学基础
Java安全性 第二部分 认证与授权
用 JSSE 进行安全传输
有了 JAAS ,我们就可以识别访问系统的用户并限制他们只能访问授权使用的那部分系统。虽然 JAAS 是迈向安全 Web 应用程序坚实的第一步,但是如果没有安全传输,那么应用程序安全性仍然是不完整的。
这里,我们仍然是以明文形式??即 HTTP、TCP/IP、FTP等??传递安全信息(包括认证信息)。所以我们需要保证数据在传输时不会被未授权的人访问。我们还需要保证数据在到达之前,没有在传输过程中修改过,不管这种修改是有意的还是无意的。我们可以利用安全套接字层(SSL)和传输层安全性(Transport Layer Security,TLS)协议实现这两种功能。
SSL 和 TLS 不是特定于 Java 的协议,它们是为维护通过套接字的数据的完整性和私密性而设计的网络层协议。Java 安全套接字扩展(JSSE)利用 SSL/TLS 可以进行安全的 Internet 通信,它提供了一个具有完整功能的应用程序框架??一个 Java 版本的 SSL 和 TLS 协议,这些功能包括数据加密、服务器认证、消息完整性,等等。使用 JSSE,我们可以定义运行任意应用程序协议??包括 HTTP、TCP/IP、FTP,甚至 Telnet??的客户机与服务器之间的安全套接字连接。从数据加密的角度看,JSSE 结合了许多与 JCE 中使用的同样的概念和算法。不过更重要的是,在简单流套接字 API 背后,它会在必要时自动使用它们。
要利用 JSSE API,我们只需要做简单的几件事。首先我们需要获得 JSSE 提供程序(请参阅 参考资料)。其次,我们需要从一个 JSSE 套接字工厂而不是直接从 java.net.Socket 类获得套接字。客户端代码从 SSLSocketFactory 获取套接字,而服务器端代码从 SSLServerSocketFactory 获取套接字。通过从这些工厂获取套接字,我们就可以利用 JSSE 提供程序提供的框架,而不是像 java.net 包允许我们所作的那样,简单地创建标准的、不安全的套接字。
有关 JSSE 的更多细节,请参阅 参考资料。
JBOSS 使用JAAS框架验证用户(存储在数据库中)
login-config.xml:
<application-policy name="EJBSecurityDomain">
<authentication>
<login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag="required">
<module-option name="dsJndiName">java:/MySqlDS</module-option>
<module-option name-"principalsQuery">Select passwd from Users where username = ?<module-option>
<module-option name="rolesQuery">Select userRoles 'Role', userRoleGroups 'RoleGroup' from UserRoles where username = ?<module-option>
<login-module>
</authentication>
</application-policy>
<application-policy name="EJBSecurityDomain">
<authentication>
<login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag="required">
<module-option name="dsJndiName">java:/MySqlDS</module-option>
<module-option name-"principalsQuery">Select passwd from Users where username = ?<module-option>
<module-option name="rolesQuery">Select userRoles 'Role', userRoleGroups 'RoleGroup' from UserRoles where username = ?<module-option>
<login-module>
</authentication>
</application-policy>
JBOSS使用JAAS框架来验证用户代码及配置(2)
JavaRanch Big Moose Saloon: JAAS With JBOSS: My How-To Tutorial
//THE IMPORTS
import org.jboss.security.auth.callback.SecurityAssociationHandler;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import java.security.Principal;
//LOGIN CODE
//Note: Login before you get initial context
try{
SecurityAssociationHandler handler = new SecurityAssociationHandler();
//PRINCIPAL TO LOGIN WITH
/*
* To make this work, you'll need to create a user with this name
* in your security file or database or whatever you use. I use a database
* to store them. For this example, we'll use user="Robert", password="Paris"
*
* Note: I am creating a new Principal subclass here because Principal is
* an abstract class.
*/
Principal userPrincipal = new Principal()
{ //THIS IS ALL YOU NEED TO IMPLEMENT
public String getName()
{ return "Robert";
}
};
//SET SECURITY ASSOCIATION HANDLER-SPECIFIC SETTINGS
//Syntax: setSecurityInfo( Principal, char[] );
handler.setSecurityinfo( userPrincipal, "Paris".toCharArray() );
//GET LOGIN CONTEXT (NOTE: EJBSecurityDomain is the name I gave it in descriptor)
LoginContext loginContext = new LoginContext( "EJBSecurityDomain", ( CallbackHandler ) handler );
//LOGIN
loginContext.login();
}catch (Exception e)
{ e.printStackTrace();}
//THEN DO ALL YOUR getInitialContext STUFF HERE
//THE IMPORTS
import org.jboss.security.auth.callback.SecurityAssociationHandler;
import javax.security.auth.callback.*;
import javax.security.auth.login.*;
import java.security.Principal;
//LOGIN CODE
//Note: Login before you get initial context
try{
SecurityAssociationHandler handler = new SecurityAssociationHandler();
//PRINCIPAL TO LOGIN WITH
/*
* To make this work, you'll need to create a user with this name
* in your security file or database or whatever you use. I use a database
* to store them. For this example, we'll use user="Robert", password="Paris"
*
* Note: I am creating a new Principal subclass here because Principal is
* an abstract class.
*/
Principal userPrincipal = new Principal()
{ //THIS IS ALL YOU NEED TO IMPLEMENT
public String getName()
{ return "Robert";
}
};
//SET SECURITY ASSOCIATION HANDLER-SPECIFIC SETTINGS
//Syntax: setSecurityInfo( Principal
handler.setSecurityinfo( userPrincipal, "Paris".toCharArray() );
//GET LOGIN CONTEXT (NOTE: EJBSecurityDomain is the name I gave it in descriptor)
LoginContext loginContext = new LoginContext( "EJBSecurityDomain", ( CallbackHandler ) handler );
//LOGIN
loginContext.login();
}catch (Exception e)
{ e.printStackTrace();}
//THEN DO ALL YOUR getInitialContext STUFF HERE
Java内寸泄漏(1)
- Powered by ymCMS v1.0.1
研究何时应该关注内存漏洞以及如何预防内存漏洞
Java 程序中也有内存漏洞?当然有。与流行的观念相反,在 Java 编程中,内存管理仍然是需要考虑的问题。在本文中,您将了解到什么会导致内存漏洞以及何时应该关注这些漏洞。您还有机会实践一下在您自己的项目中解决漏洞问题。
Java 程序中的内存漏洞是如何显现出来的
大多数程序员都知道,使用像 Java 这样的编程语言的一大好处就是,他们不必再担心内存的分配和释放问题。您只须创建对象,当应用程序不再需要这些对象时,Java 会通过一种称为“垃圾收集”的机制将这些对象删除。这种处理意味着 Java 已经解决了困扰其他编程语言的烦人问题 -- 可怕的内存漏洞。真的是这样的吗?
在深入讨论之前,我们先回顾一下垃圾收集的工作方式。垃圾收集器的工作是发现应用程序不再需要的对象,并在这些对象不再被访问或引用时将它们删除。垃圾收集器从根节点(在 Java 应用程序的整个生存周期内始终存在的那些类)开始,遍历被引用的所有节点进行清除。在它遍历这些节点的同时,它跟踪哪些对象当前正被引用着。任何类只要不再被引用,它就符合垃圾收集的条件。当删除这些对象以后,就可将它们所占用的内存资源返回给 Java 虚拟机 (JVM)。
所以的确是这样,Java 代码不要求程序员负责内存的管理和清除,它会自动对无用的对象执行垃圾收集。但是,我们要紧记的一点是仅当一个对象不再被引用时才会被统计为无用。图 1 说明了这个概念。
http://www.ccidnet.com/tech/guide/2001/04/12//image/01.jpg
图 1. 无用但仍被引用的对象
上面说明了在 Java 应用程序执行期间具有不同生存周期的两个类。类 A 首先被实例化,并会在很长一段时间或程序的整个生存期内存在。在某个时候,类 B 被创建,类 A 添加对这个新创建的类的一个引用。现在,我们假定类 B 是某个用户界面小部件,它由用户显示甚至解除。如果没有清除类 A 对 B 的引用,则即便不再需要类 B,并且即便在执行下一个垃圾收集周期以后,类 B 仍将存在并占用内存空间。
何时应该关注内存漏洞
如果您的程序在执行一段时间以后发出 java.lang.OutOfMemoryError 错误,则内存漏洞肯定是一个重大嫌疑。除了这种明显的情况之外,何时还应该关注内存漏洞呢?持完美主义观点的程序员肯定会回答,应该查找并纠正所有内存漏洞。但是,在得出这个结论之前,还有几个方面需要考虑,包括程序的生存期和漏洞的大小。
完全有这样的可能,垃圾收集器在应用程序的生存期内可能始终不会运行。不能保证 JVM 何时以及是否会调用垃圾收集器 -- 即便程序显式地调用 System.gc() 也是如此。通常,在当前的可用内存能够满足程序的内存需求时,JVM 不会自动运行垃圾收集器。当可用内存不能满足需求时,JVM 将首先尝试通过调用垃圾收集来释放出更多的可用内存。如果这种尝试仍然不能释放足够的资源,JVM 将从*作系统获取更多的内存,直至达到允许的最大极限。
例如,考虑一个小型 Java 应用程序,它显示一些用于修改配置的简单用户界面元素,并且它有一个内存漏洞。很可能到应用程序关闭时也不会调用垃圾收集器,因为 JVM 很可能有足够的内存来创建程序所需的全部对象,而此后可用内存则所剩无几。因此,在这种情况下,即使某些“死”对象在程序执行时占用着内存,它实际上并没有什么用途。
如果正在开发的 Java 代码要全天 24 小时在服务器上运行,则内存漏洞在此处的影响就比在我们的配置实用程序中的影响要大得多。在要长时间运行的某些代码中,即使最小的漏洞也会导致 JVM 耗尽全部可用内存。
在相反的情况下,即便程序的生存期较短,如果存在分配大量临时对象(或者若干吞噬大量内存的对象)的任何 Java 代码,而且当不再需要这些对象时也没有取消对它们的引用,则仍然可能达到内存极限。
最后一种情况是内存漏洞无关紧要。我们不应该认为 Java 内存漏洞像其他语言(如 C++)中的漏洞那样危险,在那些语言中内存将丢失,且永远不会被返回给*作系统。在 Java 应用程序中,我们使不需要的对象依附于*作系统为 JVM 所提供的内存资源。所以从理论上讲,一旦关闭 Java 应用程序及其 JVM,所分配的全部内存将被返回给*作系统。
确定应用程序是否有内存漏洞
为了查看在 Windows NT 平台上运行的某个 Java 应用程序是否有内存漏洞,您可能试图在应用程序运行时观察“任务管理器”中的内存设置。但是,在观察了运行中的几个 Java 应用程序以后,您会发现它们比本地应用程序占用的内存要多得多。我做过的一些 Java 项目要使用 10 到 20 MB 的系统内存才能启动。而*作系统自带的 Windows Explorer 程序只需 5 MB 左右的内存。
在 Java 应用程序内存使用方面应注意的另一点是,这个典型程序在 IBM JDK 1.1.8 JVM 中运行时占用的系统内存越来越多。似乎直到为它分配非常多的物理内存以后它才开始向系统返回内存。这些情况是内存漏洞的征兆吗?
要理解其中的缘由,我们必须熟悉 JVM 如何将系统内存用作它的堆。当运行 java.exe 时,您使用一定的选项来控制垃圾收集堆的起始大小和最大大小(分别用 -ms 和 -mx 表示)。Sun JDK 1.1.8 的默认起始设置为 1 MB,默认最大设置为 16 MB。IBM JDK 1.1.8 的默认最大设置为系统总物理内存大小的一半。这些内存设置对 JVM 在用尽内存时所执行的*作有直接影响。JVM 可能继续增大堆,而不等待一个垃圾收集周期的完成。
这样,为了查找并最终消除内存漏洞,我们需要使用比任务监视实用程序更好的工具。当您试图调试内存漏洞时,内存调试程序(请参阅参考资源)可能派得上用场。这些程序通常会显示堆中的对象数、每个对象的实例数和这些对象所占用的内存等信息。此外,它们也可能提供有用的视图,这些视图可以显示每个对象的引用和引用者,以便您跟踪内存漏洞的来源。
下面我将说明我是如何用 Sitraka Software 的 JProbedebugger 检测和去除内存漏洞的,以使您对这些工具的部署方式以及成功去除漏洞所需的过程有所了解。
内存漏洞的一个示例
本例集中讨论一个问题,我们部门当时正在开发一个商业发行版软件,这是一个 Java JDK 1.1.8 应用程序,一个测试人员花了几个小时研究这个程序才最终使这个问题显现出来。这个 Java 应用程序的基本代码和包是由几个不同的开发小组在不同的时间开发的。我猜想,该应用程序中意外出现的内存漏洞是由那些没有真正理解别人开发的代码的程序员造成的。
我们正在讨论的 Java 代码允许用户为 Palm 个人数字助理创建应用程序,而不必编写任何 Palm OS 本地代码。通过使用图形用户界面,用户可以创建窗体,向窗体中添加控件,然后连接这些控件的事件来创建 Palm 应用程序。测试人员发现,随着不断创建和删除窗体和控件,这个 Java 应用程序最终会耗尽内存。开发人员没有检测到这个问题,因为他们的机器有更多的物理内存。
为了研究这个问题,我用 JProbe 来确定什么地方出了差错。尽管用了 JProbe 所提供的强大工具和内存快照,研究仍然是一个冗长乏味、不断重复的过程,首先要确定出现内存漏洞的原因,然后修改代码,最后还得检验结果。
JProbe 提供几个选项,用来控制调试期间实际记录哪些信息。经过几次试验以后,我断定获取所需信息的最有效方法是,关闭性能数据收集,而将注意力集中在所捕获的堆数据上。JProbe 提供了一个称为 Runtime Heap Summary 的视图,它显示 Java 应用程序运行时所占用的堆内存量随时间的变化。它还提供了一个工具栏按钮,必要时可以强制 JVM 执行垃圾收集。如果您试图弄清楚,当 Java 应用程序不再需要给定的类实例时,这个实例会不会被作为垃圾收集,这个功能将很有用。图 2 显示了使用中的堆存储量随时间的变化。
http://www.ccidnet.com/tech/guide/2001/04/12//image/02-s.jpg
图 2. Runtime Heap Summary
在 Heap Usage Chart 中,蓝色部分表明已分配的堆空间大小。在启动这个 Java 程序并达到稳定状态以后,我强制垃圾收集器运行,在图中的表现就是绿线(这条线表明插入了一个检查点)左侧的蓝线的骤降。随后,我添加了四个窗体,然后又将它们删除,并再次调用了垃圾收集器。当程序返回仅有一个可视窗体的初始状态时,检查点之后的蓝色区域高于检查点之前的蓝色区域这一情况表明可能存在内存漏洞。我通过查看 Instance Summary 证实确实有一个漏洞,因为 Instance Summary 表明 FormFrame 类(它是窗体的主用户界面类)的计数在检查点之后增加了 4。
查找原因
为了将测试人员报告的问题剔出,我采取的第一个步骤是找出几个简单的、可重复的测试案例。就本例而言,我发现只须添加一个窗体,将它删除,然后强制执行垃圾收集,结果就会导致与被删除窗体相关联的许多类实例仍然处于活动状态。这个问题在 JProbe 的 Instance Summary 视图中很明显,这个视图统计每个 Java 类在堆中的实例数。
为了查明使垃圾收集器无法正常完成其工作的那些引用,我使用 JProbe 的 Reference Graph(如图 3 所示)来确定哪些类仍然引用着目前未被删除的 FormFrame 类。在调试这个问题时该过程是最复杂的过程之一,因为我发现许多不同的对象仍然引用着这个无用的对象。用来查明究竟是哪个引用者真正造成这个问题的试错过程相当耗时。
在本例中,一个根类(左上角用红色标明的那个类)是问题的发源地。右侧用蓝色突出显示的类处在从最初的 FormFrame 类跟踪而来的路径上。
http://www.ccidnet.com//tech/guide/2001/04/12//image/03-s.jpg
图 3. 在引用图中跟踪内存漏洞
就本例而言,最后查明罪魁祸首是包含一个静态 hashtable 的字体管理器类。通过逆向追踪引用者列表,我发现根节点是用来存储每个窗体所用字体的一个静态 hashtable。各个窗体可被单独放大或缩小,所以这个 hashtable 包含一个具有某个给定窗体的全部字体的 vector。当窗体的大小改变时,就会提取这个字体 vector,并将适当的缩放因子应用于字体大小。
这个字体管理器类的问题是,虽然程序在创建窗体时将字体 vector 存入这个 hashtable 中,但没有提供在删除窗体时删除 vector 的代码。因此,这个静态 hashtable(在应用程序的生存期内一直存在)永远不会删除引用每个窗体的那些键。结果,窗体及其所有关联的类都闲置在内存中。
修正
本问题的一个简单解决方案是在字体管理器类中添加一个方法,以便在用户删除窗体时以适当的键作为参数调用 hashtable 的 remove() 方法。removeKeyFromHashtables() 方法如下所示:
public void removeKeyFromHashtables(GraphCanvas graph) {
if (graph != null) {
viewFontTable.remove(graph); // 删除 hashtable 中的键
// 以预防内存漏洞
}
}
随后,我在 FormFrame 类中添加了一个对此方法的调用。FormFrame 实际上是使用 Swing 的内部框架来实现窗体用户界面的,所以我将对字体管理器的调用添加到当完全关闭内部框架时所调用的方法中,如下所示:
/**
* 当去掉 (dispose) FormFrame 时调用。清除引用以预防内存漏洞。
*/
public void internalFrameClosed(InternalFrameEvent e) {
FontManager.get().removeKeyFromHashtables(canvas);
canvas = null;
setDesktopIcon(null);
}
当作了这些修改以后,我使用调试器证实:当执行相同的测试案例时,与被删除的窗体相关联的对象计数减小。
预防内存漏洞
可以通过观察某些常见问题来预防内存漏洞。Collection 类(如 hashtable 和 vector)常常是出现内存漏洞的地方。当这个类被用 static 关键字声明并且在应用程序的整个生存期中存在时尤其是这样。
另一个常见的问题是,您将一个类注册为事件监听程序,而在不再需要这个类时没有撤销注册。此外,您常常需要在适当的时候将指向其他类的类成员变量设置为 null。
小结
查找内存漏洞的原因可能是一个乏味的过程,更不用说需要专用调试工具的情况了。但是,一旦您熟悉了这些工具以及在跟踪对象引用时进行搜索的模式,您就能够找到内存漏洞。此外,您还会摸索出一些有价值的技巧,这些技巧不仅有助于节约项目的成本,而且使您能够领悟到在以后的项目中应该避免哪些编码方式来预防内存漏洞。
参考资源
在开始查找内存漏洞之前,请先熟悉下列一种调试器:
●Sitraka Software 的 JProbe Profiler withMemory Debugger
●Intuitive System 的 Optimizeit Java Performance Profiler
●Paul Moeller 的 Win32Java Heap Inspector
●IBM alphaWorks 网站上的 Jinsight
Jinsight: A tool for visualizing the execution of Java programs(developerWorks,1999 年 11 月)详细说明了这个实用程序是如何帮助您分析性能和调试代码的。
注:本文讨论的项目是用 JDK 1.1.8 完成的,但 JDK 1.2 引入了一个新包,java.lang.ref,这个包可与垃圾收集器交互。另外,JDK 1.2 还引入了一个 java.util.WeakHashMap 类,可用它来代替传统的 java.util.Hashtable 类。这个类不会阻止垃圾收集器回收键对象。JDK 1.3 的 Solaris、Linux 和 Microsoft Windows 版本引入了 Java HotSpot Client VM,该虚拟机带有一个新的、经过改进的垃圾收集器。
(Jim Patrick
研究何时应该关注内存漏洞以及如何预防内存漏洞
Java 程序中也有内存漏洞?当然有。与流行的观念相反,在 Java 编程中,内存管理仍然是需要考虑的问题。在本文中,您将了解到什么会导致内存漏洞以及何时应该关注这些漏洞。您还有机会实践一下在您自己的项目中解决漏洞问题。
Java 程序中的内存漏洞是如何显现出来的
大多数程序员都知道,使用像 Java 这样的编程语言的一大好处就是,他们不必再担心内存的分配和释放问题。您只须创建对象,当应用程序不再需要这些对象时,Java 会通过一种称为“垃圾收集”的机制将这些对象删除。这种处理意味着 Java 已经解决了困扰其他编程语言的烦人问题 -- 可怕的内存漏洞。真的是这样的吗?
在深入讨论之前,我们先回顾一下垃圾收集的工作方式。垃圾收集器的工作是发现应用程序不再需要的对象,并在这些对象不再被访问或引用时将它们删除。垃圾收集器从根节点(在 Java 应用程序的整个生存周期内始终存在的那些类)开始,遍历被引用的所有节点进行清除。在它遍历这些节点的同时,它跟踪哪些对象当前正被引用着。任何类只要不再被引用,它就符合垃圾收集的条件。当删除这些对象以后,就可将它们所占用的内存资源返回给 Java 虚拟机 (JVM)。
所以的确是这样,Java 代码不要求程序员负责内存的管理和清除,它会自动对无用的对象执行垃圾收集。但是,我们要紧记的一点是仅当一个对象不再被引用时才会被统计为无用。图 1 说明了这个概念。
http://www.ccidnet.com/tech/guide/2001/04/12//image/01.jpg
图 1. 无用但仍被引用的对象
上面说明了在 Java 应用程序执行期间具有不同生存周期的两个类。类 A 首先被实例化,并会在很长一段时间或程序的整个生存期内存在。在某个时候,类 B 被创建,类 A 添加对这个新创建的类的一个引用。现在,我们假定类 B 是某个用户界面小部件,它由用户显示甚至解除。如果没有清除类 A 对 B 的引用,则即便不再需要类 B,并且即便在执行下一个垃圾收集周期以后,类 B 仍将存在并占用内存空间。
何时应该关注内存漏洞
如果您的程序在执行一段时间以后发出 java.lang.OutOfMemoryError 错误,则内存漏洞肯定是一个重大嫌疑。除了这种明显的情况之外,何时还应该关注内存漏洞呢?持完美主义观点的程序员肯定会回答,应该查找并纠正所有内存漏洞。但是,在得出这个结论之前,还有几个方面需要考虑,包括程序的生存期和漏洞的大小。
完全有这样的可能,垃圾收集器在应用程序的生存期内可能始终不会运行。不能保证 JVM 何时以及是否会调用垃圾收集器 -- 即便程序显式地调用 System.gc() 也是如此。通常,在当前的可用内存能够满足程序的内存需求时,JVM 不会自动运行垃圾收集器。当可用内存不能满足需求时,JVM 将首先尝试通过调用垃圾收集来释放出更多的可用内存。如果这种尝试仍然不能释放足够的资源,JVM 将从*作系统获取更多的内存,直至达到允许的最大极限。
例如,考虑一个小型 Java 应用程序,它显示一些用于修改配置的简单用户界面元素,并且它有一个内存漏洞。很可能到应用程序关闭时也不会调用垃圾收集器,因为 JVM 很可能有足够的内存来创建程序所需的全部对象,而此后可用内存则所剩无几。因此,在这种情况下,即使某些“死”对象在程序执行时占用着内存,它实际上并没有什么用途。
如果正在开发的 Java 代码要全天 24 小时在服务器上运行,则内存漏洞在此处的影响就比在我们的配置实用程序中的影响要大得多。在要长时间运行的某些代码中,即使最小的漏洞也会导致 JVM 耗尽全部可用内存。
在相反的情况下,即便程序的生存期较短,如果存在分配大量临时对象(或者若干吞噬大量内存的对象)的任何 Java 代码,而且当不再需要这些对象时也没有取消对它们的引用,则仍然可能达到内存极限。
最后一种情况是内存漏洞无关紧要。我们不应该认为 Java 内存漏洞像其他语言(如 C++)中的漏洞那样危险,在那些语言中内存将丢失,且永远不会被返回给*作系统。在 Java 应用程序中,我们使不需要的对象依附于*作系统为 JVM 所提供的内存资源。所以从理论上讲,一旦关闭 Java 应用程序及其 JVM,所分配的全部内存将被返回给*作系统。
确定应用程序是否有内存漏洞
为了查看在 Windows NT 平台上运行的某个 Java 应用程序是否有内存漏洞,您可能试图在应用程序运行时观察“任务管理器”中的内存设置。但是,在观察了运行中的几个 Java 应用程序以后,您会发现它们比本地应用程序占用的内存要多得多。我做过的一些 Java 项目要使用 10 到 20 MB 的系统内存才能启动。而*作系统自带的 Windows Explorer 程序只需 5 MB 左右的内存。
在 Java 应用程序内存使用方面应注意的另一点是,这个典型程序在 IBM JDK 1.1.8 JVM 中运行时占用的系统内存越来越多。似乎直到为它分配非常多的物理内存以后它才开始向系统返回内存。这些情况是内存漏洞的征兆吗?
要理解其中的缘由,我们必须熟悉 JVM 如何将系统内存用作它的堆。当运行 java.exe 时,您使用一定的选项来控制垃圾收集堆的起始大小和最大大小(分别用 -ms 和 -mx 表示)。Sun JDK 1.1.8 的默认起始设置为 1 MB,默认最大设置为 16 MB。IBM JDK 1.1.8 的默认最大设置为系统总物理内存大小的一半。这些内存设置对 JVM 在用尽内存时所执行的*作有直接影响。JVM 可能继续增大堆,而不等待一个垃圾收集周期的完成。
这样,为了查找并最终消除内存漏洞,我们需要使用比任务监视实用程序更好的工具。当您试图调试内存漏洞时,内存调试程序(请参阅参考资源)可能派得上用场。这些程序通常会显示堆中的对象数、每个对象的实例数和这些对象所占用的内存等信息。此外,它们也可能提供有用的视图,这些视图可以显示每个对象的引用和引用者,以便您跟踪内存漏洞的来源。
下面我将说明我是如何用 Sitraka Software 的 JProbedebugger 检测和去除内存漏洞的,以使您对这些工具的部署方式以及成功去除漏洞所需的过程有所了解。
内存漏洞的一个示例
本例集中讨论一个问题,我们部门当时正在开发一个商业发行版软件,这是一个 Java JDK 1.1.8 应用程序,一个测试人员花了几个小时研究这个程序才最终使这个问题显现出来。这个 Java 应用程序的基本代码和包是由几个不同的开发小组在不同的时间开发的。我猜想,该应用程序中意外出现的内存漏洞是由那些没有真正理解别人开发的代码的程序员造成的。
我们正在讨论的 Java 代码允许用户为 Palm 个人数字助理创建应用程序,而不必编写任何 Palm OS 本地代码。通过使用图形用户界面,用户可以创建窗体,向窗体中添加控件,然后连接这些控件的事件来创建 Palm 应用程序。测试人员发现,随着不断创建和删除窗体和控件,这个 Java 应用程序最终会耗尽内存。开发人员没有检测到这个问题,因为他们的机器有更多的物理内存。
为了研究这个问题,我用 JProbe 来确定什么地方出了差错。尽管用了 JProbe 所提供的强大工具和内存快照,研究仍然是一个冗长乏味、不断重复的过程,首先要确定出现内存漏洞的原因,然后修改代码,最后还得检验结果。
JProbe 提供几个选项,用来控制调试期间实际记录哪些信息。经过几次试验以后,我断定获取所需信息的最有效方法是,关闭性能数据收集,而将注意力集中在所捕获的堆数据上。JProbe 提供了一个称为 Runtime Heap Summary 的视图,它显示 Java 应用程序运行时所占用的堆内存量随时间的变化。它还提供了一个工具栏按钮,必要时可以强制 JVM 执行垃圾收集。如果您试图弄清楚,当 Java 应用程序不再需要给定的类实例时,这个实例会不会被作为垃圾收集,这个功能将很有用。图 2 显示了使用中的堆存储量随时间的变化。
http://www.ccidnet.com/tech/guide/2001/04/12//image/02-s.jpg
图 2. Runtime Heap Summary
在 Heap Usage Chart 中,蓝色部分表明已分配的堆空间大小。在启动这个 Java 程序并达到稳定状态以后,我强制垃圾收集器运行,在图中的表现就是绿线(这条线表明插入了一个检查点)左侧的蓝线的骤降。随后,我添加了四个窗体,然后又将它们删除,并再次调用了垃圾收集器。当程序返回仅有一个可视窗体的初始状态时,检查点之后的蓝色区域高于检查点之前的蓝色区域这一情况表明可能存在内存漏洞。我通过查看 Instance Summary 证实确实有一个漏洞,因为 Instance Summary 表明 FormFrame 类(它是窗体的主用户界面类)的计数在检查点之后增加了 4。
查找原因
为了将测试人员报告的问题剔出,我采取的第一个步骤是找出几个简单的、可重复的测试案例。就本例而言,我发现只须添加一个窗体,将它删除,然后强制执行垃圾收集,结果就会导致与被删除窗体相关联的许多类实例仍然处于活动状态。这个问题在 JProbe 的 Instance Summary 视图中很明显,这个视图统计每个 Java 类在堆中的实例数。
为了查明使垃圾收集器无法正常完成其工作的那些引用,我使用 JProbe 的 Reference Graph(如图 3 所示)来确定哪些类仍然引用着目前未被删除的 FormFrame 类。在调试这个问题时该过程是最复杂的过程之一,因为我发现许多不同的对象仍然引用着这个无用的对象。用来查明究竟是哪个引用者真正造成这个问题的试错过程相当耗时。
在本例中,一个根类(左上角用红色标明的那个类)是问题的发源地。右侧用蓝色突出显示的类处在从最初的 FormFrame 类跟踪而来的路径上。
http://www.ccidnet.com//tech/guide/2001/04/12//image/03-s.jpg
图 3. 在引用图中跟踪内存漏洞
就本例而言,最后查明罪魁祸首是包含一个静态 hashtable 的字体管理器类。通过逆向追踪引用者列表,我发现根节点是用来存储每个窗体所用字体的一个静态 hashtable。各个窗体可被单独放大或缩小,所以这个 hashtable 包含一个具有某个给定窗体的全部字体的 vector。当窗体的大小改变时,就会提取这个字体 vector,并将适当的缩放因子应用于字体大小。
这个字体管理器类的问题是,虽然程序在创建窗体时将字体 vector 存入这个 hashtable 中,但没有提供在删除窗体时删除 vector 的代码。因此,这个静态 hashtable(在应用程序的生存期内一直存在)永远不会删除引用每个窗体的那些键。结果,窗体及其所有关联的类都闲置在内存中。
修正
本问题的一个简单解决方案是在字体管理器类中添加一个方法,以便在用户删除窗体时以适当的键作为参数调用 hashtable 的 remove() 方法。removeKeyFromHashtables() 方法如下所示:
public void removeKeyFromHashtables(GraphCanvas graph) {
if (graph != null) {
viewFontTable.remove(graph); // 删除 hashtable 中的键
// 以预防内存漏洞
}
}
随后,我在 FormFrame 类中添加了一个对此方法的调用。FormFrame 实际上是使用 Swing 的内部框架来实现窗体用户界面的,所以我将对字体管理器的调用添加到当完全关闭内部框架时所调用的方法中,如下所示:
/**
* 当去掉 (dispose) FormFrame 时调用。清除引用以预防内存漏洞。
*/
public void internalFrameClosed(InternalFrameEvent e) {
FontManager.get().removeKeyFromHashtables(canvas);
canvas = null;
setDesktopIcon(null);
}
当作了这些修改以后,我使用调试器证实:当执行相同的测试案例时,与被删除的窗体相关联的对象计数减小。
预防内存漏洞
可以通过观察某些常见问题来预防内存漏洞。Collection 类(如 hashtable 和 vector)常常是出现内存漏洞的地方。当这个类被用 static 关键字声明并且在应用程序的整个生存期中存在时尤其是这样。
另一个常见的问题是,您将一个类注册为事件监听程序,而在不再需要这个类时没有撤销注册。此外,您常常需要在适当的时候将指向其他类的类成员变量设置为 null。
小结
查找内存漏洞的原因可能是一个乏味的过程,更不用说需要专用调试工具的情况了。但是,一旦您熟悉了这些工具以及在跟踪对象引用时进行搜索的模式,您就能够找到内存漏洞。此外,您还会摸索出一些有价值的技巧,这些技巧不仅有助于节约项目的成本,而且使您能够领悟到在以后的项目中应该避免哪些编码方式来预防内存漏洞。
参考资源
在开始查找内存漏洞之前,请先熟悉下列一种调试器:
●Sitraka Software 的 JProbe Profiler withMemory Debugger
●Intuitive System 的 Optimizeit Java Performance Profiler
●Paul Moeller 的 Win32Java Heap Inspector
●IBM alphaWorks 网站上的 Jinsight
Jinsight: A tool for visualizing the execution of Java programs(developerWorks,1999 年 11 月)详细说明了这个实用程序是如何帮助您分析性能和调试代码的。
注:本文讨论的项目是用 JDK 1.1.8 完成的,但 JDK 1.2 引入了一个新包,java.lang.ref,这个包可与垃圾收集器交互。另外,JDK 1.2 还引入了一个 java.util.WeakHashMap 类,可用它来代替传统的 java.util.Hashtable 类。这个类不会阻止垃圾收集器回收键对象。JDK 1.3 的 Solaris、Linux 和 Microsoft Windows 版本引入了 Java HotSpot Client VM,该虚拟机带有一个新的、经过改进的垃圾收集器。
(Jim Patrick
如何在JAAS框架下使用JBOSS和LDAP实现认证(Authenticate)用户的步骤.
Introduction to Securing Web Applications with JBoss and LDAP
Introduction
Goals
After reading this tutorial you should be able to:
Configure JBoss to authenticate users against an LDAP server (or other types of user information repository)
Design pluggable user information repositories using the Abstract Factory and Data Access Object patterns
Control the presentation of a web application with role-based presentation
Protect your EJB methods by confirming calls are authorized
This article gives an introduction to configuring security on JBoss and implementing an LDAP-based user information repository. The sample application described here demonstrates using LDAP with the JBoss Security Extension (JBossSX), and describes a simple API for managing users, roles, and groups in LDAP. In addition, this article demonstrates the use of the Abstract Factory pattern to implement pluggable persistence.
An Abstract Factory is used to decouple the LDAP access classes in order that LDAP can easily be replaced with another type of storage. This is useful if you are building multiple web applications for different clients, some requiring LDAP storage, and others requiring a database.
Requirements
You'll need the following software set up on your machine to run the sample:
LDAP server (eg OpenLDAP)
JBoss
Ant
The sample code has been tested with OpenLdap 2.0.19 built for Windows [2], jboss-3.0.4_tomcat-4.1.12 [8], and Ant 1.5.1 [9] on Windows 2000. For information on exactly how to build and deploy the sample code, please see the included readme file.
Role Based Access Control
According to the principles of Role Based Access Control (RBAC), users are granted membership into roles based on their competencies and responsibilities in the organization. The operations that a user is permitted to perform are based on the user's role. Membership of roles can be easily withdrawn and new memberships established as the organization evolves. Role associations can be established when new operations are added, simplifying the administration and management of privileges; roles can be updated without updating the privileges for every user on an individual basis. The principle of least privilege is fundamental to RBAC; this requires identifying the user's job functions, determining the minimum set of privileges required to perform that function, and restricting the user to a domain with those privileges and nothing more. In less precisely controlled systems, this is often difficult to achieve.
Sample Schema Design
The sample LDAP schema used here is built around the following entities: Users, Groups and Roles. In this approach, Users are assigned to Groups, and Groups are given Roles. The following example shows how this kind of design can be useful: Imagine two users, Jane and Bob. Jane is in the group user_admins, which has the user_management role. This means Jane is able to add and remove users from the system, update passwords, etc. Bob is in the group finance_admins, which has the finance_management role. This means Bob is able to view salary details, update salaries, etc. In this situation the information is kept secure, and is available only to the minimum number of people who need to have access to it (the principle of least privilege outlined above). Jane will not have any access to salary details, and Bob will not be able to accidentally remove users from the system. If it is necessary for one individual to be able to do both jobs, this is also possible; imagine a user called Superman, who is in both the user_admins group and the finance_admins group. This user will have both the finance_management and the user_management roles, and will have access to both types of information. The advantage of including the concept of groups in the schema is that it simplifies updating the access rights of collections of users, without requiring individual updates to each user. An entry for a user in LDIF (LDAP Data Interchange Format) format for the sample schema looks like this:
dn: uid=jbloggs,ou=People,dc=sample,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
sn: Bloggs
cn: Joe
uid: jbloggs
userpassword: no3XJAZeeb9AKbGNY65/masWpZE=
mail: jbloggs@sample.com
The DN (Distinguished Name) entry is equivalent to a unique identifier for this entry; it states exactly where this user is positioned in the directory structure. Graphically, the sample schema looks like this:
Security
JBoss Authentication
The authentication process can be triggered by a call to the standard servlet login mechanism specified in the servlet specification, ie a form submitting to the action j_security_check. The first step is therefore to create a login page, containing a form like this:
<form action="j_security_check" method="post">
Username:
Password:
<input value="Login" />
</form>
The username and password will be intercepted by the JBoss SecurityInterceptor and passed to the JAASSecurityManager class as Principal and Credential objects. It is worth noting here that if a user bookmarks a login page, or uses the browser back button to reach the page, they will see an error. This is a feature of the Tomcat implementation of the j_security_check mechanism. The next step is to set up the web.xml file as follows:
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/login_error.html</form-error-page>
</form-login-config>
</login-config>
Next, the login-config.xml file must be set up to specify that this security domain requires a certain set of LoginModules:
<application-policy name="sample_web_client_security">
<authentication> <login-module code="org.jboss.security.ClientLoginModule"
flag="required"/>
<login-module code="com.sample.security.GenericJbossLoginModule"
flag="required"/>
</authentication>
</application-policy>
Login modules within an application policy are chained . the .flag. value specifies whether they are required to succeed or not. The ClientLoginModule should be specified if you wish to use EJB security - it passes the username and credentials obtained during login to the org.jboss.security.SecurityAssociation class so that each EJB method invocation is associated with the given username and credentials. See below for details on how custom login modules work On login, the JAASSecurityManager will create a LoginContext for the security domain specified and attempt to authenticate the specified Principal by calling the login modules configured for that domain. Internally, this looks something like the following:
String name = getSecurityDomain();
CallbackHandler handler = new org.jboss.security.plugins.SecurityAssociationHandler();
handler.setSecurityInfo(principal, credential);
LoginContext lc = new LoginContext(name, handler);
lc.login();
The security domain should be specified in the jboss-web.xml and jboss.xml files, with a declaration like this:
<security-domain>java:/jaas/security-example</security-domain>
The JAASSecurityManager will then get a a javax.security.Subject from the LoginContext, which will contain a Principal called username and a javax.security.Group called Roles (containing an array of this user's roles) will be created. This is what allows methods like isUserInRole and getCallerPrincipal in the servlet and EJB containers to verify the current user.
Custom Login Modules
JBoss comes with a number of login modules out of the box, including an LDAPLoginModule that can be configured in the login-config.xml file. Developing custom login modules is however very simple, and the sample contains a custom login module for illustrative purposes. Custom login modules should extend the JBoss AbstractServerLoginModule class, overriding the validatePassword and getRoleSets methods. The GenericJbossLoginModule in the sample code delegates to a factory class in the deployed EAR file so it can obtain the list of roles and groups from the current repository, without having to be aware of the type of repository. The password validation itself is contracted out to the UserDAO implementation. In this case, the BaseLdapDAO class validates a user by attempting to connect to LDAP as that user:
props.setProperty(Context.SECURITY_PRINCIPAL,this.getDN(user));
props.setProperty(Context.SECURITY_CREDENTIALS, EncryptUtils.encryptSHA(user.getPassword()));
...
DirContext dir = new InitialDirContext(props);
A more advanced implementation could use a secure authentication mechanism; LDAP v3 supports SASL (Simple Authentication and Security Layer) to allow specifying the exact authorization mechanism, and the type of encryption to use. For additional security, it is also possible to connect to the LDAP server over SSL rather than with plain sockets. Here, we're just encrypting passwords with the SHA-1 one-way hash algorithm before storage (this code is in the EncryptUtils class).
Authorization
In this sample, authorization is implemented on three levels; web-resource protection, role-based presentation, and EJB method-level security.
Web Resource Protection
You can specify protected resources using a security constraint in the web.xml file:
<security-constraint>
<web-resource-collection>
<web-resource-name>Sample Application</web-resource-name>
<description>Require users to authenticate</description>
<url-pattern>*.jsp</url-pattern>
<http-method>POST</http-method>
<http-method>GET</http-method>
</web-resource-collection>
<auth-constraint>
<description>Only allow Authenticated_users role</description>
<role-name>Authenticated_users</role-name>
</auth-constraint>
<user-data-constraint>
<description>Encryption is not required for the application in general. </description>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
In addition to specifying protected URL patterns in the web.xml file, it is also possible to place all JSP files under the web-inf directory and make them accessible only through servlets.
Role-Based Presentation
You can control presentation in a JSP based on a user's role by using the HttpServletRequest.isUserInRole method:
<%if(request.isUserInRole("Member_admin")){%> ...
<%}%> <%else{%> ...
<%}%>
Some tag libraries (eg Struts 1.1) provide tags encapsulating this functionality.
Method-Level Security
The JBoss authentication process will propagate the user's roles to the EJB container, allowing the standard J2EE declarative security to be specified in the ejb-jar.xml file. You first need to establish a <security-role-ref> specifying the local role-name, then link this to the role specified in LDAP (the names have been kept the same here for simplicity):
<security-role-ref>
<role-name>Member_admin</role-name>
<role-link>Member_admin</role-link>
</security-role-ref>
Then, in the assembly descriptor section, use the local name to protect individual methods like this:
<security-role>
<role-name>Member_admin</role-name>
</security-role>
<method-permission>
<role-name>Member_admin</role-name>
<method>
<ejb-name>MemberService</ejb-name>
<method-name>secureMethod</method-name>
</method>
</method-permission>
To test this in action, log in to the sample application as fsmith/fsmith and try submitting directly to the SampleServlet URL (eg http://localhost:8080/security-sample/SampleServlet?method=secure). You should see a stack trace as fsmith is not in the Member_admin role.
Configuration Summary
To summarise, the steps you need to follow to secure a web application are:
Create a login page
Set up the web.xml file
Set up the login-config.xml file
Modify the jboss-web.xml and jboss.xml files to include the security domain
Place any custom login modules in the lib directory
Add security to your EJB methods
Persistence Layer Architecture
Following the preceding steps should give you the foundations for a secure web application on JBoss. The remainder of this article gives a briefly describes a possible architecture for a user information repository. In a production system, you might choose to replace much of the JNDI-specific code here with a dedicated LDAP API, such as JLDAP, or the Netscape Directory API. The example code here uses JNDI for simplicity, but there are some advantages to using a dedicated API. The structure of the persistence layer is as follows:
UserDAO, GroupDAO and RoleDAO Interfaces
These specify the contract that must be implemented by Data Access Objects for a given type of repository; in this example, the implementation classes are LdapUserDAO, LdapGroupDAO and LdapRoleDAO.
Abstract Factory
The DAOFactory class is subclassed to return a concrete factory (LdapDAOFactory in this case), which will be used to retrieve concrete instances of the UserDAO, GroupDAO and RoleDAO instances. The sample EJB is hard-wired to use the LDAP DAO factory class. You can change this to return a different Factory class if necessary.
Session Facade
In this architecture, the UserManagement EJB classes act as a fagade layer in front of the persistence classes . this can be used to coordinate calls to more than one repository. For example, while some user details (eg password) are stored in LDAP, others might be stored in a database (eg customer ID); the fagade layer can take a UserTO object and redirect as appropriate. The Session Fagade is a useful pattern from the perspective of security, as it centralizes security management; it is relatively easy to implement security on the course-grained methods at this level, rather than deeper inside the system where there may be more methods to protect.
LdapConfiguration
This is a singleton class which reads in details of the current LDAP schema from a properties file. The LDAP schema used here is simple, so this class is trivial, but in a more complex system, it might be necessary to read in all of the schema details in order to remain flexible.
Data Transfer Objects
The UserTO, GroupTO, and RoleTO classes are simple value objects used to reduce network traffic and minimise method calls across the network.
LDAP DAO Classes
The BaseLdapDAO class is responsible for common functionality, including methods for creating, updating and removing entries. Functionality specific to Users, Groups and Roles are in the LdapUser, LdapGroup and LdapRole classes. There are detailed tutorials on JNDI elsewhere on the web (see [5] for example), so we will just give a brief overview of some common tasks. For example, inserting an entry into the LDAP repository with JNDI is done with the createSubcontext method. Here, we call the create method on the LdapUserDAO class, passing in the UserTO object we want to insert. This calls the create method on the superclass, BaseLdapDAO; to determine the details of the entity to be added, BaseLdapDAO then calls the getDN and getAttributes methods on the LdapUserDAO:
createSubcontext(getDN(entity), getAttributes(entity));
Similarly, entities are removed with the DirectoryContext.destroySubcontext method, passing in the Distinguished Name (DN) of the entity to be removed:
ctx.destroySubcontext(getDN(entity));
An entry.s attributes can be modified by creating an array of ModificationItems containing the Attribute to be modified:
ModificationItem[] mods = new ModificationItem[1];
Attribute mod = new BasicAttribute("userPassword", newPass);
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod);
...
getInitialLdapContext().modifyAttributes(dn, mods);
This is a just a brief overview of some of the functionality possible when using JNDI with LDAP; the example classes include some more sample code that is not described here.
Summary
To summarise, we first covered a possible design for an LDAP schema that would allow implementation of Role Based Access Control; this schema was comprised of Users, Groups and Roles. We then covered the configuration of JBoss security, including building custom login modules. Finally, we saw a design for the persistence layer of a user information repository, using the Abstract Factory, Session Facade, and Data Access Objects patterns, and looked briefly at some JNDI code for adding, removing and updating entries in LDAP.
References
The OpenLDAP Project (http://www.openldap.org/)
OpenLdap for Windows (http://www.fivesight.com/downloads/openldap.asp)
Sun LDAP tutorial (http://java.sun.com/products/jndi/tutorial/ldap/)
Sun JNDI tutorial (http://java.sun.com/products/jndi/tutorial/)
JLDAP project (http://www.openldap.org/jldap/)
Netscape Directory API (http://www.mozilla.org/directory/javasdk.html)
Core J2EE Patterns Catalogue (http://java.sun.com/blueprints/corej2eepatterns/Patterns/index.html)
The Jakarta Struts Framework (http://jakarta.apache.org/struts/)
JBoss Group (http://www.jboss.org/)
Apache Ant (http://ant.apache.org/)
Downloads
Introduction
Goals
After reading this tutorial you should be able to:
Configure JBoss to authenticate users against an LDAP server (or other types of user information repository)
Design pluggable user information repositories using the Abstract Factory and Data Access Object patterns
Control the presentation of a web application with role-based presentation
Protect your EJB methods by confirming calls are authorized
This article gives an introduction to configuring security on JBoss and implementing an LDAP-based user information repository. The sample application described here demonstrates using LDAP with the JBoss Security Extension (JBossSX), and describes a simple API for managing users, roles, and groups in LDAP. In addition, this article demonstrates the use of the Abstract Factory pattern to implement pluggable persistence.
An Abstract Factory is used to decouple the LDAP access classes in order that LDAP can easily be replaced with another type of storage. This is useful if you are building multiple web applications for different clients, some requiring LDAP storage, and others requiring a database.
Requirements
You'll need the following software set up on your machine to run the sample:
LDAP server (eg OpenLDAP)
JBoss
Ant
The sample code has been tested with OpenLdap 2.0.19 built for Windows [2], jboss-3.0.4_tomcat-4.1.12 [8], and Ant 1.5.1 [9] on Windows 2000. For information on exactly how to build and deploy the sample code, please see the included readme file.
Role Based Access Control
According to the principles of Role Based Access Control (RBAC), users are granted membership into roles based on their competencies and responsibilities in the organization. The operations that a user is permitted to perform are based on the user's role. Membership of roles can be easily withdrawn and new memberships established as the organization evolves. Role associations can be established when new operations are added, simplifying the administration and management of privileges; roles can be updated without updating the privileges for every user on an individual basis. The principle of least privilege is fundamental to RBAC; this requires identifying the user's job functions, determining the minimum set of privileges required to perform that function, and restricting the user to a domain with those privileges and nothing more. In less precisely controlled systems, this is often difficult to achieve.
Sample Schema Design
The sample LDAP schema used here is built around the following entities: Users, Groups and Roles. In this approach, Users are assigned to Groups, and Groups are given Roles. The following example shows how this kind of design can be useful: Imagine two users, Jane and Bob. Jane is in the group user_admins, which has the user_management role. This means Jane is able to add and remove users from the system, update passwords, etc. Bob is in the group finance_admins, which has the finance_management role. This means Bob is able to view salary details, update salaries, etc. In this situation the information is kept secure, and is available only to the minimum number of people who need to have access to it (the principle of least privilege outlined above). Jane will not have any access to salary details, and Bob will not be able to accidentally remove users from the system. If it is necessary for one individual to be able to do both jobs, this is also possible; imagine a user called Superman, who is in both the user_admins group and the finance_admins group. This user will have both the finance_management and the user_management roles, and will have access to both types of information. The advantage of including the concept of groups in the schema is that it simplifies updating the access rights of collections of users, without requiring individual updates to each user. An entry for a user in LDIF (LDAP Data Interchange Format) format for the sample schema looks like this:
dn: uid=jbloggs,ou=People,dc=sample,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
sn: Bloggs
cn: Joe
uid: jbloggs
userpassword: no3XJAZeeb9AKbGNY65/masWpZE=
mail: jbloggs@sample.com
The DN (Distinguished Name) entry is equivalent to a unique identifier for this entry; it states exactly where this user is positioned in the directory structure. Graphically, the sample schema looks like this:
Security
JBoss Authentication
The authentication process can be triggered by a call to the standard servlet login mechanism specified in the servlet specification, ie a form submitting to the action j_security_check. The first step is therefore to create a login page, containing a form like this:
<form action="j_security_check" method="post">
Username:
Password:
<input value="Login" />
</form>
The username and password will be intercepted by the JBoss SecurityInterceptor and passed to the JAASSecurityManager class as Principal and Credential objects. It is worth noting here that if a user bookmarks a login page, or uses the browser back button to reach the page, they will see an error. This is a feature of the Tomcat implementation of the j_security_check mechanism. The next step is to set up the web.xml file as follows:
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/login_error.html</form-error-page>
</form-login-config>
</login-config>
Next, the login-config.xml file must be set up to specify that this security domain requires a certain set of LoginModules:
<application-policy name="sample_web_client_security">
<authentication> <login-module code="org.jboss.security.ClientLoginModule"
flag="required"/>
<login-module code="com.sample.security.GenericJbossLoginModule"
flag="required"/>
</authentication>
</application-policy>
Login modules within an application policy are chained . the .flag. value specifies whether they are required to succeed or not. The ClientLoginModule should be specified if you wish to use EJB security - it passes the username and credentials obtained during login to the org.jboss.security.SecurityAssociation class so that each EJB method invocation is associated with the given username and credentials. See below for details on how custom login modules work On login, the JAASSecurityManager will create a LoginContext for the security domain specified and attempt to authenticate the specified Principal by calling the login modules configured for that domain. Internally, this looks something like the following:
String name = getSecurityDomain();
CallbackHandler handler = new org.jboss.security.plugins.SecurityAssociationHandler();
handler.setSecurityInfo(principal, credential);
LoginContext lc = new LoginContext(name, handler);
lc.login();
The security domain should be specified in the jboss-web.xml and jboss.xml files, with a declaration like this:
<security-domain>java:/jaas/security-example</security-domain>
The JAASSecurityManager will then get a a javax.security.Subject from the LoginContext, which will contain a Principal called username and a javax.security.Group called Roles (containing an array of this user's roles) will be created. This is what allows methods like isUserInRole and getCallerPrincipal in the servlet and EJB containers to verify the current user.
Custom Login Modules
JBoss comes with a number of login modules out of the box, including an LDAPLoginModule that can be configured in the login-config.xml file. Developing custom login modules is however very simple, and the sample contains a custom login module for illustrative purposes. Custom login modules should extend the JBoss AbstractServerLoginModule class, overriding the validatePassword and getRoleSets methods. The GenericJbossLoginModule in the sample code delegates to a factory class in the deployed EAR file so it can obtain the list of roles and groups from the current repository, without having to be aware of the type of repository. The password validation itself is contracted out to the UserDAO implementation. In this case, the BaseLdapDAO class validates a user by attempting to connect to LDAP as that user:
props.setProperty(Context.SECURITY_PRINCIPAL,this.getDN(user));
props.setProperty(Context.SECURITY_CREDENTIALS, EncryptUtils.encryptSHA(user.getPassword()));
...
DirContext dir = new InitialDirContext(props);
A more advanced implementation could use a secure authentication mechanism; LDAP v3 supports SASL (Simple Authentication and Security Layer) to allow specifying the exact authorization mechanism, and the type of encryption to use. For additional security, it is also possible to connect to the LDAP server over SSL rather than with plain sockets. Here, we're just encrypting passwords with the SHA-1 one-way hash algorithm before storage (this code is in the EncryptUtils class).
Authorization
In this sample, authorization is implemented on three levels; web-resource protection, role-based presentation, and EJB method-level security.
Web Resource Protection
You can specify protected resources using a security constraint in the web.xml file:
<security-constraint>
<web-resource-collection>
<web-resource-name>Sample Application</web-resource-name>
<description>Require users to authenticate</description>
<url-pattern>*.jsp</url-pattern>
<http-method>POST</http-method>
<http-method>GET</http-method>
</web-resource-collection>
<auth-constraint>
<description>Only allow Authenticated_users role</description>
<role-name>Authenticated_users</role-name>
</auth-constraint>
<user-data-constraint>
<description>Encryption is not required for the application in general. </description>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
In addition to specifying protected URL patterns in the web.xml file, it is also possible to place all JSP files under the web-inf directory and make them accessible only through servlets.
Role-Based Presentation
You can control presentation in a JSP based on a user's role by using the HttpServletRequest.isUserInRole method:
<%if(request.isUserInRole("Member_admin")){%> ...
<%}%> <%else{%> ...
<%}%>
Some tag libraries (eg Struts 1.1) provide tags encapsulating this functionality.
Method-Level Security
The JBoss authentication process will propagate the user's roles to the EJB container, allowing the standard J2EE declarative security to be specified in the ejb-jar.xml file. You first need to establish a <security-role-ref> specifying the local role-name, then link this to the role specified in LDAP (the names have been kept the same here for simplicity):
<security-role-ref>
<role-name>Member_admin</role-name>
<role-link>Member_admin</role-link>
</security-role-ref>
Then, in the assembly descriptor section, use the local name to protect individual methods like this:
<security-role>
<role-name>Member_admin</role-name>
</security-role>
<method-permission>
<role-name>Member_admin</role-name>
<method>
<ejb-name>MemberService</ejb-name>
<method-name>secureMethod</method-name>
</method>
</method-permission>
To test this in action, log in to the sample application as fsmith/fsmith and try submitting directly to the SampleServlet URL (eg http://localhost:8080/security-sample/SampleServlet?method=secure). You should see a stack trace as fsmith is not in the Member_admin role.
Configuration Summary
To summarise, the steps you need to follow to secure a web application are:
Create a login page
Set up the web.xml file
Set up the login-config.xml file
Modify the jboss-web.xml and jboss.xml files to include the security domain
Place any custom login modules in the lib directory
Add security to your EJB methods
Persistence Layer Architecture
Following the preceding steps should give you the foundations for a secure web application on JBoss. The remainder of this article gives a briefly describes a possible architecture for a user information repository. In a production system, you might choose to replace much of the JNDI-specific code here with a dedicated LDAP API, such as JLDAP, or the Netscape Directory API. The example code here uses JNDI for simplicity, but there are some advantages to using a dedicated API. The structure of the persistence layer is as follows:
UserDAO, GroupDAO and RoleDAO Interfaces
These specify the contract that must be implemented by Data Access Objects for a given type of repository; in this example, the implementation classes are LdapUserDAO, LdapGroupDAO and LdapRoleDAO.
Abstract Factory
The DAOFactory class is subclassed to return a concrete factory (LdapDAOFactory in this case), which will be used to retrieve concrete instances of the UserDAO, GroupDAO and RoleDAO instances. The sample EJB is hard-wired to use the LDAP DAO factory class. You can change this to return a different Factory class if necessary.
Session Facade
In this architecture, the UserManagement EJB classes act as a fagade layer in front of the persistence classes . this can be used to coordinate calls to more than one repository. For example, while some user details (eg password) are stored in LDAP, others might be stored in a database (eg customer ID); the fagade layer can take a UserTO object and redirect as appropriate. The Session Fagade is a useful pattern from the perspective of security, as it centralizes security management; it is relatively easy to implement security on the course-grained methods at this level, rather than deeper inside the system where there may be more methods to protect.
LdapConfiguration
This is a singleton class which reads in details of the current LDAP schema from a properties file. The LDAP schema used here is simple, so this class is trivial, but in a more complex system, it might be necessary to read in all of the schema details in order to remain flexible.
Data Transfer Objects
The UserTO, GroupTO, and RoleTO classes are simple value objects used to reduce network traffic and minimise method calls across the network.
LDAP DAO Classes
The BaseLdapDAO class is responsible for common functionality, including methods for creating, updating and removing entries. Functionality specific to Users, Groups and Roles are in the LdapUser, LdapGroup and LdapRole classes. There are detailed tutorials on JNDI elsewhere on the web (see [5] for example), so we will just give a brief overview of some common tasks. For example, inserting an entry into the LDAP repository with JNDI is done with the createSubcontext method. Here, we call the create method on the LdapUserDAO class, passing in the UserTO object we want to insert. This calls the create method on the superclass, BaseLdapDAO; to determine the details of the entity to be added, BaseLdapDAO then calls the getDN and getAttributes methods on the LdapUserDAO:
createSubcontext(getDN(entity), getAttributes(entity));
Similarly, entities are removed with the DirectoryContext.destroySubcontext method, passing in the Distinguished Name (DN) of the entity to be removed:
ctx.destroySubcontext(getDN(entity));
An entry.s attributes can be modified by creating an array of ModificationItems containing the Attribute to be modified:
ModificationItem[] mods = new ModificationItem[1];
Attribute mod = new BasicAttribute("userPassword", newPass);
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod);
...
getInitialLdapContext().modifyAttributes(dn, mods);
This is a just a brief overview of some of the functionality possible when using JNDI with LDAP; the example classes include some more sample code that is not described here.
Summary
To summarise, we first covered a possible design for an LDAP schema that would allow implementation of Role Based Access Control; this schema was comprised of Users, Groups and Roles. We then covered the configuration of JBoss security, including building custom login modules. Finally, we saw a design for the persistence layer of a user information repository, using the Abstract Factory, Session Facade, and Data Access Objects patterns, and looked briefly at some JNDI code for adding, removing and updating entries in LDAP.
References
The OpenLDAP Project (http://www.openldap.org/)
OpenLdap for Windows (http://www.fivesight.com/downloads/openldap.asp)
Sun LDAP tutorial (http://java.sun.com/products/jndi/tutorial/ldap/)
Sun JNDI tutorial (http://java.sun.com/products/jndi/tutorial/)
JLDAP project (http://www.openldap.org/jldap/)
Netscape Directory API (http://www.mozilla.org/directory/javasdk.html)
Core J2EE Patterns Catalogue (http://java.sun.com/blueprints/corej2eepatterns/Patterns/index.html)
The Jakarta Struts Framework (http://jakarta.apache.org/struts/)
JBoss Group (http://www.jboss.org/)
Apache Ant (http://ant.apache.org/)
Downloads
JavaRanch Big Moose Saloon: JAAS With JBOSS: My How-To Tutorial
JavaRanch Big Moose Saloon: JAAS With JBOSS: My How-To Tutorial
There are a few different steps to get JAAS Auth to work in JBoss:
LOGIN CODE
-------------------
code:
--------------------------------------------------------------------------------
//THE IMPORTSimport org.jboss.security.auth.callback.SecurityAssociationHandler;import javax.security.auth.callback.*;import javax.security.auth.login.*;import java.security.Principal;//LOGIN CODE//Note: Login before you get initial contexttry{ SecurityAssociationHandler handler = new SecurityAssociationHandler(); //PRINCIPAL TO LOGIN WITH /* * To make this work, you'll need to create a user with this name * in your security file or database or whatever you use. I use a database * to store them. For this example, we'll use user="Robert", password="Paris" * * Note: I am creating a new Principal subclass here because Principal is * an abstract class. */ Principal userPrincipal = new Principal() { //THIS IS ALL YOU NEED TO IMPLEMENT public String getName() { return "Robert"; } }; //SET SECURITY ASSOCIATION HANDLER-SPECIFIC SETTINGS //Syntax: setSecurityInfo( Principal, char[] ); handler.setSecurityinfo( userPrincipal, "Paris".toCharArray() ); //GET LOGIN CONTEXT (NOTE: EJBSecurityDomain is the name I gave it in descriptor) LoginContext loginContext = new LoginContext( "EJBSecurityDomain", ( CallbackHandler ) handler ); //LOGIN loginContext.login();}catch (Exception e){ e.printStackTrace();}//THEN DO ALL YOUR getInitialContext STUFF HERE...
--------------------------------------------------------------------------------
JBOSS EJB_DESCRIPTOR INFO
---------------------------------------------
code:
--------------------------------------------------------------------------------
//THIS ALL GOES INSIDE jboss.xml IN YOUR jar FILE FOR YOUR ejb's Standard CMP 2.x EntityBean java:/jaas/EJBSecurityDomain A PersonEJB PersonHome Standard CMP 2.x EntityBean
--------------------------------------------------------------------------------
JBOSS CONFIGURATIONS
------------------------------------
code:
--------------------------------------------------------------------------------
//LOGIN FILE: Located in "${JBOSS_HOME}/server//conf/login-config.xml"// the "" is usually "default" as most people use that folder.//THIS GOES UNDER THE tag: java:/MSQLDS Select passwd from Users where username = ? Select userRoles 'Role', userRoleGroups 'RoleGroup' from UserRoles where username = ?
--------------------------------------------------------------------------------
NEEDED TO COMPILE AND RUN CLIENT
---------------------------------------------------------
NOTE: you will need these jars for BOTH compiling AND running THE CLIENT
//ALL CLIENT JARS
${JBOSS_HOME}/client/*.jar
//THIS IS THE KEY!!!! THIS IS THE JAR THEY DON'T TELL YOU
//ABOUT, BUT THAT HAS THE JAAS CLASSES!!!
${JBOSS_HOME}/server/all/lib/jbosssx.jar
RUNTIME SYSTEM PROPERTIES FOR CLIENT - REQUIRED
----------------------------------------------------------------
//NOTE: ALL THESE ARE FOR RUNNING THE CLIENT!!!
-Djava.security.manager
//I WILL GIVE YOU WHAT THIS FILE MUST CONTAIN
-Djava.security.auth.login.config=auth.conf
//I WILL GIVE YOU A SAMPLE OF THIS, JUST FOR TESTING
-Djava.security.policy=ourtest.policy
-Djava.security.auth.policy=ourtest.policy
CONFIG FILE (FOR CLIENT): auth.conf
------------------------------------------------------
code:
--------------------------------------------------------------------------------
srp-client{ //Login Module Needed - I use Database (Note it correlates to what I had in login-config.xml org.jboss.security.auth.spi.DatabaseServerLoginModule required;};//NOT 100% SURE IF THIS IS NEEDED, SINCE I HAVEN'T DONE THIS IN A WHILE AND I FORGET, //BUT IT'S IN MY FILE (Although I think it's not needed)other{ //DEFAULT CLIENT-LOGIN MODULE org.jboss.security.ClientLoginModule required;};
--------------------------------------------------------------------------------
POLICY FILE (FOR CLIENT): ourtest.policy
-----------------------------------------------------------
code:
--------------------------------------------------------------------------------
grant{ permission java.security.AllPermission;};
--------------------------------------------------------------------------------
There are a few different steps to get JAAS Auth to work in JBoss:
LOGIN CODE
-------------------
code:
--------------------------------------------------------------------------------
//THE IMPORTSimport org.jboss.security.auth.callback.SecurityAssociationHandler;import javax.security.auth.callback.*;import javax.security.auth.login.*;import java.security.Principal;//LOGIN CODE//Note: Login before you get initial contexttry{ SecurityAssociationHandler handler = new SecurityAssociationHandler(); //PRINCIPAL TO LOGIN WITH /* * To make this work, you'll need to create a user with this name * in your security file or database or whatever you use. I use a database * to store them. For this example, we'll use user="Robert", password="Paris" * * Note: I am creating a new Principal subclass here because Principal is * an abstract class. */ Principal userPrincipal = new Principal() { //THIS IS ALL YOU NEED TO IMPLEMENT public String getName() { return "Robert"; } }; //SET SECURITY ASSOCIATION HANDLER-SPECIFIC SETTINGS //Syntax: setSecurityInfo( Principal
--------------------------------------------------------------------------------
JBOSS EJB_DESCRIPTOR INFO
---------------------------------------------
code:
--------------------------------------------------------------------------------
//THIS ALL GOES INSIDE jboss.xml IN YOUR jar FILE FOR YOUR ejb's
--------------------------------------------------------------------------------
JBOSS CONFIGURATIONS
------------------------------------
code:
--------------------------------------------------------------------------------
//LOGIN FILE: Located in "${JBOSS_HOME}/server/
--------------------------------------------------------------------------------
NEEDED TO COMPILE AND RUN CLIENT
---------------------------------------------------------
NOTE: you will need these jars for BOTH compiling AND running THE CLIENT
//ALL CLIENT JARS
${JBOSS_HOME}/client/*.jar
//THIS IS THE KEY!!!! THIS IS THE JAR THEY DON'T TELL YOU
//ABOUT, BUT THAT HAS THE JAAS CLASSES!!!
${JBOSS_HOME}/server/all/lib/jbosssx.jar
RUNTIME SYSTEM PROPERTIES FOR CLIENT - REQUIRED
----------------------------------------------------------------
//NOTE: ALL THESE ARE FOR RUNNING THE CLIENT!!!
-Djava.security.manager
//I WILL GIVE YOU WHAT THIS FILE MUST CONTAIN
-Djava.security.auth.login.config=auth.conf
//I WILL GIVE YOU A SAMPLE OF THIS, JUST FOR TESTING
-Djava.security.policy=ourtest.policy
-Djava.security.auth.policy=ourtest.policy
CONFIG FILE (FOR CLIENT): auth.conf
------------------------------------------------------
code:
--------------------------------------------------------------------------------
srp-client{ //Login Module Needed - I use Database (Note it correlates to what I had in login-config.xml org.jboss.security.auth.spi.DatabaseServerLoginModule required;};//NOT 100% SURE IF THIS IS NEEDED, SINCE I HAVEN'T DONE THIS IN A WHILE AND I FORGET, //BUT IT'S IN MY FILE (Although I think it's not needed)other{ //DEFAULT CLIENT-LOGIN MODULE org.jboss.security.ClientLoginModule required;};
--------------------------------------------------------------------------------
POLICY FILE (FOR CLIENT): ourtest.policy
-----------------------------------------------------------
code:
--------------------------------------------------------------------------------
grant{ permission java.security.AllPermission;};
--------------------------------------------------------------------------------
星期一, 二月 23, 2004
Java Serializable 例程
// write.java
//http://java.sun.com/developer/TechTips/1998/tt0217.html
import java.io.*;
public class write {
public static void main(String args[])
{
try {
FileOutputStream fos =
new FileOutputStream("file.out");
ObjectOutputStream oos =
new ObjectOutputStream(fos);
oos.writeObject(new Test("testing", 37));
oos.flush();
fos.close();
}
catch (Throwable e) {
System.err.println(e);
}
}
}
// read.java
import java.io.*;
public class read {
public static void main(String args[])
{
Test testobj = null;
try {
FileInputStream fis =
new FileInputStream("file.out");
ObjectInputStream ois =
new ObjectInputStream(fis);
testobj = (Test)ois.readObject();
fis.close();
}
catch (Throwable e) {
System.err.println(e);
}
System.out.println(testobj.str);
System.out.println(testobj.ivalue);
}
}
// Test.java
public class Test implements java.io.Serializable {
public String str;
public transient int ivalue;
public Test(String s, int i)
{
str = s;
ivalue = i;
}
}
//http://java.sun.com/developer/TechTips/1998/tt0217.html
import java.io.*;
public class write {
public static void main(String args[])
{
try {
FileOutputStream fos =
new FileOutputStream("file.out");
ObjectOutputStream oos =
new ObjectOutputStream(fos);
oos.writeObject(new Test("testing", 37));
oos.flush();
fos.close();
}
catch (Throwable e) {
System.err.println(e);
}
}
}
// read.java
import java.io.*;
public class read {
public static void main(String args[])
{
Test testobj = null;
try {
FileInputStream fis =
new FileInputStream("file.out");
ObjectInputStream ois =
new ObjectInputStream(fis);
testobj = (Test)ois.readObject();
fis.close();
}
catch (Throwable e) {
System.err.println(e);
}
System.out.println(testobj.str);
System.out.println(testobj.ivalue);
}
}
// Test.java
public class Test implements java.io.Serializable {
public String str;
public transient int ivalue;
public Test(String s, int i)
{
str = s;
ivalue = i;
}
}
Java 序列化(Serializeble) 说明.
Java: Serializable
(how to save instances)
Object Serialization.
Java provides a feature called object serialization that allows you take any object that implements the Serializable interface and turns it into a sequence of bytes that can later be fully restored into the original object.
"Objects are serialized with the ObjectOutputStream and they are deserialized with the ObjectInputStream. Both of these classes are part of the java.io package, and they function, in many ways, like DataOutputStream and DataInputStream because they define the same methods for writing and reading binary representations of Java primitive types to and from streams. What ObjectOutputStream and ObjectInputStream add, however, is the ability to write and read non-primitive object and array values to and from a stream." Java in a Nutshell
The ObjectInput and ObjectOutput interfaces are the basis for serialization within Java. The ObjectInputStream and ObjectOutputStream classes, respectively, implement the interfaces.
What Objects need to be serialized?
The basic idea is to save Instance information. Why Instances?
Classes are defined and "saved" in your .java files. However, for instances, they disappear when you exit the application. One might want to save Instance information.
What information might this be?
Well, it won't be methods, since methods are in Classes, not instances. Instances hold Instance Variables and can change Class Variables. Thus, static variables in an Instance can be a problem.. more later
To get an idea of what serialization is doing, let's consider more about the nature of objects. From, "Developing Java Beans" by Robert Englander.
"Most components maintain information that defines their appearance and behavior. This information is known as the state of the object. Some of this information is represented by the object's properties. For instance, the font or color properties of a visual component are usually considered to be part of that object's state. There may also be internal data used by an object that is not exposed as properties, but plays a part in defining the behavior of the object nevertheless.
...The state information of all the components, as well as the application or applet itself, must be saved on a persistent storage medium so that it can be used to recreate the overall application state at run-time. An important aspect of the application state is the definition of the components themselves: the persistent state of an application includes a description of the components being used, as well their collective state."
Note that we use ObjectStreams to save objects. You can only read and write objects not numbers. To write and read numbers, you use methods such as writeInt/readInt or writeDouble/readDouble. (The objectstream classes implement the DataInput/DataOutput interfaces.) (more later on why you would want to do this)
Of course, numbers inside objects (IVs) are saved and stored automatically (discussion on CV storage being static later).
When an object is saved all of its state is saved. This means that all handles and objects that the saved object refers to are saved.In its simplest application the programmer includes the phrase implements java.io.Serializable in the class definition as shown in the following example from "Developing Java Beans."
public class SimpleExample implements java.io.Serializable
{
protected int anInteger;
protected float aFloat;
protected java.awt.Button aButton;
public SimpleExample()
{
}
}
note implements java.io.Serializable could be implements Serializable but need to import java.io.*
In this example there are three data members. The first two, anInteger and aFloat are primitive data types and are therefore serializable. The third, aButton is an instance of type java.awt.Button, a subclass of java.awt.Component which itself implements java.io.Serializable. Therefore the class SimpleExample can be serialized without doing anything more than declaring that it implements java.io.Serializable.
Only classes that implement the Serializable or Externalizable interface can be written to or read from an object stream. Serializable is a marker interface - it doesn't define any methods and serves only to specify whether an object is allowed to be serialized (look in the API ... it is empty!). Read what it says about the readObject and writeObject methods. The Externalizable interface (which extends Serializable) does define methods and is used by objects that want advanced control.
Below is the save() method in the example 8.1 in Java in a Nutshell 2. Note the creation of the ObjectOutputStream and the use of writeObject(). Since it does not implement the writeObject method, it needs to instantiate the ObjectOutputStream explicitly. It is only saving one Object (specificially lines.) (See Java in a Nutshell, page 173 http://www.ecst.csuchico.edu/~amk/foo/javanut2/ch08/ScribbleFrame.java)
/**
* Prompt the user for a filename, and save the scribble in that file.
* Serialize the vector of lines with an ObjectOutputStream.
* Compress the serialized objects with a GZIPOutputStream.
* Write the compressed, serialized data to a file with a FileOutputStream.
* Don't forget to flush and close the stream.
*/
public void save() {
// Create a file dialog to query the user for a filename.
FileDialog f = new FileDialog(frame, "Save Scribble", FileDialog.SAVE);
f.show(); // Display the dialog and block.
String filename = f.getFile(); // Get the user's response
if (filename != null) { // If user didn't click "Cancel".
try {
// Create the necessary output streams to save the scribble.
FileOutputStream fos = new FileOutputStream(filename);
// Save to file
GZIPOutputStream gzos = new GZIPOutputStream(fos);
// Compressed
ObjectOutputStream out = new ObjectOutputStream(gzos);
// Save objects
out.writeObject(lines); // Write the entire Vector of scribbles
out.flush(); // Always flush the output.
out.close(); // And close the stream.
}
// Print out exceptions. We should really display them in a dialog...
catch (IOException e) { System.out.println(e); }
}
}
/**
* Prompt for a filename, and load a scribble from that file.
* Read compressed, serialized data with a FileInputStream.
* Uncompress that data with a GZIPInputStream.
* Deserialize the vector of lines with a ObjectInputStream.
* Replace current data with new data, and redraw everything.
*/
public void load() {
// Create a file dialog to query the user for a filename.
FileDialog f = new FileDialog(frame, "Load Scribble", FileDialog.LOAD);
f.show(); // Display the dialog and block.
String filename = f.getFile(); // Get the user's response
if (filename != null) { // If user didn't click "Cancel".
try {
// Create necessary input streams
FileInputStream fis = new FileInputStream(filename); // Read from
file
GZIPInputStream gzis = new GZIPInputStream(fis); // Uncompress
ObjectInputStream in = new ObjectInputStream(gzis); // Read objects
// Read in an object. It should be a vector of scribbles
Vector newlines = (Vector)in.readObject();
in.close(); // Close the stream.
lines = newlines; // Set the Vector of lines.
repaint(); // And redisplay the scribble.
}
// Print out exceptions. We should really display them in a dialog...
catch (Exception e) { System.out.println(e); }
}
}
Static or transient data
However, this "ease" is not true in all cases. As we shall see, serialization is not so easily applied to classes with static or transient data members.
Only data associated with a specific instance of a class is serialized, therefore static data, that is, data associated with a class as opposed to an instance, is not serialized automatically. To serialize data stored in a static variable one must provide class-specific serialization.
Similarly, some classes may define data members to use as scratch variables. Serializing these data members may be unnecessary. Some examples of transient data include runtime statistics or hash table mapping references. These data should be marked with the transient modifier to avoid serialization. Transient, by definition, is used to designate data members that the programmer does not want or need to be serialized. See Java in a Nutshell, page 174: mouse position, preferred size, file handles (machine specific (native code)).
When writing code if declare transient, then triggers (to programmer) necessity of special code for serialization later. (when writing code if declare transient, then triggers (to programmer) necessity of special code for serialization later)
To serialize an object, you create some sort of OutputStream object and then wrap it inside an ObjectOutputStream object. At this point you only need to call writeObject() and your object is magically serialized and sent to the OutputStream. To reverse the process, you wrap an InputStream inside an ObjectInputStream and call readObject(). What comes back is, as usual, a handle to an upcast Object, so you must downcast to set things straight.
If you need to dynamically query the type of the object, you can use the getClass method. Specifically dk.getClass.getName() returns the name of the class that dk is an instance of. I.e., this asks the object for the name of its corresponding class object. (Hmmm, True, but what about syntax? I still need to know what it is to declare it...too bad) (C++ can do this in one operation (dynamic_cast (gives null if wrong type)), java can use instanceof operator to check if it is what I think (see Core Java, Ch5 Inheritence, Casting section)
Custom Serialization
Note: a class can define custom serialization and deserialization behavior for its objects by implementing writeObject() and readObject() methods. These methods are not defined by any interface. The methods must be declared private (rather surprising since they are called from outside of the class during serialization and deserialization.)
The following example illustrates the serialization process. It is not necessarily what you need to do unless you need to customize; it is what is automatically done for an instances IVs
// save a string and double to the stream
String str = "Sample";
double d = 3.14;
FileOutputStream f = new FileOutputStream ("Beans.tmp")
ObjectOutputStream s = new ObjectOutputStream(f);
s.writeObject(str);
s.writeDouble(d);
s.flush();
// restore the string and double
FileInputStream f = new FileInputStream("Beans.tmp");
ObjectInputStream s = new ObjectInputStream(f);
String str = (String)s.readObject();
double d = s.readDouble();
Note the order: when reading back keep track of the number of objects, their order and their type. In Java, remember that strings and arrays are objects and can, therefore, be restored with the writeObject/readObject methods. (Why need to do this if they are objects? why not automatic? ... IS automatic if the object is serializable and it is an IV of an instance that is serializable... and it is not static or transient)
Another example: (This is Java in a Nutshell, page 175) (show use of transient data)
// This example is from the book "Java in a Nutshell, Second Edition".
// Written by David Flanagan. Copyright (c) 1997 O'Reilly & Associates.
// You may distribute this source code for non-commercial purposes only.
// You may study, modify, and use this example for any purpose, as long
// as this notice is retained. Note that this example is provided "as is",
// WITHOUT WARRANTY of any kind either expressed or implied.
import java.io.*;
/** A simple class that implements a growable array or ints, and knows
* how to serialize itself as efficiently as a non-growable array. */
public class IntList implements Serializable
{
private int[] nums = new int[8]; // An array to store the numbers.
private transient int size = 0; // Index of next unused element of nums[].
/** Return an element of the array */
public int elementAt(int index) throws ArrayIndexOutOfBoundsException
{
if (index >= size) throw new ArrayIndexOutOfBoundsException(index);
else return nums[index];
}
/** Add an int to the array, growing the array if necessary */
public void add(int x) {
if (nums.length == size) resize(nums.length*2); // Grow array, if needed.
nums[size++] = x; // Store the int in it.
}
/** An internal method to change the allocated size of the array */
protected void resize(int newsize) {
int[] oldnums = nums;
nums = new int[newsize]; // Create a new array.
System.arraycopy(oldnums, 0, nums, 0, size); // Copy array elements.
}
/** Get rid of unused array elements before serializing the array */
private void writeObject(ObjectOutputStream out) throws IOException {
if (nums.length > size) resize(size); // Compact the array.
out.defaultWriteObject(); // Then write it out normally.
}
/** Compute the transient size field after deserializing the array */
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject(); // Read the array normally.
size = nums.length; // Restore the transient field.
}
}
What Objects need to implement Serializable?
Component implements Serializable, so all AWT components can be serialized. If a Class is serializable, all of its subclasses are. Otherwise, you need to make your classes implement serializable.
Since in a given application, one probably does not inherit most classes (other than the AWT stuff) you often need to do this explicitly.
Why not serialize everything?
Security. A knowledgable hacker could exploit information and modify object files.
Safeguards: validation, authentication, and/or security measures for keeping private variables "unsaved". (see Core Java V2)
Consider IO, locations of files change depending on the system you are running on. If you want to keep your java classes cross-platform compliant, you should not store things that are machine specific.
Some things can't be serialized. See this example
If you use someone else's serialized classes, and depend on their serialization for yours, if their version ever changes, then your serialized code may not work anymore. I had a nasty example of this with some student's code that used Swing classes that were "provided" serializable. When a given JDK would change anything in Swing, then any serialized code that was built from the previous version would not work. Same source from my end, but the provided java classes changed.
This lesson is a good one. Do not serialize everything but rather make certain things transient and rebuild them when you retrieve the serialized object. Most GUIs would follow this rule
Back to serializing...
So, each Class that you have Instances that will need to be saved should implement Serializable and , possibly have custom readObject() and writeObject() methods.
If a class does not implement the method, the default serialization provided by defaultReadObject() will be used. In custom methods, call the default first.
(The default methods may be called only from a class's read/writeObject methods. If it is called from outside the writeObject method (for example), the NotActiveException is thrown)
(What does this readObject() probably do? Upon (1) reading the object type, (2) instantiates the (blank) object and (3) sets it variables to the values saved)
If you write a save() method for the top Object, then as Java tries to serialize it, it accesses its variables - which are possibly objects that need to be serialized, and performs this recursively. From specs "The writeObject method serializes the specified object and traverses its references to other objects in the object graph recursively to create a complete serialized represetnation of the graph."
As far as subclasses (from Englander): "When an object is serialized, the highest serializable class in its derivation hierachy is located and serialized first. Then the hierachy is walked, with each subclass being serialized in turn."
Specifically, readObject and writeObject methods only need to save and load their data fields; they should not concern themselves with superclass data or any other class information. (except changes to static variables)
Example 1-4 in Core Java (show how saved) http://www.ecst.csuchico.edu/~amk/foo/CoreJava/v2ch1/ObjectFileTest.java On the SUNs at Chico (at least on Expert), the corejava package is at /opt/java/corejava, this would need to be in your CLASSPATH for this code (and other code from the CoreJava book) to run
/*
* Cay S. Horstmann & Gary Cornell, Core Java
* Published By Sun Microsystems Press/Prentice-Hall
* Copyright (C) 1997 Sun Microsystems Inc.
* All Rights Reserved.
*
* Permission to use, copy, modify, and distribute this
* software and its documentation for NON-COMMERCIAL purposes
* and without fee is hereby granted provided that this
* copyright notice appears in all copies.
*
* THE AUTHORS AND PUBLISHER MAKE NO REPRESENTATIONS OR
* WARRANTIES ABOUT THE SUITABILITY OF THE SOFTWARE, EITHER
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE, OR NON-INFRINGEMENT. THE AUTHORS
* AND PUBLISHER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED
* BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
* THIS SOFTWARE OR ITS DERIVATIVES.
*/
/**
* @version 1.01 25 Oct 1997
* @author Cay Horstmann
*/
import java.io.*;
import corejava.Day;
class ObjectFileTest
{ public static void main(String[] args)
{ try
{ Employee[] staff = new Employee[3];
staff[0] = new Employee("Harry Hacker", 35000,
new Day(1989,10,1));
staff[1] = new Manager("Carl Cracker", 75000,
new Day(1987,12,15));
staff[2] = new Employee("Tony Tester", 38000,
new Day(1990,3,15));
ObjectOutputStream out = new ObjectOutputStream(new
FileOutputStream("test1.dat"));
out.writeObject(staff);
out.close();
ObjectInputStream in = new
ObjectInputStream(new FileInputStream("test1.dat"));
Employee[] newStaff = (Employee[])in.readObject();
int i;
for (i = 0; i < newStaff.length; i++)
newStaff[i].raiseSalary(100);
for (i = 0; i < newStaff.length; i++)
newStaff[i].print();
}
catch(Exception e)
{ System.out.print("Error: " + e);
System.exit(1);
}
}
}
class Employee implements Serializable
{ public Employee(String n, double s, Day d)
{ name = n;
salary = s;
hireDay = d;
}
public Employee() {}
public void print()
{ System.out.println(name + " " + salary
+ " " + hireYear());
}
public void raiseSalary(double byPercent)
{ salary *= 1 + byPercent / 100;
}
public int hireYear()
{ return hireDay.getYear();
}
private String name;
private double salary;
private Day hireDay;
}
class Manager extends Employee
{ public Manager(String n, double s, Day d)
{ super(n, s, d);
secretaryName = "";
}
public Manager() {}
public void raiseSalary(double byPercent)
{ // add 1/2% bonus for every year of service
Day today = new Day();
double bonus = 0.5 * (today.getYear() - hireYear());
super.raiseSalary(byPercent + bonus);
}
public void setSecretaryName(String n)
{ secretaryName = n;
}
public String getSecretaryName()
{ return secretaryName;
}
private String secretaryName;
}
Keep in mind that objects may contain references to their variables, not separate copies.
Specifically, consider the example below. Two managers can share the same secretary. One does not want to save three copies of Harry. (One wants to maintain consistent data and not worry about editing numerous copies.)
Employee harry = new Employee("Harry Hacker", ...);
Manager carl = new Manager("Carl Cracker", ...);
carl.setSecretary(harry);
Manager tony = new Manager("Tony Tester", ...);
tony.setSecretary(harry);
See Core Java, chapter on Streams and Files: Object Streams Fig.5, Fig.6, Fig.7, Fig.8
Thus the term serialization...
All objects that are saved are given a serial number (1,2,3...)
When saving an object to disk, find out if the object has already been stored
If it has been stored previously, then reference the serial number. If not, store all its data.
Example 1-5 in Core Java V2 (show how saved hierarchically) http://www.ecst.csuchico.edu/~amk/foo/CoreJava/v2ch1/ObjectRefTest.java
* Cay S. Horstmann & Gary Cornell, Core Java
* Published By Sun Microsystems Press/Prentice-Hall
* Copyright (C) 1997 Sun Microsystems Inc.
* All Rights Reserved.
*
* Permission to use, copy, modify, and distribute this
* software and its documentation for NON-COMMERCIAL purposes
* and without fee is hereby granted provided that this
* copyright notice appears in all copies.
*
* THE AUTHORS AND PUBLISHER MAKE NO REPRESENTATIONS OR
* WARRANTIES ABOUT THE SUITABILITY OF THE SOFTWARE, EITHER
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE, OR NON-INFRINGEMENT. THE AUTHORS
* AND PUBLISHER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED
* BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
* THIS SOFTWARE OR ITS DERIVATIVES.
*/
/**
* @version 1.01 25 Oct 1997
* @author Cay Horstmann
*/
import java.io.*;
import java.util.*;
import corejava.Day;
class ObjectRefTest
{ public static void main(String[] args)
{ try
{
Employee[] staff = new Employee[3];
Employee harry = new Employee("Harry Hacker", 35000,
new Day(1989,10,1));
staff[0] = harry;
staff[1] = new Manager("Carl Cracker", 75000,
new Day(1987,12,15), harry);
staff[2] = new Manager("Tony Tester", 38000,
new Day(1990,3,15), harry);
ObjectOutputStream out = new ObjectOutputStream(new
FileOutputStream("test2.dat"));
out.writeObject(staff);
out.close();
ObjectInputStream in = new
ObjectInputStream(new FileInputStream("test2.dat"));
Employee[] newStaff = (Employee[])in.readObject();
for (int i = 0; i < newStaff.length; i++)
newStaff[i].raiseSalary(100);
for (int i = 0; i < newStaff.length; i++)
newStaff[i].print();
}
catch(Exception e)
{ e.printStackTrace();
System.exit(1);
}
}
}
class Employee implements Serializable
{ public Employee(String n, double s, Day d)
{ name = n;
salary = s;
hireDay = d;
}
public Employee() {}
public void raiseSalary(double byPercent)
{ salary *= 1 + byPercent / 100;
}
public int hireYear()
{ return hireDay.getYear();
}
public void print()
{ System.out.println(name + " " + salary
+ " " + hireYear());
}
private String name;
private double salary;
private Day hireDay;
}
class Manager extends Employee
{ public Manager(String n, double s, Day d, Employee e)
{ super(n, s, d);
secretary = e;
}
public Manager() {}
public void raiseSalary(double byPercent)
{ // add 1/2% bonus for every year of service
Day today = new Day();
double bonus = 0.5 * (today.getYear() - hireYear());
super.raiseSalary(byPercent + bonus);
}
public void print()
{ super.print();
System.out.print("Secretary: ");
if (secretary != null) secretary.print();
}
private Employee secretary;
}
Remember, objects contain references to its IV objects, not separate copies of objects. We want the object layout on disk to be exactly like the object layout in memory. This is persistance . Java achieves persistance through serialization .
In general:
the object stream output contains the types and data fields of all objects
each object is assigned a serial number
repeated occurences of the same object are stored as references to that serial number.
About Class Variables (static).
The problem here is when one wants to dynamically change static variables. Since these are defined in the class, when the class recreates the saved instance, it would put the old value for the static variable there.
One needs to customize (as above) to restore this information. Beware that it is an instance that is trying to save this new class variable. Specifically, if an object1 refers to a class (static) attribute of another class and object1 is to be serialized, to accurately save object1 the static attribute from the referenced class would also have to be saved and any state associated with that static attribute. However, if the referenced class is not serializable then the object1 should throw a NotSerializableException.
--------------------------------------------------------------------------------
Cautions:
While the model used for serialization is very simple, it has some drawbacks.
First, it's not as simple as marking serializable classes with the Serializable interface. It is possible for an object that can't be serialized to implement Serializable (either directly or by inheritance).
Ultimately, serialization has to do with the data members of the class, not the methods it contains; after all, Serializable is an empty interface and doesn't require you to implement any methods.
A class is serializable if, and only if, it has only members that are serializable--specifically: no static, transient members. By default, static and transient members are ignored when an object is serialized.
Generally speaking, classes that belong to the standard Java distribution are serializable unless serializing an object of that class would be a security risk. The problem is that there are many standard classes that would present security risks if serialized--for example, a FileInputStream can't be serialized, because when it is deserialized at a later time (and possibly on a different machine), you have an object that references some file handle that may no longer be meaningful, or that may point to a different file than it did originally.
You should make it a practice to check the class of any data members you add to a serializable class to make sure that data members can be serialized also. Don't make any assumptions; just look it up in the documentation.
Stating that a class implements Serializable is essentially a promise that the class can be successfully saved and restored using the serialization mechanism. The problem is that any subclass of that class automatically implements Serializable via inheritance, even if it adds some non-serializable members. Java throws a NotSerializableException (from the java.io package) if you try to save or restore a non-serializable object.
When you are writing Beans (or any class that you may want to serialize), you have to think carefully about what the class contains, and you also have to think about how the class will be used.
You can redesign almost any class so that it is serializable, but this redesign may have implications for the interface between your class and the rest of the world. Ultimately, that's the trick with object serialization. It's not as simple as marking a few classes Serializable; it has real implication for how you write code.
Versioning
The idea: you have a class and you have serialized objects made from this class. Now the class changes (you have a new version). What happens when you try to load old instance information to a newly created instance (from a newer class version)?
"When an object is serialized, some information about its class must obviously be serialized with it, so that the correct class file can be loaded when the object is deserialized. This information about the class is represented by the java.io.ObjectStreamClass class. It contains the fully-qualified name of the class and a version number. The version number is very important because an early version of a class may not be able to deserialize a serialized instance created by a later version of the same class." Java in a Nutshell, page 175
Core JavaV2, (In the 1.2 Core Java text, this information is in the Volume1) discusses what the files that are saved during serialization actually look like. Note (2) of the class description: "the serial version unique ID , which is a fingerprint of the data field types and method signatures".
Java gets this fingerprint by using their Secure Hash Algorithm SHA on the data of the class.
When a class definition changes in any way, so does this SHA fingerprint. So the idea is when you start serializing instances of a class, you should identify the fingerprint of the current version of the class.
See SUNs Stream Unique Identifiers page to see what all is in this fingerprint
To get the SHA fingerprint:
Do SHA to get the fingerprint (see Core Java about the use of serialver (a standalone program that generates) these numbers).
Once generated, put it as a definition in the class and later versions that will not break serialization compatibility
"breaking" serializations produces exceptions like:
java.io.InvalidClassException: Person; local class incompatible:
stream classdesc serialVersionUID = -2832314155938395448,
local class serialVersionUID = 480295508009809219
Situations:
if only the methods change, no problem
Program using version 1 objects: if data fields from version 1 are less than version 2, created and set to default values for type Fig.10
Program using version 1 objects: if data fields from version 1 are greater than version 2, ignore extra Fig.11
If you make larger changes that break serialization (2 and 3 above) compatibility, run serialver again to generate an updated version number. "It is up to the class designer to implement additional code in the readObject method to fix version incompatibilities or to make sure the methods are robust enough to handle null data." CoreJavaV2 pg.66 ( null is the default for un-instantiated objects)
--------------------------------------------------------------------------------
Do we get it? For a final overview see Serialization part of Bean tutorial
--------------------------------------------------------------------------------
To allow someone to check to see if the serialization process works, I have included an example demonstration. You should look at this to see a good way to allow a user to load and save materials. Hint: This could be useful for lab 1. Next, we will make use of a class that uses introspection techniques and also has a hash table. Since hash tables are usually set as transient I also have this example available to show how it was serialized. We will look at it in the next set of notes.
--------------------------------------------------------------------------------
There is obviously a lot more to Serializable. For further reference try (the online book I have mentioned) "Thinking in Java" and "Java in a Nutshell", for code and "Developing Java Beans" by Robert Englander published by O'Reilly (ISBN: 1-56592-289-1). The SUN web site to visit would be: at http://java.sun.com/j2se/1.4.2/docs/guide/serialization and specifically the Specs at http://java.sun.com/j2se/1.4.2/docs/guide/serialization/spec/serialTOC.html
non-trivial
(how to save instances)
Object Serialization.
Java provides a feature called object serialization that allows you take any object that implements the Serializable interface and turns it into a sequence of bytes that can later be fully restored into the original object.
"Objects are serialized with the ObjectOutputStream and they are deserialized with the ObjectInputStream. Both of these classes are part of the java.io package, and they function, in many ways, like DataOutputStream and DataInputStream because they define the same methods for writing and reading binary representations of Java primitive types to and from streams. What ObjectOutputStream and ObjectInputStream add, however, is the ability to write and read non-primitive object and array values to and from a stream." Java in a Nutshell
The ObjectInput and ObjectOutput interfaces are the basis for serialization within Java. The ObjectInputStream and ObjectOutputStream classes, respectively, implement the interfaces.
What Objects need to be serialized?
The basic idea is to save Instance information. Why Instances?
Classes are defined and "saved" in your .java files. However, for instances, they disappear when you exit the application. One might want to save Instance information.
What information might this be?
Well, it won't be methods, since methods are in Classes, not instances. Instances hold Instance Variables and can change Class Variables. Thus, static variables in an Instance can be a problem.. more later
To get an idea of what serialization is doing, let's consider more about the nature of objects. From, "Developing Java Beans" by Robert Englander.
"Most components maintain information that defines their appearance and behavior. This information is known as the state of the object. Some of this information is represented by the object's properties. For instance, the font or color properties of a visual component are usually considered to be part of that object's state. There may also be internal data used by an object that is not exposed as properties, but plays a part in defining the behavior of the object nevertheless.
...The state information of all the components, as well as the application or applet itself, must be saved on a persistent storage medium so that it can be used to recreate the overall application state at run-time. An important aspect of the application state is the definition of the components themselves: the persistent state of an application includes a description of the components being used, as well their collective state."
Note that we use ObjectStreams to save objects. You can only read and write objects not numbers. To write and read numbers, you use methods such as writeInt/readInt or writeDouble/readDouble. (The objectstream classes implement the DataInput/DataOutput interfaces.) (more later on why you would want to do this)
Of course, numbers inside objects (IVs) are saved and stored automatically (discussion on CV storage being static later).
When an object is saved all of its state is saved. This means that all handles and objects that the saved object refers to are saved.In its simplest application the programmer includes the phrase implements java.io.Serializable in the class definition as shown in the following example from "Developing Java Beans."
public class SimpleExample implements java.io.Serializable
{
protected int anInteger;
protected float aFloat;
protected java.awt.Button aButton;
public SimpleExample()
{
}
}
note implements java.io.Serializable could be implements Serializable but need to import java.io.*
In this example there are three data members. The first two, anInteger and aFloat are primitive data types and are therefore serializable. The third, aButton is an instance of type java.awt.Button, a subclass of java.awt.Component which itself implements java.io.Serializable. Therefore the class SimpleExample can be serialized without doing anything more than declaring that it implements java.io.Serializable.
Only classes that implement the Serializable or Externalizable interface can be written to or read from an object stream. Serializable is a marker interface - it doesn't define any methods and serves only to specify whether an object is allowed to be serialized (look in the API ... it is empty!). Read what it says about the readObject and writeObject methods. The Externalizable interface (which extends Serializable) does define methods and is used by objects that want advanced control.
Below is the save() method in the example 8.1 in Java in a Nutshell 2. Note the creation of the ObjectOutputStream and the use of writeObject(). Since it does not implement the writeObject method, it needs to instantiate the ObjectOutputStream explicitly. It is only saving one Object (specificially lines.) (See Java in a Nutshell, page 173 http://www.ecst.csuchico.edu/~amk/foo/javanut2/ch08/ScribbleFrame.java)
/**
* Prompt the user for a filename, and save the scribble in that file.
* Serialize the vector of lines with an ObjectOutputStream.
* Compress the serialized objects with a GZIPOutputStream.
* Write the compressed, serialized data to a file with a FileOutputStream.
* Don't forget to flush and close the stream.
*/
public void save() {
// Create a file dialog to query the user for a filename.
FileDialog f = new FileDialog(frame, "Save Scribble", FileDialog.SAVE);
f.show(); // Display the dialog and block.
String filename = f.getFile(); // Get the user's response
if (filename != null) { // If user didn't click "Cancel".
try {
// Create the necessary output streams to save the scribble.
FileOutputStream fos = new FileOutputStream(filename);
// Save to file
GZIPOutputStream gzos = new GZIPOutputStream(fos);
// Compressed
ObjectOutputStream out = new ObjectOutputStream(gzos);
// Save objects
out.writeObject(lines); // Write the entire Vector of scribbles
out.flush(); // Always flush the output.
out.close(); // And close the stream.
}
// Print out exceptions. We should really display them in a dialog...
catch (IOException e) { System.out.println(e); }
}
}
/**
* Prompt for a filename, and load a scribble from that file.
* Read compressed, serialized data with a FileInputStream.
* Uncompress that data with a GZIPInputStream.
* Deserialize the vector of lines with a ObjectInputStream.
* Replace current data with new data, and redraw everything.
*/
public void load() {
// Create a file dialog to query the user for a filename.
FileDialog f = new FileDialog(frame, "Load Scribble", FileDialog.LOAD);
f.show(); // Display the dialog and block.
String filename = f.getFile(); // Get the user's response
if (filename != null) { // If user didn't click "Cancel".
try {
// Create necessary input streams
FileInputStream fis = new FileInputStream(filename); // Read from
file
GZIPInputStream gzis = new GZIPInputStream(fis); // Uncompress
ObjectInputStream in = new ObjectInputStream(gzis); // Read objects
// Read in an object. It should be a vector of scribbles
Vector newlines = (Vector)in.readObject();
in.close(); // Close the stream.
lines = newlines; // Set the Vector of lines.
repaint(); // And redisplay the scribble.
}
// Print out exceptions. We should really display them in a dialog...
catch (Exception e) { System.out.println(e); }
}
}
Static or transient data
However, this "ease" is not true in all cases. As we shall see, serialization is not so easily applied to classes with static or transient data members.
Only data associated with a specific instance of a class is serialized, therefore static data, that is, data associated with a class as opposed to an instance, is not serialized automatically. To serialize data stored in a static variable one must provide class-specific serialization.
Similarly, some classes may define data members to use as scratch variables. Serializing these data members may be unnecessary. Some examples of transient data include runtime statistics or hash table mapping references. These data should be marked with the transient modifier to avoid serialization. Transient, by definition, is used to designate data members that the programmer does not want or need to be serialized. See Java in a Nutshell, page 174: mouse position, preferred size, file handles (machine specific (native code)).
When writing code if declare transient, then triggers (to programmer) necessity of special code for serialization later. (when writing code if declare transient, then triggers (to programmer) necessity of special code for serialization later)
To serialize an object, you create some sort of OutputStream object and then wrap it inside an ObjectOutputStream object. At this point you only need to call writeObject() and your object is magically serialized and sent to the OutputStream. To reverse the process, you wrap an InputStream inside an ObjectInputStream and call readObject(). What comes back is, as usual, a handle to an upcast Object, so you must downcast to set things straight.
If you need to dynamically query the type of the object, you can use the getClass method. Specifically dk.getClass.getName() returns the name of the class that dk is an instance of. I.e., this asks the object for the name of its corresponding class object. (Hmmm, True, but what about syntax? I still need to know what it is to declare it...too bad) (C++ can do this in one operation (dynamic_cast (gives null if wrong type)), java can use instanceof operator to check if it is what I think (see Core Java, Ch5 Inheritence, Casting section)
Custom Serialization
Note: a class can define custom serialization and deserialization behavior for its objects by implementing writeObject() and readObject() methods. These methods are not defined by any interface. The methods must be declared private (rather surprising since they are called from outside of the class during serialization and deserialization.)
The following example illustrates the serialization process. It is not necessarily what you need to do unless you need to customize; it is what is automatically done for an instances IVs
// save a string and double to the stream
String str = "Sample";
double d = 3.14;
FileOutputStream f = new FileOutputStream ("Beans.tmp")
ObjectOutputStream s = new ObjectOutputStream(f);
s.writeObject(str);
s.writeDouble(d);
s.flush();
// restore the string and double
FileInputStream f = new FileInputStream("Beans.tmp");
ObjectInputStream s = new ObjectInputStream(f);
String str = (String)s.readObject();
double d = s.readDouble();
Note the order: when reading back keep track of the number of objects, their order and their type. In Java, remember that strings and arrays are objects and can, therefore, be restored with the writeObject/readObject methods. (Why need to do this if they are objects? why not automatic? ... IS automatic if the object is serializable and it is an IV of an instance that is serializable... and it is not static or transient)
Another example: (This is Java in a Nutshell, page 175) (show use of transient data)
// This example is from the book "Java in a Nutshell, Second Edition".
// Written by David Flanagan. Copyright (c) 1997 O'Reilly & Associates.
// You may distribute this source code for non-commercial purposes only.
// You may study, modify, and use this example for any purpose, as long
// as this notice is retained. Note that this example is provided "as is",
// WITHOUT WARRANTY of any kind either expressed or implied.
import java.io.*;
/** A simple class that implements a growable array or ints, and knows
* how to serialize itself as efficiently as a non-growable array. */
public class IntList implements Serializable
{
private int[] nums = new int[8]; // An array to store the numbers.
private transient int size = 0; // Index of next unused element of nums[].
/** Return an element of the array */
public int elementAt(int index) throws ArrayIndexOutOfBoundsException
{
if (index >= size) throw new ArrayIndexOutOfBoundsException(index);
else return nums[index];
}
/** Add an int to the array, growing the array if necessary */
public void add(int x) {
if (nums.length == size) resize(nums.length*2); // Grow array, if needed.
nums[size++] = x; // Store the int in it.
}
/** An internal method to change the allocated size of the array */
protected void resize(int newsize) {
int[] oldnums = nums;
nums = new int[newsize]; // Create a new array.
System.arraycopy(oldnums, 0, nums, 0, size); // Copy array elements.
}
/** Get rid of unused array elements before serializing the array */
private void writeObject(ObjectOutputStream out) throws IOException {
if (nums.length > size) resize(size); // Compact the array.
out.defaultWriteObject(); // Then write it out normally.
}
/** Compute the transient size field after deserializing the array */
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject(); // Read the array normally.
size = nums.length; // Restore the transient field.
}
}
What Objects need to implement Serializable?
Component implements Serializable, so all AWT components can be serialized. If a Class is serializable, all of its subclasses are. Otherwise, you need to make your classes implement serializable.
Since in a given application, one probably does not inherit most classes (other than the AWT stuff) you often need to do this explicitly.
Why not serialize everything?
Security. A knowledgable hacker could exploit information and modify object files.
Safeguards: validation, authentication, and/or security measures for keeping private variables "unsaved". (see Core Java V2)
Consider IO, locations of files change depending on the system you are running on. If you want to keep your java classes cross-platform compliant, you should not store things that are machine specific.
Some things can't be serialized. See this example
If you use someone else's serialized classes, and depend on their serialization for yours, if their version ever changes, then your serialized code may not work anymore. I had a nasty example of this with some student's code that used Swing classes that were "provided" serializable. When a given JDK would change anything in Swing, then any serialized code that was built from the previous version would not work. Same source from my end, but the provided java classes changed.
This lesson is a good one. Do not serialize everything but rather make certain things transient and rebuild them when you retrieve the serialized object. Most GUIs would follow this rule
Back to serializing...
So, each Class that you have Instances that will need to be saved should implement Serializable and , possibly have custom readObject() and writeObject() methods.
If a class does not implement the method, the default serialization provided by defaultReadObject() will be used. In custom methods, call the default first.
(The default methods may be called only from a class's read/writeObject methods. If it is called from outside the writeObject method (for example), the NotActiveException is thrown)
(What does this readObject() probably do? Upon (1) reading the object type, (2) instantiates the (blank) object and (3) sets it variables to the values saved)
If you write a save() method for the top Object, then as Java tries to serialize it, it accesses its variables - which are possibly objects that need to be serialized, and performs this recursively. From specs "The writeObject method serializes the specified object and traverses its references to other objects in the object graph recursively to create a complete serialized represetnation of the graph."
As far as subclasses (from Englander): "When an object is serialized, the highest serializable class in its derivation hierachy is located and serialized first. Then the hierachy is walked, with each subclass being serialized in turn."
Specifically, readObject and writeObject methods only need to save and load their data fields; they should not concern themselves with superclass data or any other class information. (except changes to static variables)
Example 1-4 in Core Java (show how saved) http://www.ecst.csuchico.edu/~amk/foo/CoreJava/v2ch1/ObjectFileTest.java On the SUNs at Chico (at least on Expert), the corejava package is at /opt/java/corejava, this would need to be in your CLASSPATH for this code (and other code from the CoreJava book) to run
/*
* Cay S. Horstmann & Gary Cornell, Core Java
* Published By Sun Microsystems Press/Prentice-Hall
* Copyright (C) 1997 Sun Microsystems Inc.
* All Rights Reserved.
*
* Permission to use, copy, modify, and distribute this
* software and its documentation for NON-COMMERCIAL purposes
* and without fee is hereby granted provided that this
* copyright notice appears in all copies.
*
* THE AUTHORS AND PUBLISHER MAKE NO REPRESENTATIONS OR
* WARRANTIES ABOUT THE SUITABILITY OF THE SOFTWARE, EITHER
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE, OR NON-INFRINGEMENT. THE AUTHORS
* AND PUBLISHER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED
* BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
* THIS SOFTWARE OR ITS DERIVATIVES.
*/
/**
* @version 1.01 25 Oct 1997
* @author Cay Horstmann
*/
import java.io.*;
import corejava.Day;
class ObjectFileTest
{ public static void main(String[] args)
{ try
{ Employee[] staff = new Employee[3];
staff[0] = new Employee("Harry Hacker", 35000,
new Day(1989,10,1));
staff[1] = new Manager("Carl Cracker", 75000,
new Day(1987,12,15));
staff[2] = new Employee("Tony Tester", 38000,
new Day(1990,3,15));
ObjectOutputStream out = new ObjectOutputStream(new
FileOutputStream("test1.dat"));
out.writeObject(staff);
out.close();
ObjectInputStream in = new
ObjectInputStream(new FileInputStream("test1.dat"));
Employee[] newStaff = (Employee[])in.readObject();
int i;
for (i = 0; i < newStaff.length; i++)
newStaff[i].raiseSalary(100);
for (i = 0; i < newStaff.length; i++)
newStaff[i].print();
}
catch(Exception e)
{ System.out.print("Error: " + e);
System.exit(1);
}
}
}
class Employee implements Serializable
{ public Employee(String n, double s, Day d)
{ name = n;
salary = s;
hireDay = d;
}
public Employee() {}
public void print()
{ System.out.println(name + " " + salary
+ " " + hireYear());
}
public void raiseSalary(double byPercent)
{ salary *= 1 + byPercent / 100;
}
public int hireYear()
{ return hireDay.getYear();
}
private String name;
private double salary;
private Day hireDay;
}
class Manager extends Employee
{ public Manager(String n, double s, Day d)
{ super(n, s, d);
secretaryName = "";
}
public Manager() {}
public void raiseSalary(double byPercent)
{ // add 1/2% bonus for every year of service
Day today = new Day();
double bonus = 0.5 * (today.getYear() - hireYear());
super.raiseSalary(byPercent + bonus);
}
public void setSecretaryName(String n)
{ secretaryName = n;
}
public String getSecretaryName()
{ return secretaryName;
}
private String secretaryName;
}
Keep in mind that objects may contain references to their variables, not separate copies.
Specifically, consider the example below. Two managers can share the same secretary. One does not want to save three copies of Harry. (One wants to maintain consistent data and not worry about editing numerous copies.)
Employee harry = new Employee("Harry Hacker", ...);
Manager carl = new Manager("Carl Cracker", ...);
carl.setSecretary(harry);
Manager tony = new Manager("Tony Tester", ...);
tony.setSecretary(harry);
See Core Java, chapter on Streams and Files: Object Streams Fig.5, Fig.6, Fig.7, Fig.8
Thus the term serialization...
All objects that are saved are given a serial number (1,2,3...)
When saving an object to disk, find out if the object has already been stored
If it has been stored previously, then reference the serial number. If not, store all its data.
Example 1-5 in Core Java V2 (show how saved hierarchically) http://www.ecst.csuchico.edu/~amk/foo/CoreJava/v2ch1/ObjectRefTest.java
* Cay S. Horstmann & Gary Cornell, Core Java
* Published By Sun Microsystems Press/Prentice-Hall
* Copyright (C) 1997 Sun Microsystems Inc.
* All Rights Reserved.
*
* Permission to use, copy, modify, and distribute this
* software and its documentation for NON-COMMERCIAL purposes
* and without fee is hereby granted provided that this
* copyright notice appears in all copies.
*
* THE AUTHORS AND PUBLISHER MAKE NO REPRESENTATIONS OR
* WARRANTIES ABOUT THE SUITABILITY OF THE SOFTWARE, EITHER
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE, OR NON-INFRINGEMENT. THE AUTHORS
* AND PUBLISHER SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED
* BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
* THIS SOFTWARE OR ITS DERIVATIVES.
*/
/**
* @version 1.01 25 Oct 1997
* @author Cay Horstmann
*/
import java.io.*;
import java.util.*;
import corejava.Day;
class ObjectRefTest
{ public static void main(String[] args)
{ try
{
Employee[] staff = new Employee[3];
Employee harry = new Employee("Harry Hacker", 35000,
new Day(1989,10,1));
staff[0] = harry;
staff[1] = new Manager("Carl Cracker", 75000,
new Day(1987,12,15), harry);
staff[2] = new Manager("Tony Tester", 38000,
new Day(1990,3,15), harry);
ObjectOutputStream out = new ObjectOutputStream(new
FileOutputStream("test2.dat"));
out.writeObject(staff);
out.close();
ObjectInputStream in = new
ObjectInputStream(new FileInputStream("test2.dat"));
Employee[] newStaff = (Employee[])in.readObject();
for (int i = 0; i < newStaff.length; i++)
newStaff[i].raiseSalary(100);
for (int i = 0; i < newStaff.length; i++)
newStaff[i].print();
}
catch(Exception e)
{ e.printStackTrace();
System.exit(1);
}
}
}
class Employee implements Serializable
{ public Employee(String n, double s, Day d)
{ name = n;
salary = s;
hireDay = d;
}
public Employee() {}
public void raiseSalary(double byPercent)
{ salary *= 1 + byPercent / 100;
}
public int hireYear()
{ return hireDay.getYear();
}
public void print()
{ System.out.println(name + " " + salary
+ " " + hireYear());
}
private String name;
private double salary;
private Day hireDay;
}
class Manager extends Employee
{ public Manager(String n, double s, Day d, Employee e)
{ super(n, s, d);
secretary = e;
}
public Manager() {}
public void raiseSalary(double byPercent)
{ // add 1/2% bonus for every year of service
Day today = new Day();
double bonus = 0.5 * (today.getYear() - hireYear());
super.raiseSalary(byPercent + bonus);
}
public void print()
{ super.print();
System.out.print("Secretary: ");
if (secretary != null) secretary.print();
}
private Employee secretary;
}
Remember, objects contain references to its IV objects, not separate copies of objects. We want the object layout on disk to be exactly like the object layout in memory. This is persistance . Java achieves persistance through serialization .
In general:
the object stream output contains the types and data fields of all objects
each object is assigned a serial number
repeated occurences of the same object are stored as references to that serial number.
About Class Variables (static).
The problem here is when one wants to dynamically change static variables. Since these are defined in the class, when the class recreates the saved instance, it would put the old value for the static variable there.
One needs to customize (as above) to restore this information. Beware that it is an instance that is trying to save this new class variable. Specifically, if an object1 refers to a class (static) attribute of another class and object1 is to be serialized, to accurately save object1 the static attribute from the referenced class would also have to be saved and any state associated with that static attribute. However, if the referenced class is not serializable then the object1 should throw a NotSerializableException.
--------------------------------------------------------------------------------
Cautions:
While the model used for serialization is very simple, it has some drawbacks.
First, it's not as simple as marking serializable classes with the Serializable interface. It is possible for an object that can't be serialized to implement Serializable (either directly or by inheritance).
Ultimately, serialization has to do with the data members of the class, not the methods it contains; after all, Serializable is an empty interface and doesn't require you to implement any methods.
A class is serializable if, and only if, it has only members that are serializable--specifically: no static, transient members. By default, static and transient members are ignored when an object is serialized.
Generally speaking, classes that belong to the standard Java distribution are serializable unless serializing an object of that class would be a security risk. The problem is that there are many standard classes that would present security risks if serialized--for example, a FileInputStream can't be serialized, because when it is deserialized at a later time (and possibly on a different machine), you have an object that references some file handle that may no longer be meaningful, or that may point to a different file than it did originally.
You should make it a practice to check the class of any data members you add to a serializable class to make sure that data members can be serialized also. Don't make any assumptions; just look it up in the documentation.
Stating that a class implements Serializable is essentially a promise that the class can be successfully saved and restored using the serialization mechanism. The problem is that any subclass of that class automatically implements Serializable via inheritance, even if it adds some non-serializable members. Java throws a NotSerializableException (from the java.io package) if you try to save or restore a non-serializable object.
When you are writing Beans (or any class that you may want to serialize), you have to think carefully about what the class contains, and you also have to think about how the class will be used.
You can redesign almost any class so that it is serializable, but this redesign may have implications for the interface between your class and the rest of the world. Ultimately, that's the trick with object serialization. It's not as simple as marking a few classes Serializable; it has real implication for how you write code.
Versioning
The idea: you have a class and you have serialized objects made from this class. Now the class changes (you have a new version). What happens when you try to load old instance information to a newly created instance (from a newer class version)?
"When an object is serialized, some information about its class must obviously be serialized with it, so that the correct class file can be loaded when the object is deserialized. This information about the class is represented by the java.io.ObjectStreamClass class. It contains the fully-qualified name of the class and a version number. The version number is very important because an early version of a class may not be able to deserialize a serialized instance created by a later version of the same class." Java in a Nutshell, page 175
Core JavaV2, (In the 1.2 Core Java text, this information is in the Volume1) discusses what the files that are saved during serialization actually look like. Note (2) of the class description: "the serial version unique ID , which is a fingerprint of the data field types and method signatures".
Java gets this fingerprint by using their Secure Hash Algorithm SHA on the data of the class.
When a class definition changes in any way, so does this SHA fingerprint. So the idea is when you start serializing instances of a class, you should identify the fingerprint of the current version of the class.
See SUNs Stream Unique Identifiers page to see what all is in this fingerprint
To get the SHA fingerprint:
Do SHA to get the fingerprint (see Core Java about the use of serialver (a standalone program that generates) these numbers).
Once generated, put it as a definition in the class and later versions that will not break serialization compatibility
"breaking" serializations produces exceptions like:
java.io.InvalidClassException: Person; local class incompatible:
stream classdesc serialVersionUID = -2832314155938395448,
local class serialVersionUID = 480295508009809219
Situations:
if only the methods change, no problem
Program using version 1 objects: if data fields from version 1 are less than version 2, created and set to default values for type Fig.10
Program using version 1 objects: if data fields from version 1 are greater than version 2, ignore extra Fig.11
If you make larger changes that break serialization (2 and 3 above) compatibility, run serialver again to generate an updated version number. "It is up to the class designer to implement additional code in the readObject method to fix version incompatibilities or to make sure the methods are robust enough to handle null data." CoreJavaV2 pg.66 ( null is the default for un-instantiated objects)
--------------------------------------------------------------------------------
Do we get it? For a final overview see Serialization part of Bean tutorial
--------------------------------------------------------------------------------
To allow someone to check to see if the serialization process works, I have included an example demonstration. You should look at this to see a good way to allow a user to load and save materials. Hint: This could be useful for lab 1. Next, we will make use of a class that uses introspection techniques and also has a hash table. Since hash tables are usually set as transient I also have this example available to show how it was serialized. We will look at it in the next set of notes.
--------------------------------------------------------------------------------
There is obviously a lot more to Serializable. For further reference try (the online book I have mentioned) "Thinking in Java" and "Java in a Nutshell", for code and "Developing Java Beans" by Robert Englander published by O'Reilly (ISBN: 1-56592-289-1). The SUN web site to visit would be: at http://java.sun.com/j2se/1.4.2/docs/guide/serialization and specifically the Specs at http://java.sun.com/j2se/1.4.2/docs/guide/serialization/spec/serialTOC.html
non-trivial
订阅:
博文 (Atom)