您的位置:首页 > 编程语言 > Java开发

Acegi + Spring + Hibernate + Struts 2搭建基于角色的权限控制系统

2009-10-20 10:07 537 查看
安全永远是WEB应用系统必须面对的头等大事, 也是最头疼的事, 其实安全系统就只包括两个问题: 认证和授权.

以前做些网站系统, 安全检测逻辑都在放在须要安全控制的代码前面,
这样做有很多不好的地方, 重复多次的编码就不用说了,
代码移植性, 重用性都得不到体现,
安全检测逻辑要永远和业务逻辑放在一起.

那么, 能不能够在进入方法前就调用一些安全检测? 其实Spring AOP就是这个思想, 那么又如何实现安全检测呢? Spring Acegi Security 框架就是做这个事情.

本文主要是讨论下在已有的SSH系统中, 如何使用Acegi作为安全框架实现基于角色的权限控制(Role Based Access Control RBAC) ,
本文主要是以Java 5注解的形式来配置安全框架, 大大减化配置和操作.

本文的主要参考资料: <Spring 2.0 核心技术与最佳实践> 第10章 (Spring Acegi 安全框架)

<精通Spring 2.X -- 企业应用开发详解> 第17章 (使用Acegi
实施应用系统安全)

acegi-security-1.0.6 官方文档

说明: 本文介绍的是RBAC, 在官方文档的基础上有所扩展或改动, 以更适合WEB应用系统.
其实我觉得大多数的网站基于角色已经足够了, 一般都没必要基于权限.

文章开始:
一. 下载所要的软件或JAR包:

我的相关配置是: Java 5, Tomcat 5.5.26, Struts 2.0.11, Spring 2.5.1, Hibernate 3.2,
Acegi 1.0.6
二. 建立相关的数据库:

数据表: 用户信息表User: id, enable, user_name,
user_pass, email_box

角色信息表RoleInfo: id, role_name, role_title, descp

用户与角色关联表(用户与角色是多对多关系)UserRole: user_id, user_name,
role_id, role_name

并在这三个表中插入相关的数据, 我是定义了两种角色(role_name): ROLE_USER,
ROLE_ADMIN

和三个用户, 一个用户角色为: ROLE_USER,
ROLE_ADMIN

另一个用户角色为: ROLE_USER

第三个没有角色.
二. 修改配置文件:

其实对Acegi框架的应用难点就在配置文件, 所以要特别注意了:

在 src 建立Acegi的配置文件:
acegi-security.xml 当然这个文件的名称是可以任意的.

acegi-security.xml 说白了就是配置: 安全拦截器, 认证管理器,
决策管理器.

其内容如下:
<?xml version="1.0"
encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- =========================
认证管理器 ========================= -->
<bean
id="authenticationManager"
class="org.acegisecurity.providers.ProviderManager">
<property
name="providers">
<list>
<ref
bean="daoAuthenticationProvider" />
<ref
bean="rememberMeAuthenticationProvider" />

</list>
</property>
</bean>
<!-- 基于DAO验证的AuthenticationProvider
-->
<bean id="daoAuthenticationProvider"
class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property
name="userDetailsService" ref="userDetailsService"
/>
</bean>
<bean id="userDetailsService"
class="org.ymcn.security.AcegiUserDeitailsService">
<property
name="userDao" ref="userDao" />
<property name="userRoleDao"
ref="userRoleDao" />
</bean>
<bean id="rememberMeAuthenticationProvider"
class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
<property
name="key" value="obullxl@163.com" />
</bean>
<bean
id="rememberMeServices"
class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
<property
name="userDetailsService" ref="userDetailsService" />
<property
name="parameter" value="j_remember_me"
/>
<property name="key" value="obullxl@163.com" />
<property name="tokenValiditySeconds"
value="31536000" />
</bean>
<!-- ========================= 决策管理器 ========================= -->
<bean
id="accessDecisionManager"
class="org.acegisecurity.vote.AffirmativeBased">
<property
name="decisionVoters">
<list>
<ref bean="roleVoter"
/>
</list>
</property>
<!--
是否全部弃权就通过
-->
<property name="allowIfAllAbstainDecisions" value="false"
/>
</bean>
<bean id="roleVoter"
class="org.acegisecurity.vote.RoleVoter">
<property name="rolePrefix"
value="ROLE_" />
</bean>
<!-- ========================= 过滤器链 ========================= -->
<bean id="filterChainProxy"
class="org.acegisecurity.util.FilterChainProxy">
<property
name="filterInvocationDefinitionSource">
<value>

CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT

/**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,rememberMeFilter,exceptionFilter,securityInterceptor

</value>
</property>
</bean>
<bean
id="httpSessionContextIntegrationFilter"
class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"
/>
<bean id="logoutFilter"
class="org.acegisecurity.ui.logout.LogoutFilter">
<!--
登录退出后的URL
-->
<constructor-arg value="/"
/>
<constructor-arg>
<list>
<ref
bean="rememberMeServices" />
<bean
class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler" />

</list>
</constructor-arg>
<!-- 登录退出的URL -->
<property name="filterProcessesUrl" value="/j_logout.j"
/>
</bean>
<bean id="authenticationProcessingFilter"
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property
name="authenticationManager" ref="authenticationManager" />
<!--
登录失败后的URL -->
<property name="authenticationFailureUrl"
value="/login.jsp?msg=%E6%97%A0%E6%95%88%E7%9A%84%E7%94%A8%E6%88%B7%E5%90%8D%E6%88%96%E5%8F%A3%E4%BB%A4"
/>
<!-- 登录成功后的URL -->
<property
name="defaultTargetUrl" value="/user/cmd.jsp" />
<!--
登录的URL
-->
<property name="filterProcessesUrl"
value="/j_login.j" />
<property name="rememberMeServices"
ref="rememberMeServices" />
</bean>
<bean
id="rememberMeFilter"
class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
<property
name="authenticationManager" ref="authenticationManager" />
<property
name="rememberMeServices" ref="rememberMeServices"
/>
</bean>
<bean id="exceptionFilter"
class="org.acegisecurity.ui.ExceptionTranslationFilter">
<!--
出现AuthenticationException时的登录入口 -->
<property
name="authenticationEntryPoint">
<bean
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl" value="/login.jsp"
/>
<property name="forceHttps" value="false" />

</bean>
</property>
<!-- 出现AccessDeniedException时的Handler -->
<property
name="accessDeniedHandler">
<bean
class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
<property name="errorPage" value="/denied.jsp"
/>

</bean>
</property>
</bean>
<bean
id="securityInterceptor"
class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property
name="authenticationManager" ref="authenticationManager" />
<property
name="accessDecisionManager" ref="accessDecisionManager" />
<property
name="objectDefinitionSource">
<value>

CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT

/admin/**=ROLE_ADMIN
/user/**=ROLE_USER

/cart/previeworder*=ROLE_USER

</value>
</property>
</bean>
</beans>

在上面的配置文件中, 红色部分要特别注意,
其余的内容都差不多了.
<bean id="userDetailsService" class="org.ymcn.security.AcegiUserDeitailsService">
<property
name="userDao" ref="userDao" />
<property name="userRoleDao"
ref="userRoleDao" />
</bean>

在整个应用的安全控制中, 我们唯一要编写代码的类就是: org.ymcn.security.AcegiUserDeitailsService
就连登录和登出的代码也不要了.
三. 修改 web.xml, 增加安全控制过滤链.
<filter>

<filter-name>acegiFilterChain</filter-name>

<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>

<init-param>

<param-name>targetClass</param-name>

<param-value>org.acegisecurity.util.FilterChainProxy</param-value>

</init-param>

</filter>

<filter-mapping>

<filter-name>acegiFilterChain</filter-name>

<url-pattern>*.j</url-pattern>
</filter-mapping>

注意: 这个过滤器一定要在MVC转发过滤器的前面!!!!
四. 在 applicationContext.xml 中增加
Acegi安全控制拦截器 和 Spring的自动代理功能实现AOP代理
<!-- Acegi安全控制拦截器 -->
<bean id="serviceSecurityInterceptor"
class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
<property
name="validateConfigAttributes" value="true" />
<property
name="authenticationManager" ref="authenticationManager" />
<property
name="accessDecisionManager" ref="accessDecisionManager" />
<property
name="objectDefinitionSource">
<bean
class="org.acegisecurity.intercept.method.MethodDefinitionAttributes">

<property name="attributes">
<bean
class="org.acegisecurity.annotation.SecurityAnnotationAttributes" />

</property>

</bean>
</property>
</bean>
<!-- 利用Spring的自动代理功能实现AOP代理
-->
<bean id="autoProxyCreator"
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property
name="interceptorNames">
<list>

<value>transactionInterceptor</value>

<value>serviceSecurityInterceptor</value>

</list>
</property>
<property name="beanNames">

<list>
<value>userService</value>

<value>mailService</value>

</list>
</property>
</bean>
五. 编写在利用Acegi框架唯一要我们编写的类 AcegiUserDeitailsService.java
package org.ymcn.security;
import java.util.List;
import org.acegisecurity.GrantedAuthority;
import
org.acegisecurity.GrantedAuthorityImpl;
import
org.acegisecurity.userdetails.UserDetails;
import
org.acegisecurity.userdetails.UserDetailsService;
import
org.acegisecurity.userdetails.UsernameNotFoundException;
import
org.apache.commons.logging.Log;
import
org.apache.commons.logging.LogFactory;
import
org.springframework.dao.DataAccessException;
import
org.ymcn.dao.UserDao;
import org.ymcn.dao.UserRoleDao;
import
org.ymcn.model.User;
import org.ymcn.model.UserRole;
public class AcegiUserDeitailsService implements
UserDetailsService {
private final Log LOG =
LogFactory.getLog(AcegiUserDeitailsService.class);
/* 依赖注入 */
private UserDao userDao;
private UserRoleDao
userRoleDao;

