`
xgbjmxn
  • 浏览: 261686 次
  • 性别: Icon_minigender_1
  • 来自: 新乡
社区版块
存档分类
最新评论

集成Acegi到自己的项目

 
阅读更多


一. 简单介绍

1.1 本文目的

集成Acegi到自己的项目中, 并且将用户信息和权限放到数据库, 提供方法允许权限动态变化,变化后自动加载最新的权限

本文介绍Acegi例子的时候采用的是acegi-security-samples-tutorial-1.0.6.war

阅读本文需要对Spring有一定的了解, 如果你还没有接触过, 有些地方可能不容易理解, 这时候可能需要参考本文后附的Spring地址, 先了解一下Spring的基本知识.

本文使用的是Mysql数据库, 如果你使用其他的数据库, 可能需要修改相应的SQL.

1.2 安装与配置

项目主页: http://www.acegisecurity.org/

下载地址: http://sourceforge.net/project/showfiles.php?group_id=104215

解压文件后, 将acegi-security-samples-tutorial-1.0.6.war复制Your_Tomcat_Path/webapps/

启动Tomcat, 访问http://localhost:8080/acegi-security-samples-tutorial-1.0.6/

点击页面上任何一个链接,都需要用户登录后访问, 可以在页面上看到可用的用户名和密码.

二. 开始集成到自己的程序中

2.1 将用户和角色放在数据库中

可能是为了演示方便, 简单的展示Acegi如何控制权限, 而不依赖于任何数据库, ACEGI给出的例子采用InMemoryDaoImpl获取用户信息, 用户和角色信息放在WEB-INF/users.properties 文件中, InMemoryDaoImpl 一次性的从该配置文件中读出用户和角色信息, 格式是: 用户名=密码, 角色名, 如第一行是:

marissa=koala,ROLE_SUPERVISOR

就是说marissa的密码是koala, 并且他的角色是ROLE_SUPERVISOR

对这个文件的解析是通过applicationContext-acegi-security.xml中如下的设置进行的:

<!-- UserDetailsService is the most commonly frequently Acegi Security interface implemented by end users -->
<bean id="userDetailsService"
class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userProperties">
<bean
class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location"
value="classpath:users.properties" />
</bean>
</property>
</bean>




除了InMemoryDaoImpl之外, ACEGI还提供了Jdbc和 ldap的支持, 由于使用数据库进行验证比较常见, 下面仅就jdbc实现做出介绍.

不管是InMemoryDaoImpl还是JdbcDaoImpl都是实现了UserDetailsService接口, 而这个接口里只定义了一个方法: UserDetails loadUserByUsername(String username) 就是根据用户名加载UserDetails对象, UserDetails也是一个接口, 定义了一个用户所需要的基本信息, 包括: username, password, authorities等信息

2.1.1 直接使用JdbcDaoImpl 访问数据库中的用户信息

如果ACEGI提供的信息满足你的需要, 也就是说你只需要用户的username, password等信息, 你可以直接使用ACEGI提供的Schema, 这样, 不需要任何变动, JdbcDaoImpl就可以使用了.

如果你的数据库已经定义好了, 或者不想使用ACEGI提供的Schema,那么你也可以自定义JdbcDaoImpl的查询语句

        <property name="usersByUsernameQuery">
<value>
SELECT email, password, enabled from user u where email = ?
</value>
</property>
<property name="authoritiesByUsernameQuery">
<value>
SELECT u.email, r.role_name FROM user_role ur, user u, role r WHERE
ur.user_id = u.user_id and ur.role_id = r.role_id and u.email = ?
</value>
</property>
2.1.2 扩展JdbcDaoImpl获取更多用户信息

如果上面提到的定制查询SQL语句不能提供足够的灵活性, 那么你可能就需要定义一个JdbcDaoImpl的子类, 如果变动不大, 通过覆盖initMappingSqlQueries方法重新定义MappingSqlQuery的实例. 而如果你需要获取更多信息, 比如userId, companyId等, 那就需要做更多的改动, 第一种改动不大, 所以不具体介绍, 下面以第二种改动为例,介绍如何实现这种需求.

我们需要三张表User, Role, User_Role, 具体的SQL如下:

#
# Structure for the `role` table :
#
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`role_id` int(11) NOT NULL auto_increment,
`role_name` varchar(50) default NULL,
`description` varchar(20) default NULL,
`enabled` tinyint(1) NOT NULL default '1',
PRIMARY KEY  (`role_id`)
);
#
# Structure for the `user` table :
#
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`user_id` int(11) NOT NULL auto_increment,
`company_id` int(11) default NULL,
`email` varchar(200) default NULL,
`password` varchar(10) default NULL,
`enabled` tinyint(1) default NULL,
PRIMARY KEY  (`user_id`)
);
#
# Structure for the `user_role` table :
#
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`user_role_id` int(11) NOT NULL auto_increment,
`user_id` varchar(50) NOT NULL,
`role_id` int(11) NOT NULL,
PRIMARY KEY  (`user_role_id`)
);

前面讲过, UserDetailsService接口中只定义了一个方法: UserDetails loadUserByUsername(String username), UserDetails中不存在我们需要的userId 和companyId等信息, 所以我们首先需要扩展UserDetails接口, 并扩展org.acegisecurity.userdetails.User:

IUserDetails.java

package org.security;
import org.acegisecurity.GrantedAuthority;
/**
* The class <code>IUserDetails</code> extends the org.acegisecurity.userdetails.UserDetails interface, and provides additional userId, companyId information<br><br>
* @author wade
* @see UserDetails
*/
public interface IUserDetails extends org.acegisecurity.userdetails.UserDetails{
public int getUserId();
public void setUserId(int user_id);
public int getCompanyId();
public void setCompanyId(int company_id);
public String getUsername();
public void setUsername(String username);
public GrantedAuthority[] getAuthorities();
public void setAuthorities(GrantedAuthority[] authorities);
}




UserDetailsImpl.java

package org.security;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.userdetails.User;
/**
* The class <code>UserDetailsImpl</code> extends the org.acegisecurity.userdetails.User class, and provides additional userId, companyId information
* @author wade
*
* @see IUserDetails, User
*/
public class UserDetailsImpl extends User implements IUserDetails{
private int user_id;
private int company_id;
private String username;
private GrantedAuthority[] authorities;
public UserDetailsImpl(String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked, GrantedAuthority[] authorities)
throws IllegalArgumentException {
super(username, password, enabled, accountNonExpired, credentialsNonExpired,
accountNonLocked, authorities);
setUsername(username);
setAuthorities(authorities);
}
public UserDetailsImpl(int userid, int companyid, String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked, GrantedAuthority[] authorities)
throws IllegalArgumentException {
super(username, password, enabled, accountNonExpired, credentialsNonExpired,
accountNonLocked, authorities);
this.user_id = userid;
this.company_id = companyid;
setUsername(username);
setAuthorities(authorities);
}
public int getUserId() {
return user_id;
}
public void setUserId(int user_id) {
this.user_id = user_id;
}
public int getCompanyId() {
return company_id;
}
public void setCompanyId(int company_id) {
this.company_id = company_id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public GrantedAuthority[] getAuthorities() {
return authorities;
}
public void setAuthorities(GrantedAuthority[] authorities) {
this.authorities = authorities;
}
}




到此为止, 我们已经准备好了存放用户信息的类, 下面就开始动手修改取用户数据的代码.

假设我们用下面的SQL取用户信息:

SELECT u.user_id, u.company_id, email, password, enabled
FROM role r, user_role ur, user u
WHERE r.role_id = ur.role_id
and ur.user_id = u.user_id
and email = ?
limit 1 
用下面的SQL取用户具有的Role列表

SELECT u.email, r.role_name
FROM user_role ur, user u, role r
WHERE ur.user_id = u.user_id
and ur.role_id = r.role_id
and u.email = ? 



我们需要修改的主要是两部分:

1. 取用户和用户角色的MappingSqlQuery, 增加了查询的userId和companyId.

2. loadUserByUsername方法, 修改了返回的对象类型,和很少的内部代码.

AcegiJdbcDaoImpl.java

package org.security.acegi;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
import javax.sql.DataSource;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.userdetails.UsernameNotFoundException;
import org.acegisecurity.userdetails.jdbc.JdbcDaoImpl;
import org.security.IUserDetails;
import org.security.UserDetailsImpl;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.MappingSqlQuery;
/**
* The class AcegiJdbcDaoImpl provides the method to get IUserDetail information from db which contains userId, companyId and UserDetail information.
*
* @author wade
*
*/
public class AcegiJdbcDaoImpl extends JdbcDaoImpl {
public static final String DEF_USERS_BY_USERNAME_QUERY =
"SELECT u.user_id, u.company_id, email, password, enabled from role r, user_role ur, user u where r.role_id = ur.role_id and ur.user_id = u.user_id and email = ? limit 1";
public static final String DEF_AUTHORITIES_BY_USERNAME_QUERY =
"SELECT username,authority FROM authorities WHERE username = ?";
protected MappingSqlQuery rolesByUsernameMapping;
protected MappingSqlQuery usersByNameMapping;
private String authoritiesByUsernameQuery;
private String rolePrefix = "";
private String usersByUsernameQuery;
private boolean usernameBasedPrimaryKey = true;
public AcegiJdbcDaoImpl(){
usersByUsernameQuery = DEF_USERS_BY_USERNAME_QUERY;
authoritiesByUsernameQuery = DEF_AUTHORITIES_BY_USERNAME_QUERY;
}
public String getAuthoritiesByUsernameQuery() {
return authoritiesByUsernameQuery;
}
public String getRolePrefix() {
return rolePrefix;
}
public String getUsersByUsernameQuery() {
return usersByUsernameQuery;
}
protected void initMappingSqlQueries() {
this.usersByNameMapping = new UsersByUsernameMapping(getDataSource());
this.rolesByUsernameMapping = new AuthoritiesByUsernameMapping(getDataSource());
}
/**
     * Allows the default query string used to retrieve authorities based on username to be overriden, if
     * default table or column names need to be changed. The default query is {@link
     * #DEF_AUTHORITIES_BY_USERNAME_QUERY}; when modifying this query, ensure that all returned columns are mapped
     * back to the same column names as in the default query.
     *
     * @param queryString The query string to set
     */
public void setAuthoritiesByUsernameQuery(String queryString) {
authoritiesByUsernameQuery = queryString;
}
/**
     * Allows a default role prefix to be specified. If this is set to a non-empty value, then it is
     * automatically prepended to any roles read in from the db. This may for example be used to add the
     * <code>ROLE_</code> prefix expected to exist in role names (by default) by some other Acegi Security framework
     * classes, in the case that the prefix is not already present in the db.
     *
     * @param rolePrefix the new prefix
     */
public void setRolePrefix(String rolePrefix) {
this.rolePrefix = rolePrefix;
}
/**
     * If <code>true</code> (the default), indicates the {@link #getUsersByUsernameQuery()} returns a username
     * in response to a query. If <code>false</code>, indicates that a primary key is used instead. If set to
     * <code>true</code>, the class will use the database-derived username in the returned <code>UserDetailsImpl</code>.
     * If <code>false</code>, the class will use the {@link #loadUserByUsername(String)} derived username in the
     * returned <code>UserDetailsImpl</code>.
     *
     * @param usernameBasedPrimaryKey <code>true</code> if the mapping queries return the username <code>String</code>,
     *        or <code>false</code> if the mapping returns a database primary key.
     */
public void setUsernameBasedPrimaryKey(boolean usernameBasedPrimaryKey) {
this.usernameBasedPrimaryKey = usernameBasedPrimaryKey;
}
/**
     * Allows the default query string used to retrieve users based on username to be overriden, if default
     * table or column names need to be changed. The default query is {@link #DEF_USERS_BY_USERNAME_QUERY}; when
     * modifying this query, ensure that all returned columns are mapped back to the same column names as in the
     * default query. If the 'enabled' column does not exist in the source db, a permanent true value for this column
     * may be returned by using a query similar to <br><pre>
     * "SELECT username,password,'true' as enabled FROM users WHERE username = ?"</pre>
     *
     * @param usersByUsernameQueryString The query string to set
     */
public void setUsersByUsernameQuery(String usersByUsernameQueryString) {
this.usersByUsernameQuery = usersByUsernameQueryString;
}
public IUserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
List users = usersByNameMapping.execute(username);
if (users.size() == 0) {
throw new UsernameNotFoundException("User not found");
}
IUserDetails user = (IUserDetails) users.get(0); // contains no GrantedAuthority[]
List dbAuths = rolesByUsernameMapping.execute(user.getUsername());
addCustomAuthorities(user.getUsername(), dbAuths);
if (dbAuths.size() == 0) {
throw new UsernameNotFoundException("User has no GrantedAuthority");
}
GrantedAuthority[] arrayAuths = (GrantedAuthority[]) dbAuths.toArray(new GrantedAuthority[dbAuths.size()]);
user.setAuthorities(arrayAuths);
if (!usernameBasedPrimaryKey) {
user.setUsername(username);
}
return user;
}
/**
     * Query object to look up a user's authorities.
     */
protected class AuthoritiesByUsernameMapping extends MappingSqlQuery {
protected AuthoritiesByUsernameMapping(DataSource ds) {
super(ds, authoritiesByUsernameQuery);
declareParameter(new SqlParameter(Types.VARCHAR));
compile();
}
protected Object mapRow(ResultSet rs, int rownum)
throws SQLException {
String roleName = rolePrefix + rs.getString(2);
GrantedAuthorityImpl authority = new GrantedAuthorityImpl(roleName);
return authority;
}
}
/**
     * Query object to look up a user.
     */
protected class UsersByUsernameMapping extends MappingSqlQuery {
protected UsersByUsernameMapping(DataSource ds) {
super(ds, usersByUsernameQuery);
declareParameter(new SqlParameter(Types.VARCHAR));
compile();
}
protected Object mapRow(ResultSet rs, int rownum)
throws SQLException {
int user_id = rs.getInt(1);
int company_id = rs.getInt(2);
String username = rs.getString(3);
String password = rs.getString(4);
boolean enabled = rs.getBoolean(5);
IUserDetails user = new UserDetailsImpl(username, password, enabled, true, true, true,
new GrantedAuthority[] {new GrantedAuthorityImpl("HOLDER")});
user.setUserId(user_id);
user.setCompanyId(company_id);
return user;
}
}
}




修改spring配置, 使用我们新建立的类:

    <bean id="userDetailsService"
class="org.security.acegi.AcegiJdbcDaoImpl">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="usersByUsernameQuery">
<value>
SELECT u.user_id, u.company_id, email, password, enabled
from role r, user_role ur, user u where r.role_id = ur.role_id and ur.user_id = u.user_id
and email = ?
limit 1
</value>
</property>
<property name="authoritiesByUsernameQuery">
<value>
SELECT u.email, r.role_name FROM user_role ur, user u, role r WHERE
ur.user_id = u.user_id and ur.role_id = r.role_id and u.email = ?
</value>
</property>
</bean>




好了, 如果再有用户登录,就会调用我们的loadUserByUsername, 从数据库中读取用户数据了, 那用户的权限都有什么呢? 一个用户又对应着哪些ROLE呢? 下面先讲一下ACEGI 例子中的权限设置

2.2 将权限放在数据库中

截止到1.0.6版, Acegi没有提供直接从数据库读取权限的方法, 而是采用通过如下的配置设置权限:

    <bean id="filterInvocationInterceptor"
class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager" />
<property name="accessDecisionManager">
<bean class="org.acegisecurity.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions" value="false" />
<property name="decisionVoters">
<list>
<bean class="org.acegisecurity.vote.RoleVoter" />
<bean class="org.acegisecurity.vote.AuthenticatedVoter" />
</list>
</property>
</bean>
</property>
<property name="objectDefinitionSource">
<value><![CDATA[
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/secure/extreme/**=ROLE_SUPERVISOR
/secure/**=IS_AUTHENTICATED_REMEMBERED
/project/**=IS_AUTHENTICATED_REMEMBERED
/task/**=ROLE_DEVELOPER
/**=IS_AUTHENTICATED_ANONYMOUSLY
]]></value>
</property>
</bean>




而对大部分项目, 将权限放在数据库中可能是更灵活的, 为此, 我们需要写一个类去读取权限, 为了使这个类尽量简单, 我们把它做成PathBasedFilterInvocationDefinitionMap和RegExpBasedFilterInvocationDefinitionMap的代理类, PathBasedFilterInvocationDefinitionMap 采用的是Ant Path 风格的匹配方式, 而RegExpBasedFilterInvocationDefinitionMap采用的是Perl5风格的匹配方式. 用户可以通过在配置文件中设置来选择具体比较方式, 默认的比较方式是Ant Path 风格的匹配方式.

这样我们需要做的就是读取权限列表, 并放到相应的代理类里面, 而具体的比较则由代理类进行.

需要的表结构: Resource, Role_Resource

DROP TABLE IF EXISTS `resource`;
CREATE TABLE `resource` (
`resource_id` int(11) NOT NULL auto_increment,
`parent_resource_id` int(11) default NULL,
`resource_name` varchar(50) default NULL,
`description` varchar(100) default NULL,
PRIMARY KEY  (`resource_id`)
);
#
# Structure for the `resource_role` table :
#
DROP TABLE IF EXISTS `resource_role`;
CREATE TABLE `resource_role` (
`resource_role_id` int(11) NOT NULL auto_increment,
`resource_id` int(11) NOT NULL,
`role_id` int(11) NOT NULL,
PRIMARY KEY  (`resource_role_id`)
);



添加我们的类:

AcegiJdbcDefinitionSourceImpl.java

package org.security.acegi;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.acegisecurity.ConfigAttributeDefinition;
import org.acegisecurity.SecurityConfig;
import org.acegisecurity.intercept.web.FilterInvocationDefinitionMap;
import org.acegisecurity.intercept.web.FilterInvocationDefinitionSource;
import org.acegisecurity.intercept.web.PathBasedFilterInvocationDefinitionMap;
import org.acegisecurity.intercept.web.RegExpBasedFilterInvocationDefinitionMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.security.IResourceRole;
import org.security.ResourceRoleImpl;
import org.security.event.IPermissionListener;
import org.security.event.PermissionEventPublisher;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.jdbc.object.MappingSqlQuery;
/**
*
* The class <code>AcegiJdbcDefinitionSourceImpl</code> is proxy to
* PathBasedFilterInvocationDefinitionMap or RegExpBasedFilterInvocationDefinitionMap, This class get the permission
* settings from the database, the default sql script is: SELECT resource, role
* FROM role_permission, if it doesn't match your needs, changed it in bean
* setting. <br>
*
* <br>
* $log$<br>
* <br>
*
* @author $Author: wade $
* @see
*/
public class AcegiJdbcDefinitionSourceImpl extends JdbcDaoSupport implements
InitializingBean, FilterInvocationDefinitionSource{
private Log logger = LogFactory.getLog(this.getClass());
public static final String DEF_PERMISSIONS_QUERY = "SELECT resource, role FROM role_permission";
/** The Perl5 expression */
String PERL5_KEY = "PATTERN_TYPE_PERL5";
/** The ant path expression */
String ANT_PATH_KEY = "PATTERN_TYPE_APACHE_ANT";
/* Set default to Ant Path Expression*/
private String resourceExpression = ANT_PATH_KEY;
private boolean convertUrlToLowercaseBeforeComparison = false;
private FilterInvocationDefinitionMap definitionSource = null;
private String permissionsQuery;
private String rolePrefix = "";
public AcegiJdbcDefinitionSourceImpl() {
permissionsQuery = DEF_PERMISSIONS_QUERY;
}
public String getAuthoritiesByUsernameQuery() {
return permissionsQuery;
}
public String getRolePrefix() {
return rolePrefix;
}
/**
     * Allows the default query string used to retrieve permissions to be
     * overriden, if default table or column names need to be changed. The
     * default query is {@link #DEF_PERMISSIONS_QUERY}; when modifying this
     * query, ensure that all returned columns are mapped back to the same
     * column names as in the default query.
     *
     * @param queryString
     *            The query string to set
     */
public void setPermissionsQuery(String queryString) {
permissionsQuery = queryString;
}
/**
     * Allows a default role prefix to be specified. If this is set to a
     * non-empty value, then it is automatically prepended to any roles read in
     * from the db. This may for example be used to add the <code>ROLE_</code>
     * prefix expected to exist in role names (by default) by some other Acegi
     * Security framework classes, in the case that the prefix is not already
     * present in the db.
     *
     * @param rolePrefix
     *            the new prefix
     */
public void setRolePrefix(String rolePrefix) {
this.rolePrefix = rolePrefix;
}
/**
     * Init the permission list from db
     *
     */
protected void initMap() {
// return if we have got the latest permission list
if (definitionSource != null) {
return;
}
logger.debug("getting permissions from db");
if (PERL5_KEY.equals(getResourceExpression())) {
definitionSource = new RegExpBasedFilterInvocationDefinitionMap();
} else if (ANT_PATH_KEY.equals(getResourceExpression())) {
definitionSource = new PathBasedFilterInvocationDefinitionMap();
} else {
throw new IllegalArgumentException("wrong resourceExpression value");
}
definitionSource.setConvertUrlToLowercaseBeforeComparison(isConvertUrlToLowercaseBeforeComparison());
MappingSqlQuery permissionsMapping = new PermissionsMapping(
getDataSource());
List<IResourceRole> resources = permissionsMapping.execute();
Map<String, String> map = new HashMap<String, String>();
for (int i = 0; i < resources.size(); i++) {
ConfigAttributeDefinition defn = new ConfigAttributeDefinition();
String resource = resources.get(i).getResource();
if (map.containsKey(resource)) {
continue;
} else {
map.put(resource, resource);
}
for (int j = i; j < resources.size(); j++) {
IResourceRole resourceRole = resources.get(j);
if (resource.equals(resourceRole.getResource())) {
defn.addConfigAttribute(new SecurityConfig(resourceRole
.getRole()));
// logger.debug("added role: " + resourceRole.getRole());
}
}
definitionSource.addSecureUrl(resources.get(i).getResource(), defn);
// logger.debug("added roles to :" +
// resources.get(i).getResource());
}
}
/**
     * Query object to look up a user's authorities.
     */
protected class PermissionsMapping extends MappingSqlQuery {
protected PermissionsMapping(DataSource ds) {
super(ds, permissionsQuery);
compile();
}
protected IResourceRole mapRow(ResultSet rs, int rownum)
throws SQLException {
String resource = rs.getString(1);
String role = rolePrefix + rs.getString(2);
IResourceRole resourceRole = new ResourceRoleImpl(resource, role);
return resourceRole;
}
}
public ConfigAttributeDefinition getAttributes(Object object)
throws IllegalArgumentException {
initMap();
if (definitionSource instanceof RegExpBasedFilterInvocationDefinitionMap) {
return ((RegExpBasedFilterInvocationDefinitionMap) definitionSource).getAttributes(object);
}else if(definitionSource instanceof PathBasedFilterInvocationDefinitionMap) {
return ((PathBasedFilterInvocationDefinitionMap) definitionSource).getAttributes(object);
}
throw new IllegalStateException("wrong type of " + definitionSource + ", it should be " + RegExpBasedFilterInvocationDefinitionMap.class
+ " or " + PathBasedFilterInvocationDefinitionMap.class);
}
public Iterator getConfigAttributeDefinitions() {
initMap();
if (definitionSource instanceof RegExpBasedFilterInvocationDefinitionMap) {
return ((RegExpBasedFilterInvocationDefinitionMap) definitionSource).getConfigAttributeDefinitions();
}else if(definitionSource instanceof PathBasedFilterInvocationDefinitionMap) {
return ((PathBasedFilterInvocationDefinitionMap) definitionSource).getConfigAttributeDefinitions();
}
throw new IllegalStateException("wrong type of " + definitionSource + ", it should be " + RegExpBasedFilterInvocationDefinitionMap.class
+ " or " + PathBasedFilterInvocationDefinitionMap.class);
}
public boolean supports(Class clazz) {
initMap();
if (definitionSource instanceof RegExpBasedFilterInvocationDefinitionMap) {
return ((RegExpBasedFilterInvocationDefinitionMap) definitionSource).supports(clazz);
}else if(definitionSource instanceof PathBasedFilterInvocationDefinitionMap) {
return ((PathBasedFilterInvocationDefinitionMap) definitionSource).supports(clazz);
}
throw new IllegalStateException("wrong type of " + definitionSource + ", it should be " + RegExpBasedFilterInvocationDefinitionMap.class
+ " or " + PathBasedFilterInvocationDefinitionMap.class);
}
public String getResourceExpression() {
return resourceExpression;
}
public void setResourceExpression(String resourceExpression) {
this.resourceExpression = resourceExpression;
}
public boolean isConvertUrlToLowercaseBeforeComparison() {
return convertUrlToLowercaseBeforeComparison;
}
public void setConvertUrlToLowercaseBeforeComparison(
boolean convertUrlToLowercaseBeforeComparison) {
this.convertUrlToLowercaseBeforeComparison = convertUrlToLowercaseBeforeComparison;
}
}




修改spring配置, 使用我们新建立的类和对应的SQL:

    <bean id="filterInvocationInterceptor"
class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager"
ref="authenticationManager" />
<property name="accessDecisionManager">
<bean class="org.acegisecurity.vote.AffirmativeBased">
<property name="allowIfAllAbstainDecisions"
value="false" />
<property name="decisionVoters">
<list>
<bean class="org.acegisecurity.vote.RoleVoter" />
<bean
class="org.acegisecurity.vote.AuthenticatedVoter" />
</list>
</property>
</bean>
</property>
<property name="objectDefinitionSource">
<ref bean="rolePermissionService"/>
</property>
</bean>
<bean id="rolePermissionService"
class="org.security.acegi.AcegiJdbcDefinitionSourceImpl">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="permissionsQuery">
<value>
SELECT resource_name, role_name FROM resource_role rr, resource re, role ro
WHERE rr.role_id = ro.role_id and rr.resource_id = re.resource_id
</value>
</property>
<property name="convertUrlToLowercaseBeforeComparison" value="false"></property>
<property name="resourceExpression" value="PATTERN_TYPE_APACHE_ANT"></property>
</bean>



2.3 使用JUnit进行测试

AcegiPermissionTestCase.java

package org.security;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.acegisecurity.AccessDeniedException;
import org.acegisecurity.Authentication;
import org.acegisecurity.ConfigAttributeDefinition;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.intercept.web.FilterInvocation;
import org.acegisecurity.intercept.web.FilterInvocationDefinitionSource;
import org.acegisecurity.intercept.web.FilterSecurityInterceptor;
import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.security.BaseSpringTestCase;
import org.security.IResourceRole;
import org.security.IUserDetails;
import org.security.ResourceRoleImpl;
import org.security.acegi.AcegiJdbcDaoImpl;
/**
*
* The class <code>AcegiPermissionTestCase</code> test acegi permission settings<br><br>
* $log$<br><br>
* @author $Author: wade $
* @version $Revision: 1.0 $
* @see
*/
public class AcegiPermissionTestCase extends BaseSpringTestCase {
@Autowired
private FilterInvocationDefinitionSource objectDefinitionSource;
@Autowired
private AcegiJdbcDaoImpl userDetailsService;
@Autowired
private FilterSecurityInterceptor filterInvocationInterceptor;
/**
     * Get Authentication Token by username
     * @param username
     * @return Authentication
     */
protected Authentication getAuthentication(String username){
IUserDetails userDetail = userDetailsService.loadUserByUsername(username);
Authentication authenticated;
if(userDetail.isEnabled()){
authenticated = new UsernamePasswordAuthenticationToken(userDetail, username, userDetail.getAuthorities());
}else{
//            authenticated = new AnonymousAuthenticationToken(username, userDetail, userDetail.getAuthorities());
authenticated = new UsernamePasswordAuthenticationToken(null, null, new GrantedAuthority[]{new GrantedAuthorityImpl("ROLE_ANONYMOUS")});
}
return authenticated;
}
/**
     * get FilterInvocation from the url
     * @param url
     * @return FilterInvocation
     */
protected FilterInvocation getRequestedResource(String url){
MockHttpServletRequest request = new MockHttpServletRequest();
request.setServletPath(url);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterchain = new FilterChain(){
public void doFilter(ServletRequest arg0, ServletResponse arg1)
throws IOException, ServletException {
}};
FilterInvocation object = new FilterInvocation(request, response, filterchain);
return object;
}
/**
     * throws AccessDeniedException if no permission
     * @param username
     * @param uri
     */
public void checkPermission(boolean shouldHasPermission, String username, String url){
Authentication authenticated = getAuthentication(username);
FilterInvocation object = getRequestedResource(url);
ConfigAttributeDefinition attr = objectDefinitionSource.getAttributes(object);
boolean hasPermission = false;
try{
filterInvocationInterceptor.getAccessDecisionManager().decide(authenticated, object, attr);
hasPermission = true;
}catch(AccessDeniedException e){
hasPermission = false;
}
if(hasPermission){
assertTrue(username + " shouldn't be able to access " + url, shouldHasPermission);
}else{
assertFalse(username + " should be able to access " + url, shouldHasPermission);
}
}
public void testPermissionForAdmin(){
Map<IResourceRole, Boolean> map = new LinkedHashMap<IResourceRole, Boolean>();
map.put(new ResourceRoleImpl("/admin/index.jsp", "admin"    ), true);
map.put(new ResourceRoleImpl("/admin/index.jsp", "project"    ), false);
map.put(new ResourceRoleImpl("/admin/index.jsp", "dev"        ), false);
map.put(new ResourceRoleImpl("/admin/index.jsp", "disabled"    ), false);
map.put(new ResourceRoleImpl("/admin", "admin"    ), true);
map.put(new ResourceRoleImpl("/admin", "project"), false);
map.put(new ResourceRoleImpl("/admin", "dev"    ), false);
map.put(new ResourceRoleImpl("/admin", "disabled"), false);
map.put(new ResourceRoleImpl("/project/index.jsp", "admin"    ), true);
map.put(new ResourceRoleImpl("/project/index.jsp", "project"), true);
map.put(new ResourceRoleImpl("/project/index.jsp", "dev"    ), false);
map.put(new ResourceRoleImpl("/project/index.jsp", "disabled"), false);
map.put(new ResourceRoleImpl("/project", "admin"    ), true);
map.put(new ResourceRoleImpl("/project", "project"    ), true);
map.put(new ResourceRoleImpl("/project", "dev"        ), false);
map.put(new ResourceRoleImpl("/project", "disabled"    ), false);
map.put(new ResourceRoleImpl("/developer/index.jsp", "admin"    ), true);
map.put(new ResourceRoleImpl("/developer/index.jsp", "project"    ), true);
map.put(new ResourceRoleImpl("/developer/index.jsp", "dev"        ), true);
map.put(new ResourceRoleImpl("/developer/index.jsp", "disabled"    ), false);
map.put(new ResourceRoleImpl("/developer", "admin"        ), true);
map.put(new ResourceRoleImpl("/developer", "project"    ), true);
map.put(new ResourceRoleImpl("/developer", "dev"        ), true);
map.put(new ResourceRoleImpl("/developer", "disabled"    ), false);
map.put(new ResourceRoleImpl("/index.jsp", "admin"    ), true);
map.put(new ResourceRoleImpl("/index.jsp", "project"), true);
map.put(new ResourceRoleImpl("/index.jsp", "dev"    ), true);
map.put(new ResourceRoleImpl("/index.jsp", "disabled"), true);
map.put(new ResourceRoleImpl("/acegilogin.jsp", "admin"    ), true);
map.put(new ResourceRoleImpl("/acegilogin.jsp", "project"    ), true);
map.put(new ResourceRoleImpl("/acegilogin.jsp", "dev"    ), true);
map.put(new ResourceRoleImpl("/acegilogin.jsp", "disabled"    ), true);
Set<IResourceRole> keySet= map.keySet();
Iterator<IResourceRole> ita = keySet.iterator();
while(ita != null && ita.hasNext()){
IResourceRole resourceRole = ita.next();
boolean expectedPermission = map.get(resourceRole);
checkPermission(expectedPermission, resourceRole.getRole(), resourceRole.getResource());
}
}
}

三. 集成之后

3.1 更改数据库中的权限

到目前为止, 一切顺利, 但是有一个问题, 用户如何修改权限, 修改后我们写的类如何能知道权限变了, 需要去重新加载呢? 看来我们需要再加一些代码以便于在权限被修改后能够得到消息, 然后去刷新权限.

为此, 我们使用Observe(观察者) 模式, 在改变权限后, 由改变权限的类通过调用PermissionEventPublisher.update(this.getClass())发出消息说权限变了.

IPermissionListener.java

public interface IPermissionListener {
public void updatePermission(Class eventSource);
}
PermissionEventPublisher.java

package org.security.event;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* The class PermissionEventPublisher provides a way to notify the IPermissionListener that the permission has been changed.
* @author wade
*
*/
public class PermissionEventPublisher {
private static Log logger = LogFactory.getLog(PermissionEventPublisher.class);
private static Map<IPermissionListener, IPermissionListener> observerList =
new HashMap<IPermissionListener, IPermissionListener>();
/**
     * Attach a listener for permission event
     *
     * @param subject
     * @param listener
     */
public static void attach(IPermissionListener listener){
observerList.put(listener, listener);
if(logger.isDebugEnabled()){
logger.debug("Added listener: " + listener.getClass().getName());
}
}
/**
     * Detatch from the event updater
     * @param listener
     */
public static void detatch(IPermissionListener listener){
observerList.remove(listener);
if(logger.isDebugEnabled()){
logger.debug("Removeded listener: " + listener.getClass().getName());
}
}
/**
     * send message to each listener.
     * @param eventSource
     */
public static void update(Class eventSource){
if(logger.isDebugEnabled()){
logger.debug("permission changed from "+eventSource.getName());
}
Iterator<IPermissionListener> ita = observerList.keySet().iterator();
while(ita.hasNext()){
IPermissionListener permissionListener = ita.next();
permissionListener.updatePermission(eventSource);
if(logger.isDebugEnabled()){
logger.debug("call update for listener=" + permissionListener.getClass().getName());
}
}
}
}

修改AcegiJdbcDefinitionSourceImpl.java, 增加updatePermission方法, 在权限变化后进行处理

public class AcegiJdbcDefinitionSourceImpl extends JdbcDaoSupport implements
InitializingBean, FilterInvocationDefinitionSource, IPermissionListener {
public AcegiJdbcDefinitionSourceImpl() {
permissionsQuery = DEF_PERMISSIONS_QUERY;
//attach to event publisher, so the class can get the notify when permission changes
PermissionEventPublisher.attach(this);
}
/**
     * Set definitionSource to null, so we can get a refreshed permission list from db
     */
public void updatePermission(Class eventSource) {
definitionSource = null;
}
}



3.2 在程序中获取当前用户

直接从Acegi中取用户信息不太方便, 为了简化获取用户的方法, 可以添加一个类封装对应的逻辑, 然后通过CurrentUser.getUser()直接取到用户信息.

CurrentUser.java

/**
     * Get current user which stored in session
     * You must set a user when using junit test
     * @return IUserDetails
     */
public static IUserDetails getUser(){
//if not in unit test environment, get the current user using acegi
if ((SecurityContextHolder.getContext() == null)
|| !(SecurityContextHolder.getContext() instanceof SecurityContext)
|| (((SecurityContext) SecurityContextHolder.getContext())
.getAuthentication() == null)) {
return null;
}
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth.getPrincipal() == null) {
return null;
}
IUserDetails user = null;
if (auth.getPrincipal() instanceof IUserDetails) {
user = (IUserDetails)auth.getPrincipal();
}
return user;
}




3.3 使用Tag来判断用户是否具有某一种Role的权限

有一点一定要注意, 由于Filter的处理有顺序,所以需要将Acegi的Filter放在最前面.

<authz:authorize ifAnyGranted="ROLE_SUPERVISOR, ROLE_ADMINISTRATOR, ROLE_FULLACCESS">

Role in ROLE_SUPERVISOR, ROLE_ADMINISTRATOR, ROLE_FULLACCESS

</authz:authorize>

3.4 添加自己的Tag

Acegi 提供的Tag只能判断当前用户是不是具有某种Role, 不能判断当前用户对某一个URL有没有权限, 由于很多时候需要根据当前用户的权限来控制某些功能是否显示, 比如只有管理员才显示Add或Delete按钮

这是你可以自己写自己的Tag, 为了简单起见, 我们继承jstl的Tag, 比如下面实现两个条件的Tag, Tag的用法如下:

<auth:ifNotAuthrized url="/system/acl.action">如果当前用户没有指定url的权限,显示本部分内容</auth:ifNotAuthrized>

<auth:ifAuthrized url="/system/acl.action">如果当前用户有指定url的权限,显示本部分内容</auth:ifAuthrized>

AuthorizedTag.java

public class AuthorizedTag extends ConditionalTagSupport {
protected Log logger = LogFactory.getLog(this.getClass());
@Autowired
private FilterInvocationDefinitionSource objectDefinitionSource;
@Autowired
private FilterSecurityInterceptor filterInvocationInterceptor;
private String url;
/**
     * Get Authentication Token from  IUserDetails object
     * @param user
     * @return Authentication
     */
protected Authentication getAuthentication(IUserDetails user){
IUserDetails userDetail = user;
Authentication authenticated;
if(userDetail == null){
authenticated = new UsernamePasswordAuthenticationToken(null, null, new GrantedAuthority[]{new GrantedAuthorityImpl("ROLE_ANONYMOUS")});
}else{
if(userDetail.isEnabled()){
authenticated = new UsernamePasswordAuthenticationToken(userDetail, userDetail.getUsername(), userDetail.getAuthorities());
}else{
authenticated = new AnonymousAuthenticationToken(userDetail.getUsername(), userDetail, userDetail.getAuthorities());
}
}
return authenticated;
}
/**
     * get FilterInvocation from the url
     * @param url
     * @return FilterInvocation
     */
protected FilterInvocation getRequestedResource(String url){
MockHttpServletRequest request = new MockHttpServletRequest(pageContext.getServletContext());
request.setServletPath(url);
FilterChain filterchain = new FilterChain(){
public void doFilter(ServletRequest arg0, ServletResponse arg1)
throws IOException, ServletException {
}};
FilterInvocation object = new FilterInvocation(request, pageContext.getResponse(), filterchain);
return object;
}
@Override
protected boolean condition() throws JspTagException {
boolean result = false;
IUserDetails user = CurrentUser.getUser();
ServletContext servletContext = pageContext.getServletContext();
WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
wac.getAutowireCapableBeanFactory().autowireBeanProperties(this, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false);
ConfigAttributeDefinition attr = objectDefinitionSource.getAttributes(getRequestedResource(url));
try{
filterInvocationInterceptor.getAccessDecisionManager().decide(getAuthentication(user), url, attr);
result = true;
}catch(AccessDeniedException e){
result = false;
if(user == null){
logger.debug("anonymous has no permission on :" + url);
}else{
logger.debug(user.getUsername() + " has no permission on :" + url);
}
}
return result;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
添加Jsp页面测试新添加的Tag, 在文所附的例子程序中, 将Tag的测试代码放在index.jsp页面中, 任何人都可以访问该页面, 在页面上列出了全部地址的链接, 同时列出了当前用户有权限的地址, 这样可以方便地知道当前用户有哪些权限, 如果你想修改数据库中的权限, 然后再次测试, 可以点击页面右上侧的Reload Permission重新从数据库加载权限.

<auth:ifAuthrized url="/admin">
<p><a href="admin">Admin page</a></p>
</auth:ifAuthrized>

四. 参考文档

1. 更多深入介绍,可以根据Acegi官方提供的Suggested Steps (http://www.acegisecurity.org/suggested.html) 一步一步学习.

2. 如果要了解Acegi提供的各种功能, 可以参考http://www.acegisecurity.org/reference.html

3. 阅读本文需要对Spring有一定的了解, http://www.springframework.org/documentation

4. 扩展jstl的tag, 可以参看http://www.onjava.com/pub/a/onjava/2002/10/30/jstl3.html?page=1

5. 从https://sourceforge.net/project/platformdownload.php?group_id=216220下载本文附带的例子代码, 通过acegi.sql建立数据库, 然后将acegi-test.war放到Tomcat的webapps目录下, 或者你可以下载acegi-test.zip文件, 里面包含了完整的eclipse的项目以及sql文件.

访问http://youip:port/acegi-test, 列出全部地址的链接, 同时列出了当前用户有权限的地址链接


转自:http://acegi-test.sourceforge.net/




绿色通道:好文要顶关注我收藏该文与我联系




eafy.ye
关注 - 0
粉丝 - 1



+加关注


0

0


(请您对文章做出评价)


« 上一篇:关于远程调用(XFire/HttpInvoker/Hessian etc.)及远程服务管理的一些随想
» 下一篇:Acegi+hibernate 动态实现基于角色的权限管理


posted @ 2008-03-04 17:30 eafy.ye 阅读(3398) 评论(11)编辑 收藏


发表评论





 回复 引用  
#1楼2008-03-13 11:41 | winie[未注册用户]
我正在找这方面的资料````````呵呵`````写的good




 回复 引用  
