1. 程式人生 > >Shiro(3)實現驗證碼認證

Shiro(3)實現驗證碼認證

驗證碼是有效防止暴力破解的一種手段,常用做法是在服務端產生一串隨機字串與當前使用者會話關聯(我們通常說的放入 Session),然後向終端使用者展現一張經過“擾亂”的圖片,只有當用戶輸入的內容與服務端產生的內容相同時才允許進行下一步操作

產生驗證碼

作為演示,我們選擇開源的驗證碼元件 kaptcha。這樣,我們只需要簡單配置一個 Servlet,頁面通過 IMG 標籤就可以展現圖形驗證碼。

	<servlet>
		<servlet-name>kaptcha</servlet-name>
		<servlet-class>
			com.google.code.kaptcha.servlet.KaptchaServlet
		</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>kaptcha</servlet-name>
		<url-pattern>/images/kaptcha.jpg</url-pattern>
	</servlet-mapping>

擴充套件 UsernamePasswordTokenShiro 表單認證,頁面提交的使用者名稱密碼等資訊,用 UsernamePasswordToken 類來接收,很容易想到,要接收頁面驗證碼的輸入,我們需要擴充套件此類:

package javacommon.shiro;

import org.apache.shiro.authc.UsernamePasswordToken;

public class CaptchaUsernamePasswordToken extends UsernamePasswordToken {
	//驗證碼字串
	private String captcha;

	public CaptchaUsernamePasswordToken(String username, char[] password,
			boolean rememberMe, String host, String captcha) {
		super(username, password, rememberMe, host);
		this.captcha = captcha;
	}

	public String getCaptcha() {
		return captcha;
	}

	public void setCaptcha(String captcha) {
		this.captcha = captcha;
	}
	
}

擴充套件 FormAuthenticationFilter

接下來我們擴充套件 FormAuthenticationFilter 類

package javacommon.shiro;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CaptchaFormAuthenticationFilter extends FormAuthenticationFilter {
	private static final Logger LOG = LoggerFactory.getLogger(CaptchaFormAuthenticationFilter.class);
	
	public CaptchaFormAuthenticationFilter() {
	}
	@Override
	/**
	 * 登入驗證
	 */
	protected boolean executeLogin(ServletRequest request,
			ServletResponse response) throws Exception {
		CaptchaUsernamePasswordToken token = createToken(request, response);
		try {
			/*圖形驗證碼驗證*/
			doCaptchaValidate((HttpServletRequest) request, token);
			Subject subject = getSubject(request, response);
			subject.login(token);//正常驗證
			LOG.info(token.getUsername()+"登入成功");
			return onLoginSuccess(token, subject, request, response);
		}catch (AuthenticationException e) {
			LOG.info(token.getUsername()+"登入失敗--"+e);
			return onLoginFailure(token, e, request, response);
		}
	}

	// 驗證碼校驗
	protected void doCaptchaValidate(HttpServletRequest request,
			CaptchaUsernamePasswordToken token) {
//session中的圖形碼字串
		String captcha = (String) request.getSession().getAttribute(
				com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);
//比對
		if (captcha != null && !captcha.equalsIgnoreCase(token.getCaptcha())) {
			throw new IncorrectCaptchaException("驗證碼錯誤!");
		}
	}

	@Override
	protected CaptchaUsernamePasswordToken createToken(ServletRequest request,
			ServletResponse response) {
		String username = getUsername(request);
		String password = getPassword(request);
		String captcha = getCaptcha(request);
		boolean rememberMe = isRememberMe(request);
		String host = getHost(request);

		return new CaptchaUsernamePasswordToken(username,
				password.toCharArray(), rememberMe, host, captcha);
	}

	public static final String DEFAULT_CAPTCHA_PARAM = "captcha";

	private String captchaParam = DEFAULT_CAPTCHA_PARAM;

	public String getCaptchaParam() {
		return captchaParam;
	}

	public void setCaptchaParam(String captchaParam) {
		this.captchaParam = captchaParam;
	}

	protected String getCaptcha(ServletRequest request) {
		return WebUtils.getCleanParam(request, getCaptchaParam());
	}
	
//儲存異常物件到request
	@Override
	protected void setFailureAttribute(ServletRequest request,
			AuthenticationException ae) {
		request.setAttribute(getFailureKeyAttribute(), ae);
	}
}
前面驗證碼校驗不通過,我們丟擲一個異常 IncorrectCaptchaException,此類繼承 AuthenticationException,之所以需要擴充套件一個新的異常類,為的是在頁面能更精準顯示錯誤提示資訊。
package javacommon.shiro;

