SSM框架_3(shiro權限管理)

2017-05-01(採用shiro,實現用戶的登陸與權限控制等)

一、添加spring-shiro.xml和ehcache-shiro.xml

spring-shiro.xml:shiro主配置文件html

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd"
	   default-lazy-init="true">

	<description>Shiro安全配置</description>

	<!-- 項目自定義的Realm -->
	<bean id="userShiroRealm" class="com.ssm.shiro.UserShiroRealm" />

	<!--安全管理器 -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<!--設置自定義Realm -->
		<property name="realm" ref="userShiroRealm" />
		<!--將緩存管理器,交給安全管理器 -->
		<property name="cacheManager" ref="shiroEhcacheManager" />
	</bean>

	<!-- Shiro Filter -->
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<!-- 安全管理器 -->
		<property name="securityManager" ref="securityManager" />
		<!-- 默認的登錄訪問url -->
		<property name="loginUrl" value="/manage/login" />
		<!-- 沒有權限跳轉的url -->
		<property name="unauthorizedUrl" value="/unauth"/>
		<!-- 自定義filter配置 -->
		<property name="filters">
			<map>
				<!-- 將自定義 的FormAuthenticationFilter注入shiroFilter中 -->
			</map>
		</property>
		<property name="filterChainDefinitions">
			<value>
				/manage/login = anon<!-- 不須要認證 能夠理解爲匿名用戶或遊客 -->
				/manage/loginPost = anon<!-- 不須要認證 能夠理解爲匿名用戶或遊客 -->
				/guest/** = anon
				/manage/** = authc
			</value>
		</property>
	</bean>

	<!-- 用戶受權信息Cache, 採用EhCache -->
	<bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
		<property name="cacheManagerConfigFile" value="classpath:conf/shiro/ehcache-shiro.xml" />
	</bean>

	<!-- 在方法中 注入 securityManager ,進行代理控制 -->
	<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
		<property name="staticMethod"
				  value="org.apache.shiro.SecurityUtils.setSecurityManager" />
		<property name="arguments" ref="securityManager" />
	</bean>

	<!-- 保證明現了Shiro內部lifecycle函數的bean執行 -->
	<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

	<!-- AOP式方法級權限檢查 -->
	<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
			depends-on="lifecycleBeanPostProcessor" />

	<!-- 啓用shrio受權註解攔截方式 -->
	<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
		<property name="securityManager" ref="securityManager" />
	</bean>
</beans>

ehcache-shiro.xml:Shrio缺省提供了基於ehCache來緩存用戶認證信息和受權信息的實現,該xml爲指定ehCache的配置文件前端

<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shiroCache">
	<defaultCache maxElementsInMemory="10000" eternal="false"
		timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false"
		diskPersistent="false" diskExpiryThreadIntervalSeconds="120" />
</ehcache>

二、添加ShiroUser.java和UserShiroRealm.java

ShiroUser.java:自定義Authentication對象,使得Subject除了攜帶用戶的登陸名外還能夠攜帶更多信息java

package com.ssm.shiro;

import lombok.Data;
import lombok.Getter;

import java.io.Serializable;

/**
 * @description:自定義Authentication對象,使得Subject除了攜帶用戶的登陸名外還能夠攜帶更多信息
 */
@Data
public class ShiroUser implements Serializable {

    private static final long serialVersionUID = -1373760761780840081L;
    public String userId;
    public String userName;

    public ShiroUser( String userId, String userName) {
        this.userId = userId;
        this.userName = userName;
    }

}

UserShiroRealm.java:經過UserShiroRealm 繼承Shiro驗證用戶登陸的類爲自定義的AuthorizingRealmweb

package com.ssm.shiro;

import com.ssm.model.User;
import com.ssm.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.*;

/**
 * @description:shiro權限認證
 * 經過UserShiroRealm 繼承Shiro驗證用戶登陸的類爲自定義的AuthorizingRealm 
 */
public class UserShiroRealm extends AuthorizingRealm {

    private static Logger LOGGER = LoggerFactory.getLogger(UserShiroRealm.class);

    @Autowired
    private UserService userService;

    /**
     * 統一用戶登陸入口
     * Shiro登陸認證(原理:用戶提交 用戶名和密碼  --- shiro 封裝令牌 ---- realm 經過用戶名將密碼查詢返回 ---- 
     * shiro 自動去比較查詢出密碼和用戶輸入密碼是否一致---- 進行登錄控制 )
     * 
     * 其中密碼加密方式:md5 32爲加密
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
        LOGGER.info("Shiro開始登陸認證");
        UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
        User user = userService.findUserByUserId(token.getUsername());
        // 帳號不存在
        if (user == null) {
            return null;
        }
        ShiroUser shiroUser = new ShiroUser(user.getId(), user.getUserName());
        // 認證緩存信息
        return new SimpleAuthenticationInfo(shiroUser, user.getPassword().toCharArray(), getName());
    }
    /**
     * Shiro權限認證
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        Set<String> urlSet = new HashSet<String>();
        urlSet.add("/manage/user");
        urlSet.add("/manage/user/user");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(urlSet);
        return info;

    }
    
    /**
     * 清除權限的緩存,但該方法執行後,前端頁面的刷新會略有延遲,這是緩存形成的
     * 
     * @author jiangCaiJun
     */
    public void updateAuthz(Long roleId){
        Set<String> urlSet = new HashSet<String>();
        List<Map<String, String>>  roleResourceList= new ArrayList<>();
        if (roleResourceList.size() >0) {
            for (Map<String, String> map : roleResourceList) {
            	if(map != null){
            		urlSet.add(map.get("user"));
            	}
            }
        }
        //注意:此處模擬塞入資源權限對應的標籤,實際項目中,應從對應的資源與權限關係表中獲取
        urlSet.add("/manage/user");
        urlSet.add("/manage/user/user");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(urlSet);
    }
    
}

二、登陸方法login

前臺ajax請求,傳遞username和password過來,封裝成token,進行登陸校驗ajax

@RequestMapping(value = "/loginPost", method = RequestMethod.POST)
    @ResponseBody
    public Object loginPost(String username, String password) {
        if (StringUtils.isBlank(username)) {
            return renderError("用戶名不能爲空");
        }
        if (StringUtils.isBlank(password)) {
            return renderError("密碼不能爲空");
        }
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        try {
            subject.login(token);
        } catch (UnknownAccountException e) {
            logger.error("帳號不存在:{}", e);
            return renderError("帳號不存在");
        } catch (DisabledAccountException e) {
            logger.error("帳號未啓用:{}", e);
            return renderError("帳號未啓用");
        } catch (IncorrectCredentialsException e) {
            logger.error("密碼錯誤:{}", e);
            return renderError("密碼錯誤");
        } catch (ExcessiveAttemptsException e) {
            logger.error("登陸失敗屢次,帳戶鎖定:{}", e);
            return renderError("登陸失敗屢次,帳戶鎖定10分鐘");
        } catch (RuntimeException e) {
            logger.error("未知錯誤,請聯繫管理員:{}", e);
            return renderError("未知錯誤,請聯繫管理員");
        }
        return renderSuccess();
    }

三、登陸的實現

登陸頁面以下spring

登陸頁面

輸入正確或錯誤的用戶名和密碼,返回分別以下apache

登陸_成功

登陸_失敗

四、shiro的權限控制

4.1 回到UserShiroRealm.java,向set中塞入了該user有權限訪問的url請求地址。此處模擬塞入了 "/manage/user"和"/manage/user/user"。

/**
     * Shiro權限認證
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        Set<String> urlSet = new HashSet<String>();
        urlSet.add("/manage/user");
        urlSet.add("/manage/user/user");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(urlSet);
        return info;

    }

4.2 修改UserController.java方法:利用shiro的RequiresPermissions註解,只有在上一步成功添加了對應的url請求地址,才能請求該url

@Controller
@RequestMapping("/manage/user")
@RequiresPermissions(value = { "/manage/user" })
public class UserController {
	private static final Logger LOG = Logger.getLogger(UserController.class);

 	@Autowired
	private UserService userService;

	/**
	 * 用戶頁
	 *
	 * @return
	 */
	@RequiresPermissions(value = { "/manage/user/user" })
	@RequestMapping(value = "/user", method = RequestMethod.GET)
	public String userManager(Model model) {
		String id = "1";
		User user = userService.getUser(id);
		model.addAttribute("user", user);
		LOG.info(user.toString());
		return "user/showUser";
	}
}

4.3 html中的shiro:hasPermission標籤

利用<shiro:hasPermission name="/manage/user/user"></shiro:hasPermission">來顯示或隱藏<strong> /manage/user/user</strong>,若沒有顯示,則證實還沒有擁有該權限。緩存

<shiro:hasPermission name="/manage/user/user">
    <span>此處權限控制</span>
	<a href="${staticPath }/manage/user/user">
		${staticPath }/manage/user/user
	</a>
</shiro:hasPermission>

注意:要在頁面上添加shiro標籤安全

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>

該頁面的信息以下圖:mybatis

首頁

參考連接:Apache Shiro 使用手冊(三)Shiro 受權 - kdboy - ITeye技術網站

具體信息以下圖:

首頁

五、maven + spring + spring MVC + mybatis + shiro 項目項目結構圖

maven + spring + spring MVC + mybatis + shiro 項目項目結構圖