在昨天的文章《
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)。
未经许可的转载,本人保留一切法律权益。
一直以来,发现有某些人完全不尊重我的劳动成果,随意转载,提醒一下那些人小心哪天惹上官司。