在昨天的文章《
BlazeDS结合Tomcat进行权限控制》中,讲述了BlazeDS如何在Tomcat环境下进行权限控制,但是我们不难发现有很多缺点,甚至有一些都是致命的,比如不能跨平台(中间件),甚至不能跨版本,还有用户名角色等配置不能自定义配置在RDBMS,文件或其它地方等。所以今天我要分享给大家如何摆脱这些限制,避免这些不利因素。
所幸的是,BlazeDS的设计者们已经为我们想到了这些,我们只需要采用自定义认证的方式即可,具体实现时,需要实现flex.messaging.security.LoginCommand这个接口。我们不妨先来看看这个接口的定义(直接上代码了):
 package flex.messaging.security;
package flex.messaging.security;

 import javax.servlet.ServletConfig;
import javax.servlet.ServletConfig;

 import java.security.Principal;
import java.security.Principal;
 import java.util.List;
import java.util.List;


 /** *//**
/** *//**
 * The class name of the implementation of this interface is configured in the
 * The class name of the implementation of this interface is configured in the
 * gateway configuration's security section and is instantiated using reflection
 * gateway configuration's security section and is instantiated using reflection
 * on servlet initialization.
 * on servlet initialization.
 */
 */
 public interface LoginCommand
public interface LoginCommand


 {
{

 /** *//**
    /** *//**
 * Called to initialize a login command prior to authentication/authorization requests.
     * Called to initialize a login command prior to authentication/authorization requests.
 *
     * 
 * @param config The servlet configuration for MessageBrokerServlet.
     * @param config The servlet configuration for MessageBrokerServlet.  
 */
     */
 void start(ServletConfig config);
    void start(ServletConfig config);


 /** *//**
    /** *//**
 * Called to free up resources used by the login command.
     * Called to free up resources used by the login command.
 */
     */
 void stop();
    void stop();


 /** *//**
    /** *//**
 * The gateway calls this method to perform programmatic, custom authentication.
     * The gateway calls this method to perform programmatic, custom authentication.
 * <p>
     * <p>
 * The credentials are passed as a Map to allow for extra properties to be
     * The credentials are passed as a Map to allow for extra properties to be
 * passed in the future. For now, only a "password" property is sent.
     * passed in the future. For now, only a "password" property is sent.
 * </p>
     * </p>
 *
     *
 * @param username    The principal being authenticated
     * @param username    The principal being authenticated
 * @param credentials A map, typically with string keys and values - holds, for example, a password
     * @param credentials A map, typically with string keys and values - holds, for example, a password
 * @return principal for the authenticated user when authentication is successful; null otherwise
     * @return principal for the authenticated user when authentication is successful; null otherwise 
 */
     */
 Principal doAuthentication(String username, Object credentials);
    Principal doAuthentication(String username, Object credentials);


 /** *//**
    /** *//**
 * The gateway calls this method to perform programmatic authorization.
     * The gateway calls this method to perform programmatic authorization.
 * <p>
     * <p>
 * A typical implementation would simply iterate over the supplied roles and
     * A typical implementation would simply iterate over the supplied roles and
 * check that at least one of the roles returned true from a call to
     * check that at least one of the roles returned true from a call to
 * HttpServletRequest.isUserInRole(String role).
     * HttpServletRequest.isUserInRole(String role).
 * </p>
     * </p>
 *
     *
 * @param principal The principal being checked for authorization
     * @param principal The principal being checked for authorization
 * @param roles    A List of role names to check, all members should be strings
     * @param roles    A List of role names to check, all members should be strings
 * @return true if the principal is authorized given the list of roles
     * @return true if the principal is authorized given the list of roles
 */
     */
 boolean doAuthorization(Principal principal, List roles);
    boolean doAuthorization(Principal principal, List roles);


 /** *//**
    /** *//**
 * Attempts to log a user out from their session.
     * Attempts to log a user out from their session.
 *
     *
 * NOTE: May not be possible on all application servers.
     * NOTE: May not be possible on all application servers.
 * @param principal The principal to logout.
     * @param principal The principal to logout.
 * @return true when logout is successful
     * @return true when logout is successful
 */
     */
 boolean logout(Principal principal);
    boolean logout(Principal principal);
 }
}
最主要的3个方法:doAuthentication()用来认证,doAuthorization()用来进行授权,logout()用来执行登出时的动作,主要是释放Principal,关于Principal的概念,直接来自于Java,如需进一步了解,也可以参考JAAS的相关知识,我在之前的学习笔记《
JAAS Study Note 》中也简单的提及过,在此就不再多讲了,废话不多说了,直接上步骤了,这应该是大家喜欢的方式:
1,实现一个自定义的Principal:
 package com.robin.common.security;
package com.robin.common.security;

 import java.security.Principal;
import java.security.Principal;
 import java.util.List;
import java.util.List;


 public class UserPrincipal implements Principal, java.io.Serializable
public class UserPrincipal implements Principal, java.io.Serializable  {
{

 private String name;
    private String name;
 private List<String> subjects;
    private List<String> subjects;


 /** *//**
    /** *//**
 * Create a SamplePrincipal with a Sample username.
     * Create a SamplePrincipal with a Sample username.
 *
     * 
 * <p>
     * <p>
 *
     * 
 * @param name
     * @param name
 *            the Sample username for this user.
     *            the Sample username for this user.
 *
     * 
 * @exception NullPointerException
     * @exception NullPointerException
 *                if the <code>name</code> is <code>null</code>.
     *                if the <code>name</code> is <code>null</code>.
 */
     */

 public UserPrincipal(String name)
    public UserPrincipal(String name)  {
{
 if (name == null)
        if (name == null)
 throw new NullPointerException("illegal null input");
            throw new NullPointerException("illegal null input");

 this.name = name;
        this.name = name;
 }
    }


 public List<String> getSubjects()
    public List<String> getSubjects()  {
{
 return subjects;
        return subjects;
 }
    }


 public void setSubjects(List<String> subjects)
    public void setSubjects(List<String> subjects)  {
{
 this.subjects = subjects;
        this.subjects = subjects;
 }
    }


 public String getName()
    public String getName()  {
{
 return name;
        return name;
 }
    }


 /** *//**
    /** *//**
 * Return a string representation of this <code>SamplePrincipal</code>.
     * Return a string representation of this <code>SamplePrincipal</code>.
 *
     * 
 * <p>
     * <p>
 *
     * 
 * @return a string representation of this <code>SamplePrincipal</code>.
     * @return a string representation of this <code>SamplePrincipal</code>.
 */
     */

 public String toString()
    public String toString()  {
{
 return ("Principal's username:  " + name);
        return ("Principal's username:  " + name);
 }
    }


 /** *//**
    /** *//**
 * Compares the specified Object with this <code>SamplePrincipal</code> for
     * Compares the specified Object with this <code>SamplePrincipal</code> for
 * equality. Returns true if the given object is also a
     * equality. Returns true if the given object is also a
 * <code>SamplePrincipal</code> and the two SamplePrincipals have the same
     * <code>SamplePrincipal</code> and the two SamplePrincipals have the same
 * username.
     * username.
 *
     * 
 * <p>
     * <p>
 *
     * 
 * @param o
     * @param o
 *            Object to be compared for equality with this
     *            Object to be compared for equality with this
 *            <code>SamplePrincipal</code>.
     *            <code>SamplePrincipal</code>.
 *
     * 
 * @return true if the specified Object is equal equal to this
     * @return true if the specified Object is equal equal to this
 *         <code>SamplePrincipal</code>.
     *         <code>SamplePrincipal</code>.
 */
     */

 public boolean equals(Object o)
    public boolean equals(Object o)  {
{
 if (o == null)
        if (o == null)
 return false;
            return false;

 if (this == o)
        if (this == o)
 return true;
            return true;

 if (!(o instanceof UserPrincipal))
        if (!(o instanceof UserPrincipal))
 return false;
            return false;
 UserPrincipal that = (UserPrincipal) o;
        UserPrincipal that = (UserPrincipal) o;

 if (this.getName().equals(that.getName()))
        if (this.getName().equals(that.getName()))
 return true;
            return true;
 return false;
        return false;
 }
    }


 /** *//**
    /** *//**
 * Return a hash code for this <code>SamplePrincipal</code>.
     * Return a hash code for this <code>SamplePrincipal</code>.
 *
     * 
 * <p>
     * <p>
 *
     * 
 * @return a hash code for this <code>SamplePrincipal</code>.
     * @return a hash code for this <code>SamplePrincipal</code>.
 */
     */

 public int hashCode()
    public int hashCode()  {
{
 return name.hashCode();
        return name.hashCode();
 }
    }
 }
}

2,实现自定义的LoginCommand:
 package com.robin.common.security;
