Acegi+hibernate 动态实现基于角色的权限管理

Acegihibernate 动态实现基于角色的权限管理

最近在做项目遇到了权限管理,用户要求可以自己建立不同的角色对系统的资源进行控制, 不同的用户有不同的角色,又恰恰框架中用到了strutsspringhibernate,要求在web层调用 业务逻辑层 时不考虑权限,web层可以控制用户的显示界面,逻辑层处理用户权限问题。 想来想去好像只有spring aop 可以做到,在调用到 接口 中的方法时,首先检查用户的权限,如果检查通过则继续执行,否则抛出异常。但是新的问题又出现了,如何在逻辑层上来得到当前用户的id,以致用户的 角色,总不能每次都要从web中传来一个 httprequest,或者 session 这类的吧。在网上看了很多资料,发现了acegi,恰好解决了以上的难题,具体的实现原理这里就不多说了,网上有很多相关资料。 说正题,首先来看看acegi 的官方 example ,我下载的是acegi-security-1.0.0-RC1,解压缩后可以看到acegi-security-sample-contacts-filter.war,打开配置文件有这样几句

java代码

  <bean id="contactManagerSecurity" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">       <property name="authenticationManager"><ref bean="authenticationManager"/></property>       <property name="accessDecisionManager"><ref local="businessAccessDecisionManager"/></property>       <property name="afterInvocationManager"><ref local="afterInvocationManager"/></property>       <property name="objectDefinitionSource">          <value>             sample.contact.ContactManager.create=ROLE_USER             sample.contact.ContactManager.getAllRecipients=ROLE_USER             sample.contact.ContactManager.getAll=ROLE_USER,AFTER_ACL_COLLECTION_READ             sample.contact.ContactManager.getById=ROLE_USER,AFTER_ACL_READ             sample.contact.ContactManager.delete=ACL_CONTACT_DELETE             sample.contact.ContactManager.deletePermission=ACL_CONTACT_ADMIN             sample.contact.ContactManager.addPermission=ACL_CONTACT_ADMIN          </value>       </property>    </bean>

可以看到它是通过读配置文件来判断执行某个方法所需要的角色的,再看这几句

java代码

<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">       <property name="authenticationManager"><ref bean="authenticationManager"/></property>       <property name="accessDecisionManager"><ref local="httpRequestAccessDecisionManager"/></property>       <property name="objectDefinitionSource">          <value>                             CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON                             PATTERN_TYPE_APACHE_ANT                             /index.jsp=ROLE_ANONYMOUS,ROLE_USER                             /hello.htm=ROLE_ANONYMOUS,ROLE_USER                             /logoff.jsp=ROLE_ANONYMOUS,ROLE_USER                             /switchuser.jsp=ROLE_SUPERVISOR                             /j_acegi_switch_user=ROLE_SUPERVISOR                             /acegilogin.jsp*=ROLE_ANONYMOUS,ROLE_USER                                 /**=ROLE_USER          </value>       </property>    </bean>

同样是将页面的访问权限写死在配置文件中,再来看看它的tag是如何处理的

java代码

<auth:authorize ifAnyGranted="ROLE_DELETE">           <a href="">删除</a> </auth:authorize>

可见它是要求我们对链接或者其他资源的保护时提供 用户角色,可是既然角色是用户自己添加的我们又如何来写死在这里呢? 还有就是它对用户验证默认使用的是jdbc, JdbcDaoImpl

java代码

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">                 <property name="dataSource"><ref local="dataSource"/></property>         </bean>

而我们希望基于HibernateDao来实现。 可见仅仅使用现有的acegi 是 无法满足我们项目开发的需求的。 解决方法: 1: 开发基于数据库的保护资源。 看过acegi的源代码就会知道,对保护资源的定义是通过实现ObjectDefinitionSource这个接口来实现的,而且acegi为我们提供了默认实现的抽象类

java代码