import org.apache.shiro.authc.AuthenticationException;

public class IncorrectCaptchaException extends AuthenticationException {

	public IncorrectCaptchaException() {
		super();
	}

	public IncorrectCaptchaException(String message, Throwable cause) {
		super(message, cause);
	}

	public IncorrectCaptchaException(String message) {
		super(message);
	}

	public IncorrectCaptchaException(Throwable cause) {
		super(cause);
	}
}

Filter的配置及使用
<?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:util="http://www.springframework.org/schema/util"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd">

	<!-- Shiro Filter 攔截器相關配置 -->
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<!-- securityManager -->
		<property name="securityManager" ref="securityManager" />
		<!-- 登入路徑 -->
		<property name="loginUrl" value="/login.jsp" />
		<!-- 登入成功後跳轉路徑 -->
		<property name="successUrl" value="/pages/index.jsp" />
		<!-- 授權失敗跳轉路徑 -->
		<property name="unauthorizedUrl" value="/login.jsp" />
		<property name="filters">
			<util:map>
				<entry key="authc" value-ref="myAuthenFilter" />
			</util:map>
		</property>
		<!-- 過濾鏈定義 -->
		<property name="filterChainDefinitions">
			<value>
				/login.jsp = authc
				/pages/* = authc
				/index.jsp* = authc
				/logout.do = logout
				<!-- 訪問這些路徑必須擁有某種許可權 /role/edit/* = perms[role:edit] /role/save = perms[role:edit] 
					/role/list = perms[role:view] -->
			</value>
		</property>
	</bean>

	<!-- 自定義驗證攔截器 -->
	<bean id="myAuthenFilter" class="javacommon.shiro.CaptchaFormAuthenticationFilter" />

	<!-- securityManager -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="myRealm" />
	</bean>

	<!-- <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> 
		<property name="cacheManager" ref="cacheManager" /> </bean> -->

	<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

	<!-- 自定義Realm實現 -->
	<bean id="myRealm" class="javacommon.shiro.CustomRealm">
		<!-- <property name="cacheManager" ref="shiroCacheManager" /> -->
	</bean>
</beans>

登入頁面:

<%@page import="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"%>
<%@page import="javacommon.shiro.IncorrectCaptchaException"%>
<%@page import="org.apache.shiro.authc.AuthenticationException"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<%
	String path = request.getContextPath();
	String basePath = request.getScheme() + "://"
			+ request.getServerName() + ":" + request.getServerPort()
			+ path + "/";
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<base href="<%=basePath%>">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<style type="text/css">
.error {
	color: red;
}
</style>
<script type="text/javascript">
function refreshCaptcha(){
	document.getElementById("img_captcha").src="<%=basePath%>images/kaptcha.jpg?t=" + Math.random();
}
</script>
</head>
<body>

	<%
		Object obj = request
				.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);
		String msg = "";
		if (obj != null) {
			if (obj instanceof IncorrectCaptchaException)
				msg = "驗證碼錯誤!";
			else 
				msg = "賬號或密碼錯誤!";
		}
		
		out.println("<div class='error'>" + msg + "</div>");
	%>

	<form action="login.jsp" method="post">
		<input type="hidden" name="rememberMe" value="true" /> <br />
		<table>

			<tr>
				<td>使用者帳號:</td>
				<td><input type="text" name="username" id="username" value="" /></td>
			</tr>
			<tr>
				<td>登入密碼:</td>
				<td><input type="password" name="password" id="password"
					value="" /></td>
			</tr>
			<tr>
				<td>驗證碼:</td>
				<td><input type="text" name="captcha" /></td>
			</tr>
			<tr>
				<td> </td>
				<td><img alt="驗證碼" src="images/kaptcha.jpg" title="點選更換"
					id="img_captcha" onclick="javascript:refreshCaptcha();">(看不清<a href="javascript:void(0)" onclick="javascript:refreshCaptcha()">換一張</a>)</td>
			</tr>
			<tr>
				<td colspan="2"><input value="登入" type="submit"></td>
			</tr>
		</table>

	</form>
</body>
</html>