随笔-193  评论-715  文章-1  trackbacks-0
在昨天的文章《BlazeDS结合Tomcat进行权限控制》中,讲述了BlazeDS如何在Tomcat环境下进行权限控制,但是我们不难发现有很多缺点,甚至有一些都是致命的,比如不能跨平台(中间件),甚至不能跨版本,还有用户名角色等配置不能自定义配置在RDBMS,文件或其它地方等。所以今天我要分享给大家如何摆脱这些限制,避免这些不利因素。

所幸的是,BlazeDS的设计者们已经为我们想到了这些,我们只需要采用自定义认证的方式即可,具体实现时,需要实现flex.messaging.security.LoginCommand这个接口。我们不妨先来看看这个接口的定义(直接上代码了):
package flex.messaging.security;

import javax.servlet.ServletConfig;

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

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

public interface LoginCommand
{
    
/**
     * Called to initialize a login command prior to authentication/authorization requests.
     * 
     * 
@param config The servlet configuration for MessageBrokerServlet.  
     
*/

    
void start(ServletConfig config);

    
/**
     * Called to free up resources used by the login command.
     
*/

    
void stop();

    
/**
     * The gateway calls this method to perform programmatic, custom authentication.
     * <p>
     * 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.
     * </p>
     *
     * 
@param username    The principal being authenticated
     * 
@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 
     
*/

    Principal doAuthentication(String username, Object credentials);

    
/**
     * The gateway calls this method to perform programmatic authorization.
     * <p>
     * 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
     * HttpServletRequest.isUserInRole(String role).
     * </p>
     *
     * 
@param principal The principal being checked for authorization
     * 
@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
     
*/

    
boolean doAuthorization(Principal principal, List roles);

    
/**
     * Attempts to log a user out from their session.
     *
     * NOTE: May not be possible on all application servers.
     * 
@param principal The principal to logout.
     * 
@return true when logout is successful
     
*/

    
boolean logout(Principal principal);
}

最主要的3个方法:doAuthentication()用来认证,doAuthorization()用来进行授权,logout()用来执行登出时的动作,主要是释放Principal,关于Principal的概念,直接来自于Java,如需进一步了解,也可以参考JAAS的相关知识,我在之前的学习笔记《JAAS Study Note 》中也简单的提及过,在此就不再多讲了,废话不多说了,直接上步骤了,这应该是大家喜欢的方式:

1,实现一个自定义的Principal:
package com.robin.common.security;

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

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

    
private String name;
    
private List<String> subjects;

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

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

        
this.name = name;
    }


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


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


    
public String getName() {
        
return name;
    }


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

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


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

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

        
if (this == o)
            
return true;

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

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


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

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

}


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

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

import javax.servlet.ServletConfig;

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