package com.robin.common.security;

 import java.security.Principal;
import java.security.Principal;
 import java.util.ArrayList;
import java.util.ArrayList;
 import java.util.List;
import java.util.List;
 import java.util.Map;
import java.util.Map;

 import javax.servlet.ServletConfig;
import javax.servlet.ServletConfig;

 import flex.messaging.io.MessageIOConstants;
import flex.messaging.io.MessageIOConstants;
 import flex.messaging.security.LoginCommand;
import flex.messaging.security.LoginCommand;


 public class RdbmsLoginCommand implements LoginCommand
public class RdbmsLoginCommand implements LoginCommand  {
{


 public Principal doAuthentication(String username, Object credentials)
    public Principal doAuthentication(String username, Object credentials)  {
{
 String password = extractPassword(credentials);
        String password = extractPassword(credentials);
 System.out.println("###Username:"+username+",Password:"+password+"###");
        System.out.println("###Username:"+username+",Password:"+password+"###");
 //TODO: use this user name and password to validate from RDBMS.
        //TODO: use this user name and password to validate from RDBMS.
 //And then, query user's roles and set to principle.
        //And then, query user's roles and set to principle.

 if(true)
        if(true) {
{
 UserPrincipal principal = new UserPrincipal(username);
            UserPrincipal principal = new UserPrincipal(username);
 List<String> subjects = new ArrayList<String>();
            List<String> subjects = new ArrayList<String>();
 subjects.add("ROLE_AD");
            subjects.add("ROLE_AD");

 if(username.equals("admin"))
            if(username.equals("admin")) {
{
 subjects.add("ADMIN");
                subjects.add("ADMIN");
 }
            }
 principal.setSubjects(subjects);
            principal.setSubjects(subjects);
 return principal;
            return principal;

 } else
        } else {
{
 return null;
            return null;
 }
        }
 }
    }


 public boolean doAuthorization(Principal principal, List roles)
    public boolean doAuthorization(Principal principal, List roles)  {
{
 System.out.println(principal+"##########################");
        System.out.println(principal+"##########################");
 UserPrincipal p=(UserPrincipal)principal;
        UserPrincipal p=(UserPrincipal)principal;
 List<String> subjects = p.getSubjects();
        List<String> subjects = p.getSubjects();

 for (int i = 0; i < subjects.size(); i++)
        for (int i = 0; i < subjects.size(); i++)  {
{
 String subject= subjects.get(i);
            String subject= subjects.get(i);

 for (int j = 0; j < roles.size(); j++)
            for (int j = 0; j < roles.size(); j++)  {
{
 System.out.print(roles.get(j)+"$$$");
                System.out.print(roles.get(j)+"$$$");

 if(subject.equals(roles.get(j)))
                if(subject.equals(roles.get(j))) {
{
 return true;
                    return true;
 }
                }
 }
            }
 }
        }
 return false;
        return false;
 }
    }


 public boolean logout(Principal principal)
    public boolean logout(Principal principal)  {
{
 System.out.println(principal+"will logout at once.");
        System.out.println(principal+"will logout at once.");
 principal = null;
        principal = null;
 return true;
        return true;
 }
    }


 public void start(ServletConfig arg0)
    public void start(ServletConfig arg0)  {
{
 
        
 }
    }


 public void stop()
    public void stop()  {
{

 }
    }
 
    
 private String extractPassword(Object credentials)
    private String extractPassword(Object credentials)

 
     {
{
 String password = null;
        String password = null;
 if (credentials instanceof String)
        if (credentials instanceof String)

 
         {
{
 password = (String)credentials;
            password = (String)credentials;
 }
        }
 else if (credentials instanceof Map)
        else if (credentials instanceof Map)

 
         {
{
 password = (String)((Map)credentials).get(MessageIOConstants.SECURITY_CREDENTIALS);
            password = (String)((Map)credentials).get(MessageIOConstants.SECURITY_CREDENTIALS);
 }
        }
 return password;
        return password;
 }
    }

 }
}

这些代码都非常简单,我想就不用我再解释了。
3,在BlazeDS中配置security-constraint,先配置service-config.xml:
 <security>
