基于数据库的Struts Menu,Spring Security整合

Appfuse 2.1.0M1整合了Struts Menu和Spring Security 2.0.4.主要分两个部分实现了权限控制。
Spring Security–用户对权限的控制
1 通过实现Spring Security的一组接口(UserDetails,UserDetailsService, GrantedAuthority …),Appfuse实现了User和Authority之间的关联。用户登录的时候就可以从Database中获取其对应的Authority进行后续的权限判断。
2 通过security.xml, Appfuse定义了Authority对URL,Method,Field的控制。
这其中的一个问题是Authority名称需要hard code在security.xml中,不够灵活。这个问题不在本文的讨论范围内。

Struts Menu–权限对菜单的控制
Struts Menu的menu-config.xml文件定义了菜单内容以及对应的Authority名称。Appfuse通过这样实现了权限对菜单的控制。
这其中的问题是Authority名称需要hard code在menu-config.xml,不能够修改。这也是本文希望解决的问题。

解决思路
同样是Matt的作品,Struts Menu非常的强大灵活。
http://struts-menu.sourceforge.net/security.html
http://demo.raibledesigns.com/struts-menu/dynamicMenu.jsp
Dynamic Menu本身没有实现对权限的控制,但是它提供了一个基本的扩展的思路。

解决办法
Menu类主要基于Dynamic Menu的db schema,增加了与Role(实现了GrantedAuthority接口)的多对多关联,关联方式是Eager,可以一次获得所有的关联关系。

/**
*
*/
package com.shine.model;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
/**
* @author xiluo2
*
*/
@Entity
@Table(name = "menu")
public class Menu extends BaseObject {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
private String parentName;
private String title;
private String location;
private Set roles = new HashSet();
/**
* @return the id
*/
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
/**
* @param id the id to set
*/
public void setId(Long id) {
this.id = id;
}
/**
* @return the name
*/
@Column(name="name", length=30)
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the parentName
*/
@Column(name="parent_name", length=30)
public String getParentName() {
return parentName;
}
/**
* @param parentName the parentName to set
*/
public void setParentName(String parentName) {
this.parentName = parentName;
}
/**
* @return the title
*/
@Column(name="title", length=50)
public String getTitle() {
return title;
}
/**
* @param title the title to set
*/
public void setTitle(String title) {
this.title = title;
}
/**
* @return the location
*/
@Column(name="location", length=255)
public String getLocation() {
return location;
}
/**
* @param location the location to set
*/
public void setLocation(String location) {
this.location = location;
}
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "menu_role",
joinColumns = {@JoinColumn(name = "menu_id")},
inverseJoinColumns = @JoinColumn(name = "role_id")
)
public Set getRoles() {
return roles;
}
public void setRoles(Set roles) {
this.roles = roles;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 0;
result = prime * result + ((location == null) ? 0 : location.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
result = prime * result + ((parentName == null) ? 0 : parentName.hashCode());
result = prime * result + ((title == null) ? 0 : title.hashCode());
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (getClass() != obj.getClass())
return false;
Menu other = (Menu) obj;
if (location == null) {
if (other.location != null)
return false;
} else if (!location.equals(other.location))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (parentName == null) {
if (other.parentName != null)
return false;
} else if (!parentName.equals(other.parentName))
return false;
if (title == null) {
if (other.title != null)
return false;
} else if (!title.equals(other.title))
return false;
return true;
}
/**
* {@inheritDoc}
*/
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SIMPLE_STYLE)
.append(this.name)
.append(this.title)
.append(this.location)
.toString();
}
}

通过Appfuse可以自动生成对应的MenuDao, MenuManager。
然后就是在系统启动的加载MenuRepository.主要是在StartupListener的setupConext方法中添加下列代码。

MenuManager menuManager = (MenuManager) ctx.getBean("menuManager");
List<Menu> menuList = menuManager.getAllMenus();
MenuRepository repository = new MenuRepository();
repository.setServletContext(context);
MenuDisplayerMapping displayerMapping = new MenuDisplayerMapping();
displayerMapping.setName("Velocity"); displayerMapping.setType("net.sf.navigator.displayer.VelocityMenuDisplayer");
repository.addMenuDisplayerMapping(displayerMapping);
for (int i=0; i < menuList.size() ; i++) {
MenuComponent mc = new MenuComponent();
Menu menuObject = menuList.get(i);
String name = menuObject.getName();
mc.setName(name);
String parent = menuObject.getParentName();
if (parent != null) {
MenuComponent parentMenu = repository.getMenu(parent);
if (parentMenu == null) {
// create a temporary parentMenu
parentMenu = new MenuComponent();
parentMenu.setName(parent);
repository.addMenu(parentMenu);
}
mc.setParent(parentMenu);
}
String title = menuObject.getTitle();
mc.setTitle(title);
String location = menuObject.getLocation();
mc.setLocation(location);
//Generate the role list string
String rolestring = null;
if ( menuObject.getRoles() != null && menuObject.getRoles().size() > 0 ) {
StringBuilder buffer = new StringBuilder();
Iterator iterator = menuObject.getRoles().iterator();
while ( iterator.hasNext() ){
buffer.append(iterator.next().getName());
buffer.append(",");
}
rolestring = buffer.toString();
}
mc.setRoles(rolestring);
repository.addMenu(mc);
}
context.setAttribute(MenuRepository.MENU_REPOSITORY_KEY, repository);

大致的流程和Dynamic Menu一样。主要做了两点修改。
1)将每个Menu对应的Set拼接成字符串,存在MenuComponent中。
2)自行创建MenuDisplayerMapping ,没有利用defaultMenuDisplayerMapping.(主要是StartupListener的时候好像还没有defaultMenuDisplayerMapping,不知道该怎么调整顺序)

OK了。这样User->Role(Authority)->Menu的关联都可以通过数据库方便的操作了。

 

无觅相关文章插件,快速提升流量