Shiro(3)实现验证码认证
2016-03-17 18:07
381 查看
验证码是有效防止暴力破解的一种手段,常用做法是在服务端产生一串随机字符串与当前用户会话关联(我们通常说的放入 Session),然后向终端用户展现一张经过“扰乱”的图片,只有当用户输入的内容与服务端产生的内容相同时才允许进行下一步操作
产生验证码
作为演示,我们选择开源的验证码组件 kaptcha。这样,我们只需要简单配置一个 Servlet,页面通过 IMG 标签就可以展现图形验证码。
[html] view
plain copy
<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 类来接收,很容易想到,要接收页面验证码的输入,我们需要扩展此类:
[java] view
plain copy
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 类
[java] view
plain copy
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,之所以需要扩展一个新的异常类,为的是在页面能更精准显示错误提示信息。
[java] view
plain copy
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的配置及使用
[html] view
plain copy
<?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>
登录页面:
[html] view
plain copy
<%@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>
产生验证码
作为演示,我们选择开源的验证码组件 kaptcha。这样,我们只需要简单配置一个 Servlet,页面通过 IMG 标签就可以展现图形验证码。[html] view
plain copy
<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 类来接收,很容易想到,要接收页面验证码的输入,我们需要扩展此类:
[java] view
plain copy
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 类
[java] view
plain copy
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,之所以需要扩展一个新的异常类,为的是在页面能更精准显示错误提示信息。
[java] view
plain copy
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的配置及使用
[html] view
plain copy
<?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>
登录页面:
[html] view
plain copy
<%@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>
相关文章推荐
- RESTful API 设计指南
- PHP实现生成唯一编号(36进制的不重复编号)
- 监控Storm进程 NimbusMonitor.sh
- 3.15 晚会—「饿了么」之殇
- Go语言核心之美 2.5-字符串
- opencv version_string.inc error: stray ‘\’ in program
- 迷宫算法
- java的一个类!
- 自动寻路Navmesh之入门
- #001 GIT创建分支
- ar命令详解
- Spark Streaming使用Kafka保证数据零丢失
- 压力测试的轻量级具体做法
- Rgeliomp程序IVF的生成
- 排序规则不一致引起代理错误
- ios 静态库冲突的解决办法
- snmp4j获取交换机信息
- 关于Android热修复技术(AndFix—阿里)
- 热敏打印机打印图片
- 【codecs.BOM】使用Python程序输出csv格式数据