shiro權限控制的簡單實現

權限控制經常使用的有shiro、spring security,二者相比較,各有優缺點,此篇文章以shiro爲例,實現系統的權限控制。

1、數據庫的設計

簡單的五張表,用戶、角色、權限及關聯表:html

CREATE TABLE `sysrole` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `name` varchar(255) NOT NULL,
  `role` varchar(255) NOT NULL COMMENT '角色名',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='角色表';

CREATE TABLE `sysuser` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `username` varchar(250) NOT NULL,
  `password` varchar(250) NOT NULL,
  `salt` varchar(250) NOT NULL COMMENT '密碼鹽',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='用戶表';

CREATE TABLE `user_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `role_id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='用戶角色表';

CREATE TABLE `syspermission` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(250) NOT NULL COMMENT '權限名稱',
  `type` int(1) NOT NULL COMMENT '權限類型,1菜單menu,2按鈕button,3數據data',
  `permission` varchar(250) NOT NULL COMMENT '權限表達式',
  `parent_id` int(11) NOT NULL COMMENT '父級ID',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8 COMMENT='權限表';

CREATE TABLE `role_permission` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_id` int(11) NOT NULL,
  `permission_id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=35 DEFAULT CHARSET=utf8 COMMENT='角色權限表';

2、配置shiro

1.pom.xml文件中引入shiro的jar包

     <dependency>
              <groupId>org.apache.shiro</groupId>
              <artifactId>shiro-spring</artifactId>
              <version>1.3.2</version>
        </dependency>
        
        <dependency>
              <groupId>org.apache.shiro</groupId>
              <artifactId>shiro-core</artifactId>
              <version>1.3.2</version>
        </dependency>
        
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.3.2</version>
        </dependency>
        
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-guice</artifactId>
            <version>1.3.2</version>
        </dependency>

二、編寫shiro的配置文件

import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;

import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;


@Configuration
public class ShiroConfig {
    
    private static Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
    
    //緩存管理器
    @Bean(name = "cacheShiroManager")
    public EhCacheManager getCacheManager(){
        return new EhCacheManager();
    }
    
    //生命週期處理器
    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }
    
    //hash加密處理
    @Bean(name = "hashedCredentialsMatcher")
    public HashedCredentialsMatcher getHashedCredentialsMatcher(){
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("MD5");
        credentialsMatcher.setHashIterations(2);//散列的次數,好比散列兩次,至關於 md5(md5(""));
        credentialsMatcher.setStoredCredentialsHexEncoded(true);
        return     credentialsMatcher;    
    }
    
    //瀏覽器會話的cookie管理
    @Bean(name = "sessionIdCookie")
    public SimpleCookie getSessionIdCookie(){
        SimpleCookie cookie = new SimpleCookie("sid");
        cookie.setHttpOnly(true);
        cookie.setMaxAge(-1);//瀏覽器關閉時失效此Cookie;
        return cookie;
    }
    
    //記住個人cookie管理
    @Bean(name = "rememberMeCookie")
    public SimpleCookie getRememberMeCookie(){
        SimpleCookie cookie = new SimpleCookie("rememberMe");
        //若是httyOnly設置爲true,則客戶端不會暴露給客戶端腳本代碼,使用HttpOnly cookie有助於減小某些類型的跨站點腳本攻擊;
        cookie.setHttpOnly(true);
        cookie.setMaxAge(2592000);//記住個人cookie有效期30天
        return cookie;
    }
    
    //記住我cookie管理器
    @Bean
    public CookieRememberMeManager getRememberManager(){
        CookieRememberMeManager meManager = new CookieRememberMeManager();
        meManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
        meManager.setCookie(getRememberMeCookie());
        return meManager;
    }
    
    //session驗證管理器
    @Bean(name = "sessionValidationScheduler")
    public ExecutorServiceSessionValidationScheduler getExecutorServiceSessionValidationScheduler(){
        ExecutorServiceSessionValidationScheduler scheduler = new ExecutorServiceSessionValidationScheduler();
        //設置session驗證時間,15分鐘一次
        scheduler.setInterval(900000);
        return scheduler;
    }
    
    @Bean(name = "sessionManager")
    public DefaultWebSessionManager getSessionManage(){
        
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setGlobalSessionTimeout(1800000);//過時時間30分鐘
        //session按期驗證
        sessionManager.setSessionValidationScheduler(getExecutorServiceSessionValidationScheduler());
        sessionManager.setDeleteInvalidSessions(true);
        //會話cookie
        sessionManager.setSessionIdCookie(getSessionIdCookie());
        sessionManager.setSessionIdCookieEnabled(true);
        sessionManager.setSessionValidationSchedulerEnabled(true);
        //session監聽
        LinkedList<SessionListener> list = new LinkedList<SessionListener>();
        list.add(new MyShiroSessionListener());
        sessionManager.setSessionListeners(list);
        //session的存儲
        EnterpriseCacheSessionDAO cacheSessionDAO = new EnterpriseCacheSessionDAO();
        sessionManager.setCacheManager(getCacheManager());
        sessionManager.setSessionDAO(cacheSessionDAO);
        return sessionManager;
    }
    
    @Bean(name = "myRealm")
    public AuthorizingRealm  getShiroRealm(){
        AuthorizingRealm  realm = new MyShiroRealm(getCacheManager(),getHashedCredentialsMatcher());
        realm.setName("my_shiro_auth_cache");
//        realm.setAuthenticationCache(getCacheManager().getCache(realm.getName()));
        realm.setAuthenticationTokenClass(UsernamePasswordToken.class);
        realm.setCredentialsMatcher(getHashedCredentialsMatcher());
        return realm;
    }
    
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getSecurityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setCacheManager(getCacheManager());
        securityManager.setRealm(getShiroRealm());
        securityManager.setRememberMeManager(getRememberManager());
        securityManager.setSessionManager(getSessionManage());
        return securityManager;
    }
    
    @Bean
    public MethodInvokingFactoryBean getMethodInvokingFactoryBean(){
        MethodInvokingFactoryBean factoryBean = new MethodInvokingFactoryBean();
        factoryBean.setStaticMethod("org.apache.shiro.SecurityUtils.setSecurityManager");
        factoryBean.setArguments(new Object[]{getSecurityManager()});
        return factoryBean;
    }
    
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator getAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }
    
    @Bean
    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(){
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(getSecurityManager());
        return advisor;
    }
    
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(){
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(getSecurityManager());
        //設置登陸頁面路徑
        factoryBean.setLoginUrl("/login");
        //設置登陸成功跳轉的路徑,此方法有bug
     // factoryBean.setSuccessUrl("/manager/hello");
        //將無需攔截的方法及頁面使用anon配置        
        filterChainDefinitionMap.put("", "anon");        
        //將需認證的方法及頁面使用authc配置
        filterChainDefinitionMap.put("", "authc");  
factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return factoryBean; } @Bean(name = "shiroDialect") public ShiroDialect shiroDialect(){ return new ShiroDialect(); } }

