<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Tao

    Tao obeys its own inherent Nature

    集成ACEGI 進行權限控制

    一. 簡單介紹

    1.1 本文目的

    集成Acegi到自己的項目中, 并且將用戶信息和權限放到數據庫, 提供方法允許權限動態變化,變化后自動加載最新的權限

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

    閱讀本文需要對Spring有一定的了解, 如果你還沒有接觸過, 有些地方可能不容易理解, 這時候可能需要參考本文后附的Spring地址, 先了解一下Spring的基本知識.

    本文使用的是Mysql數據庫, 如果你使用其他的數據庫, 可能需要修改相應的SQL.

    本文及所附的全部代碼放在http://acegi-test.sourceforge.net/

    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, 列出全部地址的鏈接, 同時列出了當前用戶有權限的地址鏈接

    posted on 2008-01-30 17:28 wade 閱讀(4292) 評論(7)  編輯  收藏 所屬分類: Java 、Mysql 、Acegi

    評論

    # re: 集成ACEGI 進行權限控制 2008-02-17 12:54 93ttl

    好東西呀……  回復  更多評論   

    # re: 集成ACEGI 進行權限控制[未登錄] 2008-03-01 21:13 一農

    不錯,很清晰。  回復  更多評論   

    # Excellent[未登錄] 2008-05-06 03:39 John

    All I want is almost here.   回復  更多評論   

    # re: 集成ACEGI 進行權限控制 2008-06-11 10:41 wmj2003

    建議作者采用最新版本的acegi,再測試一下。  回復  更多評論   

    # re: 集成ACEGI 進行權限控制 2008-07-09 12:59 謝謝

    流程很清晰,不會像有些人,為了發放貼著,僅是copy+paste
    如果我可以給你加分,我會加9分
    還有一份,等我驗證成功以后,看看還能不能回到該頁面
    如果可以,還有一分也給你加上

    謝謝  回復  更多評論   

    # re: 集成ACEGI 進行權限控制 2008-07-16 14:45 swantt

    WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);

    wac.getAutowireCapableBeanFactory().autowireBeanProperties(this, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, false);
    ConfigAttributeDefinition attr =objectDefinitionSource.getAttributes(getRequestedResource(url));

    在這里objectDefinitionSource報空指針了..這個全局變量沒有賦值呀.用自動裝載不知道怎么弄的. 請樓主指點.我的QQ:76322540,請加我QQ.謝謝!  回復  更多評論   

    # re: 集成ACEGI 進行權限控制 2008-07-17 16:24 wade

    objectDefinitionSource 需要在配置文件中定義, 通過autoWire, spring會注入具體實例

    @Autowired
    private FilterInvocationDefinitionSource objectDefinitionSource;

    在本文附帶的例子中, 相應bean的id 是: rolePermissionService, 你可以在spring的配置文件中找到
    BTW: 我沒有QQ
      回復  更多評論   

    導航

    <2008年1月>
    303112345
    6789101112
    13141516171819
    20212223242526
    272829303112
    3456789

    統計

    常用鏈接

    留言簿(7)

    隨筆分類

    隨筆檔案

    相冊

    Photo

    搜索

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 久久精品国产亚洲AV高清热| 蜜桃视频在线观看免费网址入口| 亚洲网址在线观看| 午夜老司机免费视频| 怡红院免费的全部视频| 亚洲精品乱码久久久久久V | 成人福利在线观看免费视频| 亚洲啪啪免费视频| 亚洲一区二区三区电影| 国产亚洲AV夜间福利香蕉149 | 中文字幕在线观看免费| 国产午夜亚洲精品不卡| 亚洲精品一二三区| 亚洲乱码中文论理电影| 亚洲成a人片在线观看中文app| 亚洲av无码av制服另类专区| 国产精一品亚洲二区在线播放| AV在线亚洲男人的天堂| 亚洲午夜久久久久妓女影院| 亚洲人成国产精品无码| 伊人久久大香线蕉亚洲五月天| 免费无码又爽又高潮视频| 国产一区二区免费在线| 免费h黄肉动漫在线观看| 亚洲片一区二区三区| 精品亚洲综合在线第一区| 亚洲AV无码专区电影在线观看| 亚洲精品乱码久久久久久久久久久久 | 亚洲福利电影在线观看| 97久久国产亚洲精品超碰热| 亚洲精品天堂无码中文字幕| h视频免费高清在线观看| 久久国产精品免费网站| 日韩欧美一区二区三区免费观看| 又粗又大又猛又爽免费视频| 亚洲国产成人片在线观看| 亚洲av无一区二区三区| a级毛片无码免费真人久久| 成年男女男精品免费视频网站| 免费永久在线观看黄网站| 亚洲VA中文字幕不卡无码|