public class RdbmsLoginCommand implements LoginCommand {

    
public Principal doAuthentication(String username, Object credentials) {
        String password 
= extractPassword(credentials);
        System.out.println(
"###Username:"+username+",Password:"+password+"###");
        
//TODO: use this user name and password to validate from RDBMS.
        
//And then, query user's roles and set to principle.
        if(true){
            UserPrincipal principal 
= new UserPrincipal(username);
            List
<String> subjects = new ArrayList<String>();
            subjects.add(
"ROLE_AD");
            
if(username.equals("admin")){
                subjects.add(
"ADMIN");
            }

            principal.setSubjects(subjects);
            
return principal;
        }
 else{
            
return null;
        }

    }


    
public boolean doAuthorization(Principal principal, List roles) {
        System.out.println(principal
+"##########################");
        UserPrincipal p
=(UserPrincipal)principal;
        List
<String> subjects = p.getSubjects();
        
for (int i = 0; i < subjects.size(); i++{
            String subject
= subjects.get(i);
            
for (int j = 0; j < roles.size(); j++{
                System.out.print(roles.get(j)
+"$$$");
                
if(subject.equals(roles.get(j))){
                    
return true;
                }

            }

        }

        
return false;
    }


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


    
public void start(ServletConfig arg0) {
        
    }


    
public void stop() {

    }

    
    
private String extractPassword(Object credentials)
    
{
        String password 
= null;
        
if (credentials instanceof String)
        
{
            password 
= (String)credentials;
        }

        
else if (credentials instanceof Map)
        
{
            password 
= (String)((Map)credentials).get(MessageIOConstants.SECURITY_CREDENTIALS);
        }

        
return password;
    }


}

这些代码都非常简单,我想就不用我再解释了。

3,在BlazeDS中配置security-constraint,先配置service-config.xml:
<security>
        
<login-command class="com.robin.common.security.RdbmsLoginCommand" server="all"/>
        
<security-constraint id="administrators">
        
<auth-method>Custom</auth-method>
            
<roles>
                
<role>ADMIN</role>
            
</roles>
        
</security-constraint>
        
<security-constraint id="users">
            
<auth-method>Custom</auth-method>
            
<roles>
                
<role>ROLE_AD</role>
                
<role>ADMIN</role>
            
</roles>
        
</security-constraint>
    
</security>

然后在remote-config.xml中配置每个destination的授权规则:
<destination id="DomainService">
        
<properties>
            
<source>com.robin.service.domain.DomainService</source>
            
<include-methods>
            
<method name="getAllDomains"/>
            
<method name="addOrUpdateDomain" security-constraint="administrators"/>
            
</include-methods>
        
</properties>
        
<security>
            
<security-constraint ref="users"/>
        
</security>
    
</destination>

4,服务端的配置就大功告成了,现在来看看客户端如何实现登录:
<?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%"
           xmlns:component 
= "com.robin.common.component.*">
    
    
<fx:Declarations>
        
<mx:RemoteObject id = "loginService" destination = "remoting_AMF_SecurityConstraint_Custom" showBusyCursor = "true" fault = "Alert.show(event.fault.faultString, 'Error');"/>
    
</fx:Declarations>
    
<fx:Script>
        
<![CDATA[
            import com.robin.common.events.SwitchModuleEvent;
            import mx.controls.Alert;
            import mx.messaging.ChannelSet;
            import mx.messaging.config.ServerConfig;
            import mx.rpc.AsyncResponder;
            import mx.rpc.AsyncToken;
            import mx.rpc.events.FaultEvent;
            import mx.rpc.events.ResultEvent;

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

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

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

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

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

        
]]>
    
</fx:Script>
    
<mx:HBox x="100" y = "100">
        
<component:RequiredLabel text = "user name:" isRequired = "true"/>
        
<s:TextInput id = "username" text = "admin"/>
        
<component:RequiredLabel text = "password:" isRequired = "true"/>
        
<s:TextInput id = "password" text = "" displayAsPassword = "true"/>
        
<s:Button label = "Login" click = "login();"/>
    
</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. "/>

</mx:Module>

主要是用ChannelSet.login()方法和logout()方法进行登录与登出,登出的详细代码在此就省略了,有兴趣的朋友可以自己试试或者参考Adobe官方的《BlazeDS dev guide》。

小结一下:
1,解决了与Tomcat等中间件绑定的问题。
2,可以将用户和角色的对应关系存放在RDBMS中,并可以开发相应功能进行动态编辑而不需要重启中间件。
3,可以自定义登录界面,而不是借助于浏览器的窗口和HTTP BASIC方式。
4,不好之处是,由于角色已经在代码或配置中绑定,无法动态新增角色。


大家如有不清楚与需要讨论的地方,欢迎留言!

本Blog所有内容不得随意转载,版权属于作者所有。如需转载请与作者联系( fastzch@163.com    QQ:9184314)。
未经许可的转载,本人保留一切法律权益。
一直以来,发现有某些人完全不尊重我的劳动成果,随意转载,提醒一下那些人小心哪天惹上官司。



posted on 2010-04-28 09:56 Robin's Programming World 阅读(2967) 评论(3)  编辑  收藏 所属分类: JavaFlex & Flash

评论:
# re: BlazeDS自定义认证与权限控制 2010-04-28 11:25 | 俏物悄语
看见就撒娇的撒  回复  更多评论
  
# re: BlazeDS自定义认证与权限控制[未登录] 2014-03-11 13:16 | yxy
<mx:RemoteObject id = "loginService" destination = "remoting_AMF_SecurityConstraint_Custom"这里的destination在哪里定义?是干什么的  回复  更多评论
  
# re: BlazeDS自定义认证与权限控制 2015-06-14 14:08 | 内谁
NB!!!  回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航: