隨筆-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 閱讀(3039) 評論(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!!!  回復  更多評論
            
          主站蜘蛛池模板: 明水县| 金堂县| 通州区| 庆安县| 上犹县| 罗平县| 小金县| 古田县| 本溪| 六安市| 福安市| 炎陵县| 同德县| 肃宁县| 陵川县| 乌海市| 五常市| 庆城县| 台东市| 类乌齐县| 滁州市| 齐齐哈尔市| 乐陵市| 绍兴县| 沁源县| 宣威市| 滁州市| 班戈县| 康定县| 乐亭县| 凌云县| 鄂伦春自治旗| 邯郸县| 义乌市| 德保县| 香格里拉县| 尚志市| 寿光市| 汕头市| 南充市| 沐川县|