Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Mastering Enterprise JavaBeans™ and the Java 2 Platform, Enterprise Edition - Roman E

..pdf
Скачиваний:
41
Добавлен:
24.05.2014
Размер:
6.28 Mб
Скачать

138 M A S T E R I N G E N T E R P R I S E J A V A B E A N S

With programmatic authorization, you hard-code security checks into your bean code. Your business logic is interlaced with security checks, bloating your bean code.

Security Roles

Authorization in EJB relies on security roles. A security role is a collection of client identities. For a client to be authorized to perform an operation, its security identity must be in the correct security role for that operation. The EJB deployer is responsible for associating the identities with the correct security roles after you write your beans.

The advantage to using security roles is you do not hard-code specific identities into your beans. This is necessary when you are developing beans for deployment in a wide variety of security environments, because each environment will have its own list of identities. This also allows you to modify access control without recompiling your bean code.

Specifying security roles in EJB is again application server-specific. The following demonstrates how to do it with BEA’s WebLogic server in the weblogic.properties file:

weblogic.security.group.employees=EmployeeA, EmployeeB

weblogic.security.group.managers=ManagerA

weblogic.security.group.administrators=AdminA

...

The application server queries these groups at runtime when performing authorization.

Declarative Authorization

With declarative authorization, you declare your bean’s authorization requirements in your deployment descriptor. The container will fulfill these requirements at runtime. For example, take the following sample deployment descriptor entry using BEA’s WebLogic server:

(accessControlEntries

submitPurchaseOrder [employees]

approvePurchaseOrder [managers]

DEFAULT

[administrators]

); end accessControlEntries

The first entry specifies that all identities authorized as employees can submit purchase orders, but the second entry specifies that only identities authorized as managers can approve purchase orders. The last entry specifies that administrators have access to call all other methods on this bean. The EJB container

Go back to the first page for a quick link to buy this book online!

Adding Functionality to Your Beans 139

will automatically perform these security checks on your bean’s methods at runtime, and throw a java.lang.SecurityException back to the client code if the client identity is not authenticated or authorized.

Programmatic Authorization

To perform explicit security authorization checks in your enterprise beans, you must query the EJB context to retrieve information about the current client. There are two relevant methods: isCallerInRole(Identity role) and getCallerIdentity().

isCallerInRole()

isCallerInRole(Identity role) checks whether the current caller is in a particular security role. When you call this method, you pass the security role that you want the caller compared against. The difference between isCallerInRole(Identity role) and declarative security is isCallerInRole(Identity role) does not check the security roles you’ve defined in the deployment descriptor at all, but rather checks the security role you define in your code. For example:

import java.security.Identity;

...

public class MyBean implements SessionBean {

private SessionContext ctx;

...

public void foo() {

Identity id = new MyIdentity("administrators"); if (ctx.isCallerInRole(id)) {

System.out.println("An admin called me"); return;

}

System.out.println("A non-admin called me");

}

}

The above code demonstrates how to perform different actions based upon the security role of the client. Only if the caller is in the role defined by MyIdentity does the caller have administrator access.

Note that we must also define the MyIdentity class—it is a concrete implementation of the java.security.Identity abstract class. For demonstration purposes this is quite simple, and is shown below.

import java.security.Identity;

Go back to the first page for a quick link to buy this book online!

140 M A S T E R I N G E N T E R P R I S E J A V A B E A N S

public class MyIdentity extends Identity {

public MyIdentity(String id) { super(id);

}

}

getCallerIdentity()

getCallerIdentity() retrieves the current caller’s security identity. You can then use that identity for many purposes, such as using the caller’s distinguished name in a database query. Here is sample code that uses getCallerIdentity():

import java.security.Identity;

...

public class MyBean implements SessionBean {

private SessionContext ctx;

...

public void bar() {

Identity id = ctx.getCallerIdentity();

String name = id.getName();

System.out.println("The caller's name is " + name);

}

}

Declarative or Programmatic?

As with persistence and transactions, security is a middleware service that you should strive to externalize from your beans. By using declarative security, you decouple your beans’ business purpose from specific security policies, enabling others to modify security rules without modifying bean code.

In the ideal world, we’d code all our beans with declarative security. But unfortunately, the EJB 1.0 specification does not provide adequate facilities for this; specifically, there is no portable way to declaratively perform instance-level authorization. This is best illustrated with an example.

Let’s say you have an enterprise bean that models a bank account. The caller of the enterprise bean is a bank account manager who wants to withdraw or deposit into that bank account. But this bank account manager is only responsible for bank accounts with balances below $1000, and we don’t want him modifying bank accounts with balances larger than that. With EJB security, there is no way to declare in your deployment descriptor that bank account managers can

Go back to the first page for a quick link to buy this book online!

Adding Functionality to Your Beans 141

only modify certain bean instances. You can only specify security roles on the enterprise bean class, and those security rules will apply for all instances of that class. For these situations, you should resort to programmatic security.

Security Propagation

Behind the scenes, all security checks are made possible due to security contexts. Security contexts encapsulate the current caller’s security state. You never see security contexts in your application code, because the container uses them behind the scenes. When you call a method in EJB, the container can propagate your security information by implicitly passing your security context within the stubs and skeletons.

You can control how security information is propagated in your deployment descriptor via the runAsMode and the runAsIdentity entries. Let’s examine each of these.

runAsMode and runAsIdentity

Your bean’s runAsMode specifies the security identity your bean assumes when it performs operations (such as calling other beans). There are three settings you can specify for runAsMode:

CLIENT_IDENTITY: The bean propagates the client’s security context. Whatever security identity the client was associated with is the security identity that the bean will use when it executes.

SYSTEM_IDENTITY: The bean runs as with system-level authority.

SPECIFIED_IDENTITY: The bean runs as the identity specified by runAsIdentity deployment descriptor setting.

Your EJB container is responsible for intercepting all method calls and ensuring that your bean is running in the runAsMode and runAsIdentity settings you specify. For example, if you set the runAsMode to “SPECIFIED_IDENTITY”, and you set the runAsIdentity to “admin”, then the container must enforce that your bean runs as the “admin” identity when calling other beans.

Security Context Propagation Portability

Unfortunately, the EJB 1.0 and EJB 1.1 specifications do not specify how containers should propagate security contexts. What this means to you is that any two EJB containers are likely to be incompatible in how they deal with security. If you call a method from container A into container B, container B will not understand how to receive the security context sent by container A. There has been discussion of a new EJB/IIOP protocol that defines security context propagation in a

Go back to the first page for a quick link to buy this book online!

142 M A S T E R I N G E N T E R P R I S E J A V A B E A N S

portable way as part of EJB 2.0, but that is not likely to emerge until well into the year 2000.

Understanding EJB Object Handles

Many EJB applications require the ability for clients to disconnect from beans, and then reconnect again later to resume using that bean. For example, if you have a shopping cart that you’d like to save for a later time, and that shopping cart is manifested by a stateful session bean, you’d want your shopping cart state maintained when you reconnect later.

EJB provides for this need with EJB object handles. An EJB object handle is a long-lived proxy for an EJB object. If for some reason you disconnect from the EJB container/server, you can use the EJB object handle to reconnect to your EJB object, so that you don’t lose your conversational state with that bean. An EJB object handle is an essentially persistent reference to an EJB object. The following code demonstrates using EJB object handles:

//First, get the EJB object handle from the EJB object. javax.ejb.Handle myHandle = myEJBObject.getHandle();

//Next, serialize myHandle, and then save it in

//permanent storage.

ObjectOutputStream stream = ...; stream.writeObject(myHandle);

//time passes...

//When we want to use the EJB object again,

//deserialize the EJB object handle ObjectInputStream stream = ...;

Handle myHandle = (Handle) stream.readObject();

//Convert the EJB object handle back into an EJB object MyRemoteInterface myEJBObject =

(MyRemoteInterface) myHandle.getEJBObject();

//Resume calling methods again myEJBObject.callMethod();

If you’re using an EJB container based on Java RMI-IIOP (see Chapter 11) rather than plain vanilla RMI (see Appendix A), you need to use the following statement to convert the EJB object handle into an EJB object instead:

// Convert the EJB object handle into an EJB object

MyRemoteInterface myEJBObject = (MyRemoteInterface)

Go back to the first page for a quick link to buy this book online!

Adding Functionality to Your Beans 143

javax.rmi.PortableRemoteObject.narrow(

myHandle.getEJBObject(), MyRemoteInterface.class);

Unfortunately, EJB object handles have a strong domain limitation: Handles cannot be saved in one environment and then re-stored in a different environment. This means handles are not portable across EJB containers. It also means you cannot save a handle on one machine and then re-use it on a different machine.

Example: The Puzzle Game “Fazuul”

Let’s put the concepts we’ve just explored to concrete use. This bean deployment will be a bit more advanced than the ones we’ve seen so far, as it will illustrate the new concepts we’ve learned in this chapter, building on our knowledge from Chapters 4 and 5. This example will also show you how to use both stateful and stateless session beans together.

In this example, we’re going to have some fun and write a simple puzzle game using EJB. This game is called Fazuul and is based on an old Bulletin Board System (BBS) game written by Tim Stryker and Galacticomm, Inc.

What Is Fazuul?

Our Fazuul game puts you in a strange world of components—funny-looking objects that are on the ground. You have an infinite supply of these components because you can create them at will. You create components by pushing a button on a machine—whenever you push the button, three new components pop out. This is shown in Figure 6.1.

Your objective is to combine these basic components into new, more advanced components. These advanced new components can be combined with other components, yielding even more interesting devices. Whenever you combine two components, they are destroyed and a brand new one is born. Your job is to continue to combine components until you’ve reached the final, ultimate component, at which point you’ve won the game.

The three basic components our Fazuul machine generates are called Snarfs, Vrommells, and Rectors. These are definitely strange names, and they are part of the mysterious world that is Fazuul. These three components can be combined to form greater objects, such as Lucias and Subberts, as shown in Figure 6.2.

How do you know which components to combine? You must look at each component’s description, which give you hints. Use the hints properly, and you’ll combine the correct components and ultimately win the game. Combine components haphazardly, and you could generate a more primitive component. This is a very simple concept, and it makes for an interesting, challenging game.

Go back to the first page for a quick link to buy this book online!

144 M A S T E R I N G E N T E R P R I S E J A V A B E A N S

Component

Make

Component

 

Push Button

Make Component

Component

 

Machine

 

 

 

Make

Component

 

 

 

 

Component

Figure 6.1 The Fazuul Machine.

Snarf

Combine

Subbert

Vrommell

Figure 6.2 Some basic components in Fazuul.

Go back to the first page for a quick link to buy this book online!

Adding Functionality to Your Beans 145

Specifying Fazuul in EJB

Our Fazuul game is broken up into the following parts:

■■A Component session bean. Components are the objects in the Fazuul world, such as a Snarf component or a Vrommell component. Components are owned by a particular client, and they are thus modeled well by stateful session beans. Our component bean will support the following methods:

ejbCreate() initializes a component to a certain name, such as Snarf.

attachTo() tries to attach this component to another component. If successful, the new component will be returned.

getName() returns the string name of this component, such as “Snarf.”

getDescription() returns the long description of this component. The long description gives hints about how to combine it with other components.

■■A Machine session bean, responsible for making new components. Because this machine simply generates components, there is no state associated with it—it is hence best modeled as a stateless session bean. The following method is supported:

makeComponent() generates a new, random component.

■■Security functionality. The user should not be able to create components directly, but must use the machine as a facade. Otherwise, the user could create the winning component right away.

■■The ability to save and restore the game state. The user should be able to quit the game and later resume where he left off. We’ll accomplish this using EJB object handles.

■■A text-based client application that interacts with the user. This application has a simple main game loop that calls the appropriate bean methods to perform the game.

Making the Game Data-Driven through

Environment Properties

Our game supports a single enterprise bean for representing components in our Fazuul world—the Component bean. This bean can represent any kind of component, such as a Snarf or a Vrommell. Components know what kind of objects they are by examining their name string (e.g., “Snarf”), which is passed in during ejbCreate(). But how do they know which components they can attach to, in order to form a new component? And how do the components know what their long descriptions are?

Go back to the first page for a quick link to buy this book online!

146 M A S T E R I N G E N T E R P R I S E J A V A B E A N S

We’ll provide this functionality in the application-specific environment properties that will ship with our component bean. Our environment properties will provide a mapping from component names, such as “Snarf,” to their long descriptions, such as “This is a Snarf. Snarfs can attach to Vrommells, but don’t like Fwiffos.”

We’ll also make use of the environment properties to keep our list of which components can combine with other components. For example, we’ll add the line, “Snarf+Vrommell=Subbert.” When the client code calls a component’s attachTo() method, the component will check the available combinations and throw a ComponentException if the two components can’t be attached together. Otherwise, the newly generated component will be returned.

Finally, remember that our machine is responsible for generating components. We’ll need to supply it with a list of creatable components. This list will be in separate environment properties deployed with the machine bean. By having a separate list, we can control which components our machine creates; after all, we don’t want our machine to generate the final, “winning” component—that would make the game too easy!

Implementing Fazuul

Our Fazuul deployment will consist of the following files:

Component.java. The remote interface for ComponentBean.

ComponentBean.java. The enterprise bean class implementation of our components, such as Snarfs.

EJB Design Strategies

Making Your Beans Data-driven

We are using a completely data-driven model in this chapter’s example. All of our gamespecific logic is being externalized into environment properties. Data-driven models make your applications easily extensible and evolutionary. For example, we can add new components, new descriptions, and new solution paths to our game without recompiling a single line of code. All we’d have to do is change the environment properties. We could easily build on a GUI to allow nontechnical end users to customize our beans as well. The data-driven paradigm is extremely beneficial when you’re selling enterprise beans to other companies because you can protect your beans’ source code yet still provide a way for the customer to tune your beans. Of course, environment properties aren’t the most extensible way to have a data-driven model—a more sophisticated mechanism is to use a resource such as a database. For our game, environment properties will suffice.

Go back to the first page for a quick link to buy this book online!

Adding Functionality to Your Beans 147

ComponentHome.java. The home interface for ComponentBean.

ComponentException.java. Application-level exceptions that our ComponentBean throws when trying to attach to another bean.

The ComponentBean deployment descriptor and environment properties.

The deployment descriptor lists the middleware requirements of our bean. The container reads in the deployment descriptor at deployment time. The environment properties allow users of our component bean to customize our bean logic. Our component bean will read the environment properties in at runtime.

Machine.java. The remote interface for MachineBean.

MachineBean.java. The bean implementation of our machine that generates components.

MachineHome.java. The home interface for MachineBean.

MachineException.java. Application-level exceptions that our MachineBean throws. Remember that application-level exceptions are “regular, run-of-the- mill exceptions,” whereas system-level exceptions are serious problems, such as a database crash.

The MachineBean deployment descriptor and environment properties.

The deployment descriptor lists the middleware requirements of our bean. The container reads in the deployment descriptor at deployment time. The environment properties allow users of our machine bean to customize our bean logic. Our machine bean will read the environment properties in at runtime.

Client.java. The client code that calls our beans and provides a text-based interface.

Let’s take a look at each of these in detail.

The Component Remote Interface

Component.java is the remote interface for our components. Our interface exposes three business methods. The EJB container will implement this interface; the implementation is the EJB object. The EJB object has container-specific logic for delegating method calls to our enterprise bean implementation. The code is shown in Source 6.1.

The key method of this interface is:

Component attachTo(Component other)

This method will try to attach two components together and return the resulting component.

Go back to the first page for a quick link to buy this book online!