<security>
 <login-command class="com.robin.common.security.RdbmsLoginCommand" server="all"/>
        <login-command class="com.robin.common.security.RdbmsLoginCommand" server="all"/>
 <security-constraint id="administrators">
        <security-constraint id="administrators">
 <auth-method>Custom</auth-method>
        <auth-method>Custom</auth-method>
 <roles>
            <roles>
 <role>ADMIN</role>
                <role>ADMIN</role>
 </roles>
            </roles>
 </security-constraint>
        </security-constraint>
 <security-constraint id="users">
        <security-constraint id="users">
 <auth-method>Custom</auth-method>
            <auth-method>Custom</auth-method>
 <roles>
            <roles>
 <role>ROLE_AD</role>
                <role>ROLE_AD</role>
 <role>ADMIN</role>
                <role>ADMIN</role>
 </roles>
            </roles>
 </security-constraint>
        </security-constraint>
 </security>
    </security>
然后在remote-config.xml中配置每个destination的授权规则:
 <destination id="DomainService">
<destination id="DomainService">
 <properties>
        <properties>
 <source>com.robin.service.domain.DomainService</source>
            <source>com.robin.service.domain.DomainService</source>
 <include-methods>
            <include-methods>
 <method name="getAllDomains"/>
            <method name="getAllDomains"/>
 <method name="addOrUpdateDomain" security-constraint="administrators"/>
            <method name="addOrUpdateDomain" security-constraint="administrators"/>
 </include-methods>
            </include-methods>
 </properties>
        </properties>
 <security>
        <security>
 <security-constraint ref="users"/>
            <security-constraint ref="users"/>
 </security>
        </security>
 </destination>
    </destination>
4,服务端的配置就大功告成了,现在来看看客户端如何实现登录:
 <?xml version = "1.0" encoding = "utf-8"?>
<?xml version = "1.0" encoding = "utf-8"?>
 <mx:Module xmlns:fx = "http://ns.adobe.com/mxml/2009" xmlns:s = "library://ns.adobe.com/flex/spark" xmlns:mx = "library://ns.adobe.com/flex/mx" layout = "absolute" width = "100%" height = "100%"
<mx:Module xmlns:fx = "http://ns.adobe.com/mxml/2009" xmlns:s = "library://ns.adobe.com/flex/spark" xmlns:mx = "library://ns.adobe.com/flex/mx" layout = "absolute" width = "100%" height = "100%"
 xmlns:component = "com.robin.common.component.*">
           xmlns:component = "com.robin.common.component.*">
 
    
 <fx:Declarations>
    <fx:Declarations>
 <mx:RemoteObject id = "loginService" destination = "remoting_AMF_SecurityConstraint_Custom" showBusyCursor = "true" fault = "Alert.show(event.fault.faultString, 'Error');"/>
        <mx:RemoteObject id = "loginService" destination = "remoting_AMF_SecurityConstraint_Custom" showBusyCursor = "true" fault = "Alert.show(event.fault.faultString, 'Error');"/>
 </fx:Declarations>
    </fx:Declarations>
 <fx:Script>
    <fx:Script>
 < import com.robin.common.events.SwitchModuleEvent;
            import com.robin.common.events.SwitchModuleEvent;
 import mx.controls.Alert;
            import mx.controls.Alert;
 import mx.messaging.ChannelSet;
            import mx.messaging.ChannelSet;
 import mx.messaging.config.ServerConfig;
            import mx.messaging.config.ServerConfig;
 import mx.rpc.AsyncResponder;
            import mx.rpc.AsyncResponder;
 import mx.rpc.AsyncToken;
            import mx.rpc.AsyncToken;
 import mx.rpc.events.FaultEvent;
            import mx.rpc.events.FaultEvent;
 import mx.rpc.events.ResultEvent;
            import mx.rpc.events.ResultEvent;

 // Define a ChannelSet object.
            // Define a ChannelSet object.
 public var cs:ChannelSet;
            public var cs:ChannelSet;
 // Define an AsyncToken object.
            // Define an AsyncToken object.
 public var token:AsyncToken;
            public var token:AsyncToken;

 // Initialize ChannelSet object based on the
            // Initialize ChannelSet object based on the
 // destination of the RemoteObject component.
           // destination of the RemoteObject component.
 private function creationCompleteHandler():void {
            private function creationCompleteHandler():void {
 if (cs == null)
                if (cs == null)
 cs = ServerConfig.getChannelSet(loginService.destination);
                   cs = ServerConfig.getChannelSet(loginService.destination);
 }
            }

 // Login and handle authentication success or failure.
            // Login and handle authentication success or failure.
 private function login():void {
            private function login():void {
 // Make sure that the user is not already logged in.
                // Make sure that the user is not already logged in.
 var user:String = username.text;
                var user:String = username.text;
 var pwd:String = password.text;
                var pwd:String = password.text;
 if (user == "" || pwd == "") {
                if (user == "" || pwd == "") {
 Alert.show("User name or password is empty, please check them.", "Info");
                    Alert.show("User name or password is empty, please check them.", "Info");
 return;
                    return;
 }
                }
 if (this.parentApplication.cs.authenticated == false) {
                if (this.parentApplication.cs.authenticated == false) {
 this.parentApplication.token = this.parentApplication.cs.login(user, pwd);
                    this.parentApplication.token = this.parentApplication.cs.login(user, pwd);
 // Add result and fault handlers.
                    // Add result and fault handlers.
 this.parentApplication.token.addResponder(new AsyncResponder(LoginResultEvent, LoginFaultEvent));
                    this.parentApplication.token.addResponder(new AsyncResponder(LoginResultEvent, LoginFaultEvent));
 }
                }
 }
            }

 // Handle successful login.
            // Handle successful login.
 private function LoginResultEvent(event:ResultEvent, token:Object = null):void {
            private function LoginResultEvent(event:ResultEvent, token:Object = null):void {
 switch (event.result) {
                switch (event.result) {
 case "success":
                    case "success":
 break;
                        break;
 default:
                    default:
 }
                }
 var switchEvent:SwitchModuleEvent = new SwitchModuleEvent(SwitchModuleEvent.SWITCH, "");
                var switchEvent:SwitchModuleEvent = new SwitchModuleEvent(SwitchModuleEvent.SWITCH, "");
 dispatchEvent(switchEvent);
                dispatchEvent(switchEvent);
 }
            }

 // Handle login failure.
            // Handle login failure.
 private function LoginFaultEvent(event:FaultEvent, token:Object = null):void {
            private function LoginFaultEvent(event:FaultEvent, token:Object = null):void {
 trace(event.fault.faultCode);
                trace(event.fault.faultCode);
 switch (event.fault.faultCode) {
                switch (event.fault.faultCode) {
 case "Client.Authentication":
                    case "Client.Authentication":
 default:
                    default:
 Alert.show("Login failure: " + event.fault.faultString);
                        Alert.show("Login failure: " + event.fault.faultString);
 }
                }
 }
            }

 ]]>
        ]]>
 </fx:Script>
    </fx:Script>
 <mx:HBox x="100" y = "100">
    <mx:HBox x="100" y = "100">
 <component:RequiredLabel text = "user name:" isRequired = "true"/>
        <component:RequiredLabel text = "user name:" isRequired = "true"/>
 <s:TextInput id = "username" text = "admin"/>
        <s:TextInput id = "username" text = "admin"/>
 <component:RequiredLabel text = "password:" isRequired = "true"/>
        <component:RequiredLabel text = "password:" isRequired = "true"/>
 <s:TextInput id = "password" text = "" displayAsPassword = "true"/>
        <s:TextInput id = "password" text = "" displayAsPassword = "true"/>
 <s:Button label = "Login" click = "login();"/>
        <s:Button label = "Login" click = "login();"/>
 </mx:HBox>
    </mx:HBox>
 <s:Label x="100" y="130" text="Notes: You can use any user to register, and if you wanna access add or update functions, you need to user 'admin' user. "/>
    <s:Label x="100" y="130" text="Notes: You can use any user to register, and if you wanna access add or update functions, you need to user 'admin' user. "/>

 </mx:Module>
</mx:Module>

主要是用ChannelSet.login()方法和logout()方法进行登录与登出,登出的详细代码在此就省略了,有兴趣的朋友可以自己试试或者参考Adobe官方的《BlazeDS dev guide》。
小结一下:
1,解决了与Tomcat等中间件绑定的问题。
2,可以将用户和角色的对应关系存放在RDBMS中,并可以开发相应功能进行动态编辑而不需要重启中间件。
3,可以自定义登录界面,而不是借助于浏览器的窗口和HTTP BASIC方式。
4,不好之处是,由于角色已经在代码或配置中绑定,无法动态新增角色。
大家如有不清楚与需要讨论的地方,欢迎留言!
本Blog所有内容不得随意转载,版权属于作者所有。如需转载请与作者联系( fastzch@163.com    QQ:9184314)。
未经许可的转载,本人保留一切法律权益。
一直以来,发现有某些人完全不尊重我的劳动成果,随意转载,提醒一下那些人小心哪天惹上官司。