public abstract class AbstractMethodDefinitionSource     implements MethodDefinitionSource {     //~ Static fields/initializers =============================================     private static final Log logger = LogFactory.getLog(AbstractMethodDefinitionSource.class);     //~ Methods ================================================================     public ConfigAttributeDefinition getAttributes(Object object)         throws IllegalArgumentException {         Assert.notNull(object, "Object cannot be null");         if (object instanceof MethodInvocation) {             return this.lookupAttributes(((MethodInvocation) object).getMethod());         }         if (object instanceof JoinPoint) {             JoinPoint jp = (JoinPoint) object;             Class targetClazz = jp.getTarget().getClass();             String targetMethodName = jp.getStaticPart().getSignature().getName();             Class[] types = ((CodeSignature) jp.getStaticPart().getSignature())                     .getParameterTypes();             if (logger.isDebugEnabled()) {                 logger.debug("Target Class: " + targetClazz);                 logger.debug("Target Method Name: " + targetMethodName);                 for (int i = 0; i < types.length; i++) {                     if (logger.isDebugEnabled()) {                         logger.debug("Target Method Arg #" + i + ": "                                 + types[i]);                     }                 }             }             try {                 return this.lookupAttributes(targetClazz.getMethod(targetMethodName, types));             } catch (NoSuchMethodException nsme) {                 throw new IllegalArgumentException("Could not obtain target method from JoinPoint: " + jp);             }         }         throw new IllegalArgumentException("Object must be a MethodInvocation or JoinPoint");     }     public boolean supports(Class clazz) {         return (MethodInvocation.class.isAssignableFrom(clazz)         || JoinPoint.class.isAssignableFrom(clazz));     }     protected abstract ConfigAttributeDefinition lookupAttributes(Method method); }

我们要做的就是实现它的 protected abstract ConfigAttributeDefinition lookupAttributes(Method method);方法, 以下是我的实现方法,大致思路是这样,通过由抽象类传来的Method 对象得到 调 用这个方法的 包名,类名,方法名 也就是secureObjectName, 查询数据库并将结果映射为Function 也就是secureObject ,由于Function Role 的多对多关系 可以得到 Function所对应的 Roles ,在将role 包装成GrantedAuthority (也就是acegi中的角色)。其中由于频繁的对数据库的查询 所以使用Ehcache 来作为缓存。

java代码

package sample.auth; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Set; import org.acegisecurity.ConfigAttributeDefinition; import org.acegisecurity.ConfigAttributeEditor; import org.acegisecurity.GrantedAuthority; import org.acegisecurity.GrantedAuthorityImpl; import org.acegisecurity.intercept.method.AbstractMethodDefinitionSource; import org.springframework.util.Assert; import sample.auth.cache.AuthorityBasedFunctionCache; import sample.auth.cache.info.FunctionByNameCache; import sample.dao.IBaseDao; import sample.mappings.function.Function; import sample.mappings.role.Role; public class DatabaseDrivenMethodDefinitionSourcew extends                 AbstractMethodDefinitionSource {                 // baseDao 提供通过HIbenate对数据库操作的实现         private IBaseDao baseDao;         // AuthorityBasedFunctionCache 通过Function Role 时缓存         private AuthorityBasedFunctionCache cache;         // FunctionByNameCache 由反射到的方法名查找 数据库对应的Function 时的缓存         private FunctionByNameCache functionCache;         public FunctionByNameCache getFunctionCache() {                 return functionCache;         }         public void setFunctionCache(FunctionByNameCache functionCache) {                 this.functionCache = functionCache;         }         protected ConfigAttributeDefinition lookupAttributes(Method mi) {                         Assert.notNull(mi,"lookupAttrubutes in the DatabaseDrivenMethodDefinitionSourcew is null");                 String secureObjectName=mi.getDeclaringClass().getName() +"."+ mi.getName();                 //Function 为数据库中保护资源的映射                 Function secureObject=functionCache.getFunctionByCache(secureObjectName);                 if(secureObject==null)//if secure object not exist in database                 {                         secureObject=(Function)baseDao.loadByKey(Function.class, "protectfunction", secureObjectName);                         functionCache.putFunctionInCache(secureObject);                 }                                     if(secureObject==null)                         Assert.notNull(secureObject,"secureObject(Function) not found in db");                 //retrieving roles associated with this secure object                                 Collection roles = null;                 GrantedAuthority[] grantedAuthoritys = cache.getAuthorityFromCache(secureObject.getName());                 // 如果是第一次 cache 为空                 if(grantedAuthoritys == null){                                                 Set rolesSet = secureObject.getRoles();                         Iterator it = rolesSet.iterator();                         List list = new ArrayList();                         while(it.hasNext()){                                                                 Role role = (Role)it.next();                                 GrantedAuthority g = new  GrantedAuthorityImpl(role.getName());                                 list.add(g);                                }                         grantedAuthoritys = (GrantedAuthority[])list.toArray(new GrantedAuthority[0]);                         cache.putAuthorityInCache(secureObject.getName(),grantedAuthoritys);                         roles = Arrays.asList(grantedAuthoritys);                 }else{                                                 roles = Arrays.asList(grantedAuthoritys);                 }                                 if(!roles.isEmpty()){                         ConfigAttributeEditor configAttrEditor=new ConfigAttributeEditor();                         StringBuffer rolesStr=new StringBuffer();                         for(Iterator it = roles.iterator();it.hasNext();){                                 GrantedAuthority role=(GrantedAuthority)it.next();                                 rolesStr.append(role.getAuthority()).append(",");                         }                         configAttrEditor.setAsText( rolesStr.toString().substring(0,rolesStr.length()-1) );                         ConfigAttributeDefinition configAttrDef=(ConfigAttributeDefinition)configAttrEditor.getValue();                         return configAttrDef;                 }                 Assert.notEmpty(roles,"collection of roles is null or empty");                 return null;                         }         public Iterator getConfigAttributeDefinitions() {                                 return null;         }         public IBaseDao getBaseDao() {                 return baseDao;         }         public void setBaseDao(IBaseDao baseDao) {                 this.baseDao = baseDao;         }         public AuthorityBasedFunctionCache getCache() {                 return cache;         }         public void setCache(AuthorityBasedFunctionCache cache) {                 this.cache = cache;         } }

2:定义 基于方法的 自定义标志 通过以上的分析 , 要想使用acegi 做页面的显示控制仅仅靠角色(Role)是不行的,因为用户可能随时定义出新的角色,所以只能 基于方法(Function)的控制。可是acegi 只是提供了基于 角色的 接口GrantedAuthority ,怎么办? Wink ,如法炮制。 首先定义出我们自己的GrantedFunction,实现也雷同 GrantedAuthorityImpl

java代码

package sample.auth; import java.io.Serializable; public class GrantedFunctionImpl implements GrantedFunction , Serializable{     private String function;     //~ Constructors ===========================================================     public GrantedFunctionImpl(String function) {         super();         this.function = function;     }     protected GrantedFunctionImpl() {         throw new IllegalArgumentException("Cannot use default constructor");     }     //~ Methods ================================================================     public String getFunction() {         return this.function;     }     public boolean equals(Object obj) {         if (obj instanceof String) {             return obj.equals(this.function);         }         if (obj instanceof GrantedFunction) {             GrantedFunction attr = (GrantedFunction) obj;             return this.function.equals(attr.getFunction());         }         return false;     }     public int hashCode() {         return this.function.hashCode();     }     public String toString() {         return this.function;     } }

以 下是我的标志实现,大致思路是 根据 页面 的传来的 方法名(即 FunctionName)查询出对应的Functions,并且包装成grantedFunctions ,然后根据用户的角色查询出用户对应的Functions ,再取这两个集合的交集,最后再根据这个集合是否为空判断是否显示标志体的内容。

java代码

package sample.auth; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.servlet.jsp.JspException; import javax.servlet.jsp.tagext.Tag; import javax.servlet.jsp.tagext.TagSupport; import org.acegisecurity.Authentication; import org.acegisecurity.GrantedAuthority; import org.acegisecurity.context.SecurityContextHolder; import org.springframework.util.StringUtils; import org.springframework.web.util.ExpressionEvaluationUtils; import sample.web.action.AppContext; /** * * @author limq * */ public class AuthorizeActionTag extends TagSupport{             private String ifAllGranted = "";             private String ifAnyGranted = "";             private String ifNotGranted = "";                         public void setIfAllGranted(String ifAllGranted) throws JspException {                 this.ifAllGranted = ifAllGranted;             }             public String getIfAllGranted() {                 return ifAllGranted;             }             public void setIfAnyGranted(String ifAnyGranted) throws JspException {                 this.ifAnyGranted = ifAnyGranted;             }             public String getIfAnyGranted() {                 return ifAnyGranted;             }             public void setIfNotGranted(String ifNotGranted) throws JspException {                 this.ifNotGranted = ifNotGranted;             }             public String getIfNotGranted() {                 return ifNotGranted;             }                         public int doStartTag() throws JspException {                 if (((null == ifAllGranted) || "".equals(ifAllGranted))                     && ((null == ifAnyGranted) || "".equals(ifAnyGranted))                     && ((null == ifNotGranted) || "".equals(ifNotGranted))) {                     return Tag.SKIP_BODY;                 }                 final Collection granted = getPrincipalFunctionByAuthorities();                 final String evaledIfNotGranted = ExpressionEvaluationUtils                     .evaluateString("ifNotGranted", ifNotGranted, pageContext);                 if ((null != evaledIfNotGranted) && !"".equals(evaledIfNotGranted)) {                     Set grantedCopy = retainAll(granted,                                     parseSecurityString(evaledIfNotGranted));                     if (!grantedCopy.isEmpty()) {                         return Tag.SKIP_BODY;                     }                 }                 final String evaledIfAllGranted = ExpressionEvaluationUtils                     .evaluateString("ifAllGranted", ifAllGranted, pageContext);                 if ((null != evaledIfAllGranted) && !"".equals(evaledIfAllGranted)) {                     if (!granted.containsAll(parseSecurityString(evaledIfAllGranted))) {                         return Tag.SKIP_BODY;                     }                 }                 final String evaledIfAnyGranted = ExpressionEvaluationUtils                     .evaluateString("ifAnyGranted", ifAnyGranted, pageContext);                 if ((null != evaledIfAnyGranted) && !"".equals(evaledIfAnyGranted)) {                     Set grantedCopy = retainAll(granted,                                     parseSecurityString(evaledIfAnyGranted));                     if (grantedCopy.isEmpty()) {                         return Tag.SKIP_BODY;                     }                 }                 return Tag.EVAL_BODY_INCLUDE;             }     /**      * 得到用户的Authentication,并且从Authentication中获得 Authorities,进而得到 授予用户的 Function      * @return      */             private Collection getPrincipalFunctionByAuthorities() {                                                     Authentication currentUser = SecurityContextHolder.getContext()             .getAuthentication();                 if (null == currentUser) {                     return Collections.EMPTY_LIST;                 }                 if ((null == currentUser.getAuthorities())                     || (currentUser.getAuthorities().length < 1)) {                     return Collections.EMPTY_LIST;                 }            // currentUser.getAuthorities() 返回的是 GrantedAuthority[]                 List granted = Arrays.asList(currentUser.getAuthorities());                 AuthDao authDao =(AuthDao) AppContext.getInstance().getAppContext().getBean("authDao");                 Collection grantedFunctions = authDao.getFunctionsByRoles(granted);                 return grantedFunctions;             }             /**              * 得到用户功能(Function)的集合,并且验证是否合法              * @param c Collection 类型              * @return Set类型              */             private Set SecurityObjectToFunctions(Collection c) {                 Set target = new HashSet();                 for (Iterator iterator = c.iterator(); iterator.hasNext();) {                     GrantedFunction function = (GrantedFunction) iterator.next();                     if (null == function.getFunction()) {                         throw new IllegalArgumentException(                             "Cannot process GrantedFunction objects which return null from getFunction() - attempting to process "                             + function.toString());                     }                     target.add(function.getFunction());                 }                 return target;             }             /**              * 处理页面标志属性 ,用' '区分              */             private Set parseSecurityString(String functionsString) {                 final Set requiredFunctions = new HashSet();                 final String[] functions = StringUtils                     .commaDelimitedListToStringArray(functionsString);                 for (int i = 0; i < functions.length; i++) {                     String authority = functions[i];                  // Remove the role's whitespace characters without depending on JDK 1.4+                  // Includes space, tab, new line, carriage return and form feed.                  String function = StringUtils.replace(authority, " ", "");                  function = StringUtils.replace(function, "/t", "");                  function = StringUtils.replace(function, "/r", "");                  function = StringUtils.replace(function, "/n", "");                  function = StringUtils.replace(function, "/f", "");                  requiredFunctions.add(new GrantedFunctionImpl(function));                 }                 return requiredFunctions;             }             /**              * 获得用户所拥有的Function 要求的 Function 的交集              * @param granted 用户已经获得的Function              * @param required 所需要的Function              * @return              */                       private Set retainAll(final Collection granted, final Set required) {                 Set grantedFunction = SecurityObjectToFunctions(granted);                 Set requiredFunction = SecurityObjectToFunctions(required);                 // retailAll() 获得 grantedFunction requiredFunction 的交集                 // 即删除 grantedFunction   除了 requiredFunction 的项                 grantedFunction.retainAll(requiredFunction);                 return rolesToAuthorities(grantedFunction, granted);             }             /**              *              * @param grantedFunctions 已经被过滤过的Function                         * @param granted 未被过滤过的,即用户所拥有的Function              * @return              */             private Set rolesToAuthorities(Set grantedFunctions, Collection granted) {                 Set target = new HashSet();                 for (Iterator iterator = grantedFunctions.iterator(); iterator.hasNext();) {                     String function = (String) iterator.next();                     for (Iterator grantedIterator = granted.iterator();                         grantedIterator.hasNext();) {                         GrantedFunction grantedFunction = (GrantedFunction) grantedIterator                             .next();                         if (grantedFunction.getFunction().equals(function)) {                             target.add(grantedFunction);                             break;                         }                     }                 }                 return target;             } }

再说明一下吧,通过 AppContext 获得了Spring的上下文,以及AuthDao(实际意义上讲以不再是单纯的Dao,应该是Service

java代码

package sample.auth; import java.util.Collection; public interface  AuthDao {     /**      *  根据用户的角色集合 得到 用户的 操作权限      * @param granted 已授予用户的角色集合      * @return 操作权限的集合      */         public Collection getFunctionsByRoles(Collection granted); }

以下是AuthDao 的实现

java代码

package sample.auth; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.acegisecurity.GrantedAuthority; import sample.auth.cache.FunctionCache; import sample.auth.cache.info.RoleByNameCache; import sample.dao.IBaseDao; import sample.mappings.function.Function; import sample.mappings.role.Role; public class AuthDaoImpl  implements AuthDao {     private IBaseDao baseDao;     private FunctionCache cache;     private RoleByNameCache roleCache;             public RoleByNameCache getRoleCache() {                 return roleCache;         }         public void setRoleCache(RoleByNameCache roleCache) {                 this.roleCache = roleCache;         }         public FunctionCache getCache() {                 return cache;         }         public void setCache(FunctionCache cache) {                 this.cache = cache;         }         public IBaseDao getBaseDao() {         return baseDao;     }     public void setBaseDao(IBaseDao baseDao) {         this.baseDao = baseDao;     }           public Collection getFunctionsByRoles(Collection granted) {                 Set set = new HashSet();                 if(null == granted) throw new IllegalArgumentException("Granted Roles cannot be null");                         for(Iterator it = granted.iterator();it.hasNext();){                         GrantedAuthority grantedAuthority = (GrantedAuthority)it.next();             Role  role = roleCache.getRoleByRoleNameCache(grantedAuthority.getAuthority()); //             if(role == null){                     role = (Role)baseDao.loadByKey(Role.class, "name", grantedAuthority.getAuthority());                     roleCache.putRoleInCache(role);             }             GrantedFunction[] grantedFunctions = cache.getFunctionFromCache(role.getName());                         if(grantedFunctions == null){                                         Set functions = role.getFunctions();                             for(Iterator it2 = functions.iterator();it2.hasNext();){                            Function function = (Function)it2.next();                     GrantedFunction grantedFunction = new GrantedFunctionImpl(function.getName());                                     set.add(  grantedFunction  );                             }                                               grantedFunctions = (GrantedFunction[]) set.toArray(new GrantedFunction[0]);                             cache.putFuncitonInCache(role.getName(),grantedFunctions);             }                         for(int i = 0 ; i < grantedFunctions.length; i++){                     GrantedFunction grantedFunction = grantedFunctions[i];                     set.add(grantedFunction);             }                 }                         return set;         } }

3 基于hibernate的用户验证 acegi 默认的 的 用户验证是 通过UserDetailsService 接口 实现的 也就是说我们只要实现了 它的loadUserByUsername 方法。

java代码

public UserDetails loadUserByUsername(String username)         throws UsernameNotFoundException, DataAccessException;

以下是我的实现

java代码

package sample.auth; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Set; import org.acegisecurity.GrantedAuthority; import org.acegisecurity.GrantedAuthorityImpl; import org.acegisecurity.userdetails.User; import org.acegisecurity.userdetails.UserDetails; import org.acegisecurity.userdetails.UserDetailsService; import org.acegisecurity.userdetails.UsernameNotFoundException; import org.springframework.dao.DataAccessException; import sample.auth.cache.AuthorityBasedUserCache; import sample.dao.IBaseDao; import sample.mappings.role.Role; import sample.utils.MisUtils; public class HibernateDaoImpl implements UserDetailsService{                 private String rolePrefix = "";         private boolean usernameBasedPrimaryKey = false;     private AuthorityBasedUserCache cache;     private IBaseDao baseDao;         public String getRolePrefix() {                 return rolePrefix;         }                 public void setRolePrefix(String rolePrefix) {                 this.rolePrefix = rolePrefix;         }                 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {                 UserDetails user = getUsersByUsernameQuery(username);                 if(user == null) return null;                                 GrantedAuthority[] arrayAuths =getAuthoritiesByUsernameQuery(username);              if (arrayAuths.length == 0) {                     throw new UsernameNotFoundException("User has no GrantedAuthority");                 }                    return new User(username, user.getPassword(), user.isEnabled(),                      true, true, true, arrayAuths);         }         /**         * 根据用户名查找用户         * @param username         * @return         * @throws DataAccessException         */         public UserDetails getUsersByUsernameQuery(String username)throws DataAccessException {                         sample.mappings.user.User misUser = (sample.mappings.user.User)baseDao.loadByKey(sample.mappings.user.User.class,"name",username);                         if(misUser != null)                         {                         org.acegisecurity.userdetails.UserDetails user =                         new User(misUser.getName(),misUser.getPassword(),MisUtils.parseBoolean(misUser.getEnable()),true,true,true,getAuthoritiesByUsernameQuery(username));                         return user;                         }else                         return null;                        }                 /**           * 根据用户名查找角色           * @param username           * @return GrantedAuthority[] 用户角色           * @throws DataAccessException           */         public GrantedAuthority[] getAuthoritiesByUsernameQuery(String username)                 throws DataAccessException {                 sample.mappings.user.User misUser                 = (sample.mappings.user.User)baseDao.loadByKey(sample.mappings.user.User.class,"name",username);         if(misUser != null){                 GrantedAuthority[] grantedAuthoritys = cache.getAuthorityFromCache(misUser.getName());                         if(grantedAuthoritys == null){                                         Set roles =     misUser.getRoles();                         Iterator it = roles.iterator();                                         List list = new ArrayList();                         while(it.hasNext() ){                                         GrantedAuthorityImpl gai = new GrantedAuthorityImpl(  ((Role)it.next()).getName()  );                                 list.add(gai);                                 }                         grantedAuthoritys =(GrantedAuthority[]) list.toArray(new  GrantedAuthority[0]);                         cache.putAuthorityInCache(misUser.getName(),grantedAuthoritys);                         return grantedAuthoritys;                                 }                return grantedAuthoritys;         }                 return null; }         public IBaseDao getBaseDao() {                 return baseDao;         }         public void setBaseDao(IBaseDao baseDao) {                 this.baseDao = baseDao;         }         public AuthorityBasedUserCache getCache() {                 return cache;         }         public void setCache(AuthorityBasedUserCache cache) {                 this.cache = cache;         } } 通过以上对acegi 处理,足以满足我们目前在spring下基于RBAC的动态权限管理。同时在对频繁的数据库查询上使用了Ehcache作为缓存,在性能上有了很大的改善。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值