Struts2+Spring+Mybatis集成Apache Shiro權限控制框架,本章主要講述如何利用Shiro框架實現登錄驗證。
擴展Shiro的表單驗證過濾器
簡述
源碼-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;
}
}
自定義安全認證實現類
簡述
源碼-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>
簡述
Web.xml
<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