public void setUserDao(UserDao userDao) {
this.userDao
= userDao;
}
public void setUserRoleDao(UserRoleDao userRoleDao) {

this.userRoleDao = userRoleDao;
}

/* 用户所有的权限 */
//private final
List<GrantedAuthority> grantedAuthList = new
ArrayList<GrantedAuthority>(6);
private GrantedAuthority[]
grantedAuthArray;

public UserDetails loadUserByUsername(String
userName)
throws UsernameNotFoundException, DataAccessException {

if(LOG.isDebugEnabled()) {
LOG.debug("Loading UserDetails of userName: "
+ userName);
}
/* 取得用户 */
User user =
userDao.getUserByName(userName);
if(user == null) {

LOG.warn("UserDetails load failed: No such UserRole with userName: " +
userName);
throw new UsernameNotFoundException("User name is not
found.");
}
/* 取得所有用户权限 */
List<UserRole>
userRoleList = userRoleDao.getUserRoleByUserName(userName);

if(userRoleList == null || userRoleList.size() == 0) {
LOG.warn("UserRole
load failed: No such UserRole with userName: " + userName);
throw
new UsernameNotFoundException("UserRole is not found.");
}
/*
取得用户的所有角色 */

int size = userRoleList.size();
grantedAuthArray = new
GrantedAuthority[size];
int j = 0;
for(int i = 0; i < size; i++)
{
UserRole userRole = userRoleList.get(i);
if(userRole != null)
{
this.grantedAuthArray[j++] = new
GrantedAuthorityImpl(userRole.getRoleName().toUpperCase());
}

}
LOG.info("UserName: " + userName + " loaded successfully.");

return new org.acegisecurity.userdetails.User(userName,
user.getUserPass(),
true, true, true, true,
this.grantedAuthArray);
}
}
六. 在业务逻辑代码中利用Java 5注释实现安全控制
@Secured({"ROLE_USER"})
void
sendSimpleMail(Long userId);
@Secured({"ROLE_ADMIN"})
void
sendAttachmentMail() throws Exception;

其实就是在需要安全控制的方法前加上: @Secured({"角色名"}),
非常的简单
七. 整个工作完成

Acegi框架完全是一种可插拔式的, 完全可以在原有的系统中加个一个配置文件, 和在每个方法前加上: @Secured({"角色名"}) 就可完成.
上面的 AcegiUserDeitailsService.java
中的有 UserDao, UserRoleDao, 我想一看就知道它们是干什么的了, 这完全取决于个人的实现, 与Acegi无关,
它仅仅只要返回一个 return
new org.acegisecurity.userdetails.User(userName, user.getUserPass(),

true, true,
true, true,
this.grantedAuthArray) 就可以了.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: