S2SM集成Shiro-登錄驗證

Struts2+Spring+Mybatis集成Apache Shiro權限控制框架,本章主要講述如何利用Shiro框架實現登錄驗證。


擴展Shiro的表單驗證過濾器

簡述

這裏主要用於登錄驗證失敗後,將異常封裝成對應的提示消息回寫到請求中,方便頁面顯示驗證失敗原因。如果不擴展,Shiro會將異常寫到請求中(key:shiroLoginFailure),前端jsp頁面可以通過shiroLoginFailure鍵獲取取對應的異常信息。

源碼-FormAuthenticationFilter.java

package cn.zfcr.shiro.filter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.springframework.stereotype.Service;

/** 
    文件名:FormAuthenticationFilter.java
    類說明:  Shiro 表單驗證過濾器擴展 寫入登錄驗證失敗的錯誤提示
    作者:章鋒
    郵箱: [email protected]
    日期:2017年6月22日 下午2:29:54  
    描述:例:章鋒-認證
    版本:1.0
*/
@Service
public class FormAuthenticationFilter extends org.apache.shiro.web.filter.authc.FormAuthenticationFilter {
    
    public static final String DEFAULT_MESSAGE_PARAM = "message";
    
    private String messageParam = DEFAULT_MESSAGE_PARAM;

	/**
	 * 登錄失敗調用事件
	 */
	@Override
	protected boolean onLoginFailure(AuthenticationToken token,
			AuthenticationException e, ServletRequest request, ServletResponse response) {
		String className = e.getClass().getName(), message = "";
		if (IncorrectCredentialsException.class.getName().equals(className)
				|| UnknownAccountException.class.getName().equals(className)){
			message = "用戶名或密碼錯誤, 請重試.";
		}
		else if (e.getMessage() != null && StringUtils.startsWith(e.getMessage(), "msg:")){
			message = StringUtils.replace(e.getMessage(), "msg:", "");
		}
		else{
			message = "系統異常,請稍後再試!";
			e.printStackTrace();
		}
        request.setAttribute(getFailureKeyAttribute(), className);
        request.setAttribute(getMessageParam(), message);
        return true;
	}
	
	public String getMessageParam() {
        return messageParam;
    }
}


自定義安全認證實現類

簡述

安全認證實現類,在這裏,主要有兩個用途:
1、在登錄驗證時,根據前端填寫的用戶名,從數據庫中取出對應的用戶名密碼等信息。
2、訪問需要權限驗證的url時,從數據中查出當前登錄用戶的權限。

源碼-SystemAuthorizingRealm.java

package cn.zfcr.shiro.realm;

import java.io.Serializable;

import javax.annotation.PostConstruct;
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.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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.apache.shiro.util.ByteSource;
import org.springframework.stereotype.Service;

import cn.zfcr.base.user.service.UserInfoService;
import cn.zfcr.busi.sysmanage.entity.UserInfo;
import cn.zfcr.common.constants.GlobalConstants;
import cn.zfcr.system.utils.Encodes;

/** 
    文件名:SystemAuthorizingRealm.java
    類說明:  Shiro 安全認證實現類
    作者:章鋒
    郵箱: [email protected]
    日期:2017年6月21日 下午2:29:54  
    描述:例:章鋒-認證
    版本:1.0
*/
@Service
public class SystemAuthorizingRealm extends AuthorizingRealm {

	private Logger log = Logger.getLogger(this.getClass().getName());
	
	@Resource
	private UserInfoService userInfoService;

	/**
	 * 認證回調函數, 登錄時調用
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) {
	    UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
	    if(log.isDebugEnabled()){
	        log.debug("登錄驗證:" + token.toString());
	    }
		
		// 校驗用戶名密碼
		UserInfo userInfo = userInfoService.getByLoginName(token.getUsername());
		if (userInfo != null) {
			if (GlobalConstants.NO.equals(userInfo.getLoginFlag())){
				throw new AuthenticationException("該帳號禁止登錄.");
			}
			byte[] salt = Encodes.decodeHex(userInfo.getPassword().substring(0,16));
			return new SimpleAuthenticationInfo(new Principal(userInfo), 
					userInfo.getPassword().substring(16), ByteSource.Util.bytes(salt), getName());
		} else {
			return null;
		}
	}

	/**
	 * 授權查詢回調函數, 進行鑑權但緩存中無用戶的授權信息時調用
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		Principal principal = (Principal) getAvailablePrincipal(principals);
		UserInfo userInfo = userInfoService.getByLoginName(principal.getLoginName());
		if (userInfo != null) {
			SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
			
			// 添加用戶權限
			info.addStringPermission("user:index");
			
			return info;
		} else {
			return null;
		}
	}
	
	/**
	 * 設定密碼校驗的Hash算法與迭代次數
	 */
	@PostConstruct
	public void initCredentialsMatcher() {
		HashedCredentialsMatcher matcher = new HashedCredentialsMatcher("SHA-1");
		matcher.setHashIterations(1024);
		setCredentialsMatcher(matcher);
	}
	
	/**
	 * 授權用戶信息
	 */
	public static class Principal implements Serializable {

		private static final long serialVersionUID = 1L;
		
		private String id; // 編號
		private String loginName; // 登錄名
		private String name; // 姓名
		private boolean mobileLogin; // 是否手機登錄
		
		public Principal(UserInfo user) {
            this.id = user.getId();
            this.loginName = user.getLoginName();
            this.name = user.getName();
        }

		public Principal(UserInfo user, boolean mobileLogin) {
			this.id = user.getId();
			this.loginName = user.getLoginName();
			this.name = user.getName();
			this.mobileLogin = mobileLogin;
		}

		public String getId() {
			return id;
		}

		public String getLoginName() {
			return loginName;
		}

		public String getName() {
			return name;
		}

		public boolean isMobileLogin() {
			return mobileLogin;
		}

		@Override
		public String toString() {
			return id;
		}

	}
}

Shiro配置文件

spring-context-shiro.xml

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

	<description>Shiro Configuration</description>

	<!-- Shiro權限過濾過濾器定義 -->
	<bean name="shiroFilterChainDefinitions" class="java.lang.String">
		<constructor-arg>
			<value>
				/common/** = anon
				/css/** = anon
				/framework/** = anon
				/images/** = anon
				/js/** = anon
				
				/login-logout.action = logout
				/login-main.action = authc
				/z/** = user
			</value>
		</constructor-arg>
	</bean>
	
	<!-- 安全認證過濾器 -->
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<property name="securityManager" ref="securityManager" />
		<!-- 跳轉到登錄頁面的action,以及登錄表單提交的action(表單必須設置post方式) -->
		<property name="loginUrl" value="/login-main.action" />
		<!-- 登錄成功後跳轉action,可不配置,默認爲/ -->
		<property name="successUrl" value="/" />
		<!-- 自定義過濾器配置 -->
		<property name="filters">
            <map>
                <entry key="cas" value-ref="casFilter"/>
                <entry key="authc" value-ref="formAuthenticationFilter"/>
            </map>
        </property>
        <!-- 過濾器的驗證策略配置,指定哪些不需要權限,哪些需要權限,需要什麼權限 -->
		<property name="filterChainDefinitions">
			<ref bean="shiroFilterChainDefinitions"/>
		</property>
	</bean>
	
	<!-- CAS認證過濾器 -->  
	<bean id="casFilter" class="org.apache.shiro.cas.CasFilter">  
		<property name="failureUrl" value="/login-main.action"/>
	</bean>
	
	<!-- 定義Shiro安全管理配置 -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="systemAuthorizingRealm" />
		<property name="cacheManager" ref="cacheManager" />
	</bean>
	
	<!-- 緩存配置,如果不配置,shiro不使用緩存 -->
	<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
		<property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
	</bean>
	
	<!-- 保證實現了Shiro內部lifecycle函數的bean執行 -->
	<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
	
	<!-- AOP式方法級權限檢查,在方法上指定@RequiresPermissions註解,即可進行權限驗證  -->
	<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor">
		<property name="proxyTargetClass" value="true" />
	</bean>
	<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    	<property name="securityManager" ref="securityManager"/>
	</bean>
	
</beans>

簡述

Struts、Spring、Mybatis的配置文件,這裏不貼出來了,集成Shiro不需要在這些配置文件中額外添加東西。
http://www.cppblog.com/guojingjia2006/archive/2014/05/14/206956.html,這篇文章中對Shiro的內置過濾器描述的很詳細,可以參考下。


Web.xml

添加掃描shiro配置文件
<param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml,classpath:spring-context-shiro.xml</param-value>
<!--     <param-value>classpath:applicationContext.xml</param-value> -->
  </context-param>

指定shiro的過濾器委託給Spring的DelegatingFilterProxy處理
<filter>  
	    <filter-name>shiroFilter</filter-name>  
	    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
	    <init-param>  
	        <param-name>targetFilterLifecycle</param-name>  
	        <param-value>true</param-value>  
	    </init-param>  
	</filter>  
	<filter-mapping>  
	    <filter-name>shiroFilter</filter-name>  
	    <url-pattern>/*</url-pattern>  
	</filter-mapping>

登錄頁面的主要代碼-main.jsp

        alert('${shiroLoginFailure}');
	alert('${message}');<form id="form1" name="form1" class="login" 
	       autocomplete="off" action="/xxproject/login-main.action" method="post">
	        <p class="title">
                            登錄
	        </p>
	        <input type="text" name="username" id="username" placeholder="用戶名或郵箱" autofocus/>
	        <i class="fa fa-user">
	        </i>
	        <input type="password" name="password" id="password" placeholder="密碼" />
	        <i class="fa fa-key">
	        </i>
	        <a href="#">
	                   忘記密碼?
	        </a>
	        <button type="submit" id="btnLogin">
	            <span class="state">
	                            登 錄
	            </span>
	        </button>
	    </form>

文本框用戶的name必須是username
密碼框的name必須是password
必須指定method="post"

測試過程:
輸入url進行登錄頁面,http:/localhost:8080/xxproject/login-main.action
輸入錯誤的用戶名,點擊登錄,此時,應該會彈出UnknownAccountException和用戶名或密碼錯誤, 請重試.的提示。
輸入正確的用戶名和密碼,點擊登錄,此時,應該會跳轉到項目主頁。


發佈了47 篇原創文章 · 獲贊 10 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章