三、自定義shiroRealm

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.thymeleaf.util.StringUtils;


public class MyShiroRealm extends AuthorizingRealm {
    //注入查詢用戶信息的service層
    @Resource
        private IAuthorityService authorityService;
    
    private Logger log = Logger.getLogger(MyShiroRealm.class);
    public MyShiroRealm(CacheManager cacheManager, CredentialsMatcher matcher) {
        super(cacheManager, matcher);
    }
    
    //受權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {
        
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        SysUser user = (SysUser) principals.getPrimaryPrincipal();
        for(SysRole role:user.getRoleList()){
            authorizationInfo.addRole(role.getRole());
            for(SysPermission p:role.getPermissions()){
                authorizationInfo.addStringPermission(p.getPermission());
            }
        }
        
        return authorizationInfo;
        
    }

   //認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken user = (UsernamePasswordToken) token;
        String username = user.getUsername();
        String password = user.getPassword().toString();
        user.isRememberMe();
        
        if(StringUtils.isEmpty(username)){
            throw new IncorrectCredentialsException("username is null");
        }else if(StringUtils.isEmpty(password)){
            throw new IncorrectCredentialsException("password is null");
        }
        //根據帳戶名稱查詢用戶信息
        SysUser sysUser = authorityService.findByUsername(username);
        if(sysUser == null){
            log.error("用戶不存在"+username);
            return null;
        }
     //密碼加密策略可自定義,此處使用自定義的密碼鹽
CredentialsSalt
    SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(sysUser,sysUser.getPassword(), ByteSource.Util.bytes(sysUser.getCredentialsSalt()),getName());
    
return authenticationInfo;
  }
}

四、自定義session監聽

import org.apache.log4j.Logger;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;

public class MyShiroSessionListener implements SessionListener {
    
    private Logger log = Logger.getLogger(MyShiroSessionListener.class);
    @Override
    public void onStart(Session session) {
        log.info("----會話建立:"+session.getId());
    }

    @Override
    public void onStop(Session session) {
        log.info("----會話中止:"+session.getId());
    }

    @Override
    public void onExpiration(Session session) {
        log.info("----會話過時:"+session.getId());
    }

}

 

3、使用註解控制後臺方法的權限

@RequiresAuthentication,認證經過可訪問java

@RequiresPermissions("***"),有***權限可訪問web

@RequiresGuest,遊客便可訪問spring

@RequiresRoles("***"),有***角色可訪問數據庫

@RequiresUser("***"),是***用戶可訪問apache

 

4、使用shiro標籤細粒度控制頁面按鈕及數據的權限

thyemleaf模板引入shiro標籤庫瀏覽器

<html xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">緩存

<shiro:guest/>
<shiro:user/>
<shiro:principal/>
<shiro:hasPermission/> 
<shiro:lacksPermission/>
<shiro:hasRole/>
<shiro:lacksRole/>
<shiro:hasAnyRoles/>
<shiro:authenticated/>
<shiro:notAuthenticated/>

使用方法參考:shiro官網cookie

相關文章
相關標籤/搜索