#2楼2008-04-22 18:12 | QQ:23390520[未注册用户]
哥们你的文章很好,你一定是个令人佩服之人

但是我在进行 登陆的时候,选中复选框没有问题。
可是当我 关闭浏览器(没有退出)的时候,再次登陆 就不能重建session了

哥们,麻烦你解决下哦




 回复 引用  
#3楼2008-07-06 17:27 | hunterk[未注册用户]
你的文章很详细,非常不错~~




 回复 引用  
#4楼2008-07-28 11:50 | 树的回忆[未注册用户]
你的文章写得很清楚,谢谢楼主咯!




 回复 引用  
#5楼2008-10-16 11:14 | 世玉[未注册用户]
请帮我解决个问题:

http://topic.csdn.net/u/20081014/20/c3562b08-8d0f-41fa-aa7f-59ef63b63513.html
不胜感激!!!




 回复 引用  
#6楼2008-12-03 10:32 | Ivan's Blog[未注册用户]
Actually there is no need such complex to make the authrozation supports DB, all things only to do is to extends AbstractObjectDefinitionSource and override the lookAttribute method to fetchs security definitions from db or cache(for better performance).




 回复 引用  
#7楼2009-06-21 23:24 | 张志juney[未注册用户]
您的文章写的非常好

请问一下,在监听resource改动时,重新加载权限这个地方,definitionSource这个变量是哪个类里面的啊?我怎么就是找不到这个属性,如果方便的话请加我qq:75492100,请帮我一个忙。非常感谢。




 回复 引用 查看  
#8楼2009-07-04 19:00 | acegi     
好乱
没有条理,越看越复杂
而且里面你用到得类或接口(自己定义的),根本就没交代
会让初学的人看了想死
因为你给的东西不全
你自己写的有些类,接口都没贴出来

写教程不是这么写滴
写教程要把配置文件,代码等完完全全贴出来
不是从中抽出一些贴出来

看的我头昏脑胀
一步一步按你的配下来
结果。。。有些类都不知道是啥
你也没贴出来
。。。




 回复 引用 查看  
#9楼2009-07-04 19:06 | acegi     
CREATE TABLE `resource_role` (
`resource_role_id` int(11) NOT NULL auto_increment,
`resource_id` int(11) NOT NULL,
`role_id` int(11) NOT NULL,
PRIMARY KEY (`resource_role_id`)
);

String resource = rs.getString(1);
String role = rolePrefix + rs.getString(2);
IResourceRole resourceRole = new ResourceRoleImpl(resource, role);
return resourceRole;

上面建表的时候明明是int(11)
怎么到下面的时候resource,role是String了???????

完全没交代IResourceRole和ResourceRoleImpl是啥
不过我猜应该是resource_role这个表反向生成的类吧????
分享到:
评论

相关推荐

    acegi集成CAS示例

    acegi集成CAS示例,演示了acegi项目集成标准cas的方法

    acegi参考的部分翻译

    第一章安全1: 准备Acegi通过对流行的WEB容器的可选集成而为使用Spring编写的项目提供认证与授权的能力.这种安全架构是全部用"Spring方式"开发,包括使用beancontexts、拦截器和接口驱动的编程方式。结果,对那些为...

    简单acegi程序

    在应用系统开发的过程中权限的设置和用户的安全管理是系统中不可缺少的部分,但如果自己去实现有时因为项目的原因可能就会在后期忽略了这块,在spring中有集成了这个部分,感谢spring 给我们带来了这么好的东西。

    jasypt API Docs 1.9 (CHM格式)

    Jasypt开发团队推出了Java加密工具Jasypt 1.4,它可与Spring Framework、Hibernate和Acegi Security集成。 Jasypt 1.4的新特征包括:加密属性文件(encryptable properties files)、Spring Framework集成、加密...

    jasypt-1.9.3-dist.zip

    Jasypt 这个Java类包为开发人员提供一种简单的方式来为项目增加加密功能,包括:密码Digest认证,文本和对象加密,集成 hibernate,Spring Security(Acegi)来增强密码管理。 Jasypt是一个Java库,可以使开发者不需...

    jasypt-1.9.3.jar

    Jasypt 这个Java类包为开发人员提供一种简单的方式来为项目增加加密功能,包括:密码Digest认证,文本和对象加密,集成 hibernate,Spring Security(Acegi)来增强密码管理。 &gt;Jasypt是一个Java库,可以使开发者不需...

    JAVA上百实例源码以及开源项目源代码

     通过网络或磁盘等方式,把公钥编码传送给李四,李四接收到张三编码后的公钥,将其解码,李四用张三的公钥加密信息,并发送给李四,张三用自己的私钥解密从李四处收到的信息…… Java利用DES私钥对称加密代码实例 ...

    Roller4.0的另外一套cas简单解决方案

    打开官方发布的可直接发布的部署包apache-roller-4.0\webapp\roller\WEB-INF的目录,复制除了classes目录以外的所有目录到自建项目的WEB-INF目录下面.这里要注意一下.就是lib目录.首先lib目录中我们已经修改了roller-...

    JAVA上百实例源码以及开源项目

     通过网络或磁盘等方式,把公钥编码传送给李四,李四接收到张三编码后的公钥,将其解码,李四用张三的公钥加密信息,并发送给李四,张三用自己的私钥解密从李四处收到的信息…… Java利用DES私钥对称加密代码实例 ...

    jasypt-1.7 简单的java字符串加密工具

    简单好用的String加密工具。 为开发人员提供一种简单的方式来为项目增加加密功能,包括:密码Digest认证,文本和对象加密,集成hibernate,Spring Security(Acegi)来增强密码管理。

    Spring技术内幕:深入解析Spring架构与设计原理

    分 享 到: 收藏 评论(3) 举报 我的待评论资源 - 资源简介×××总共两个zip文件;7zip压缩 ×××Spring 技术内幕.zip.001 ×××Spring 技术内幕.zip.002 本书是spring领域的问鼎之作,由业界拥有10余年...

    jasypt1.9.2&jce1.6-1.8

    Jasypt这个Java类包为开发人员提供一种简单的方式来为项目增加加密功能,包括:密码Digest认证,文本和对象加密,集成hibernate,Spring Security(Acegi)来增强密码管理。 更多资源详见: ...

    java开源包1

    可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 Java对象的SQL接口 JoSQL JoSQL...

    java开源包11

    可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 Java对象的SQL接口 JoSQL JoSQL...

    java开源包2

    可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 Java对象的SQL接口 JoSQL JoSQL...

    java开源包3

    可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 Java对象的SQL接口 JoSQL JoSQL...

    java开源包6

    可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 Java对象的SQL接口 JoSQL JoSQL...

    java开源包5

    可以将列表数据缓存到redis中,其他kv结构数据继续缓存到memcached 6. 支持redis的主从集群,可以做读写分离。缓存读取自redis的slave节点,写入到redis的master节点。 Java对象的SQL接口 JoSQL JoSQL...

Global site tag (gtag.js) - Google Analytics