Shiro在web应用中实现验证码、回显登录失败信息
2017-08-15 10:18
656 查看
转载:Shiro在web应用中实现验证码功能
目录结构:
概述
扩展shiro认证
验证码工具
验证码servlet
配置文件修改
修改登录页面
测试验证
[一]、概述
本文简单讲述在web应用整合shiro后,如何实现登录验证码认证的功能。
[二]、扩展shiro的认证
创建验证码异常类:CaptchaException.java
此过滤器继承FormAuthenticationFilter,FormAuthenticationFilter中在request范围类保存了用户登录失败时的错误信息:
由于在ShiroDbRealm(下文会提到)中登录认证时,如果验证失败,方法都是抛出:AuthenticationException 异常(父类异常),所以在页面获取request返回错误信息时,使用jstl无法区分异常类型,所以在新的FormAuthenticationCaptchaFilter中重写了保存异常信息到request范围的方法,使其返回准确的异常信息。
package com.micmiu.modules.support.shiro;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
/**
*
* @author <a href="http://www.micmiu.com">Michael Sun</a>
*/
public class FormAuthenticationCaptchaFilter extends FormAuthenticationFilter {
public static final String DEFAULT_CAPTCHA_PARAM = "captcha";
private String captchaParam = DEFAULT_CAPTCHA_PARAM;
public String getCaptchaParam() {
return captchaParam;
}
protected String getCaptcha(ServletRequest request) {
return WebUtils.getCleanParam(request, getCaptchaParam());
}
protected AuthenticationToken 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 UsernamePasswordCaptchaToken(username,
password.toCharArray(), rememberMe, host, captcha);
}
/**
* 重写父类FormAuthenticationFilter的方法,返回登录验证异常对应真实异常子类信息
* 目的:用于登录失败时,在登陆页面返显错误信息,提示用户
*/
@Override
protected void setFailureAttribute(ServletRequest request,
AuthenticationException ae) {
String className ="";
if(ae instanceof LockedAccountException){
className=LockedAccountException.class.getName();
}else if(ae instanceof UnknownAccountException){
className=UnknownAccountException.class.getName();
}else if(ae instanceof CaptchaException){
className=CaptchaException.class.getName();
}else if(ae instanceof DisabledAccountException){
className=DisabledAccountException.class.getName();
}else if(ae instanceof IncorrectCredentialsException){
className=IncorrectCredentialsException.class.getName();
}else {
className = ae.getClass().getName();
}
request.setAttribute(getFailureKeyAttribute(), className);
/* String className = ae.getClass().getName();
request.setAttribute(getFailureKeyAttribute(), className);*/
}
}
修改shiro认证逻辑:ShiroDbRealm.java
[三]、验证码工具类
CaptchaUtil.java:此工具类和servlet可以使用自己的定义
[四]、创建验证码的servlet
CaptchaServlet.java
[五]、修改配置文件
在 web.xml 中增加配置:
修改 applicationContext-shiro.xml 中的配置如下:
[六]、修改登录页面
login.jsp
或者也可以这样获取异常信息,两种方式本质一样:
[七]、验证测试
启动项目后会看到如下页面:
目录结构:
概述
扩展shiro认证
验证码工具
验证码servlet
配置文件修改
修改登录页面
测试验证
[一]、概述
本文简单讲述在web应用整合shiro后,如何实现登录验证码认证的功能。
[二]、扩展shiro的认证
创建验证码异常类:CaptchaException.java
package com.micmiu.modules.support.shiro; import org.apache.shiro.authc.AuthenticationException; /** * * @author <a href="http://www.micmiu.com">Michael Sun</a> */ public class CaptchaException extends AuthenticationException { private static final long serialVersionUID = 1L; public CaptchaException() { super(); } public CaptchaException(String message, Throwable cause) { super(message, cause); } public CaptchaException(String message) { super(message); } public CaptchaException(Throwable cause) { super(cause); } }扩展默认的用户认证的bean为:UsernamePasswordCaptchaToken.java
package com.micmiu.modules.support.shiro; import org.apache.shiro.authc.UsernamePasswordToken; /** * extends UsernamePasswordToken for captcha * * @author <a href="http://www.micmiu.com">Michael Sun</a> */ public class UsernamePasswordCaptchaToken extends UsernamePasswordToken { private static final long serialVersionUID = 1L; private String captcha; public String getCaptcha() { return captcha; } public void setCaptcha(String captcha) { this.captcha = captcha; } public UsernamePasswordCaptchaToken() { super(); } public UsernamePasswordCaptchaToken(String username, char[] password, boolean rememberMe, String host, String captcha) { super(username, password, rememberMe, host); this.captcha = captcha; } }扩展原始默认的过滤为:FormAuthenticationCaptchaFilter.java
此过滤器继承FormAuthenticationFilter,FormAuthenticationFilter中在request范围类保存了用户登录失败时的错误信息:
由于在ShiroDbRealm(下文会提到)中登录认证时,如果验证失败,方法都是抛出:AuthenticationException 异常(父类异常),所以在页面获取request返回错误信息时,使用jstl无法区分异常类型,所以在新的FormAuthenticationCaptchaFilter中重写了保存异常信息到request范围的方法,使其返回准确的异常信息。
package com.micmiu.modules.support.shiro;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
/**
*
* @author <a href="http://www.micmiu.com">Michael Sun</a>
*/
public class FormAuthenticationCaptchaFilter extends FormAuthenticationFilter {
public static final String DEFAULT_CAPTCHA_PARAM = "captcha";
private String captchaParam = DEFAULT_CAPTCHA_PARAM;
public String getCaptchaParam() {
return captchaParam;
}
protected String getCaptcha(ServletRequest request) {
return WebUtils.getCleanParam(request, getCaptchaParam());
}
protected AuthenticationToken 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 UsernamePasswordCaptchaToken(username,
password.toCharArray(), rememberMe, host, captcha);
}
/**
* 重写父类FormAuthenticationFilter的方法,返回登录验证异常对应真实异常子类信息
* 目的:用于登录失败时,在登陆页面返显错误信息,提示用户
*/
@Override
protected void setFailureAttribute(ServletRequest request,
AuthenticationException ae) {
String className ="";
if(ae instanceof LockedAccountException){
className=LockedAccountException.class.getName();
}else if(ae instanceof UnknownAccountException){
className=UnknownAccountException.class.getName();
}else if(ae instanceof CaptchaException){
className=CaptchaException.class.getName();
}else if(ae instanceof DisabledAccountException){
className=DisabledAccountException.class.getName();
}else if(ae instanceof IncorrectCredentialsException){
className=IncorrectCredentialsException.class.getName();
}else {
className = ae.getClass().getName();
}
request.setAttribute(getFailureKeyAttribute(), className);
/* String className = ae.getClass().getName();
request.setAttribute(getFailureKeyAttribute(), className);*/
}
}
修改shiro认证逻辑:ShiroDbRealm.java
package com.micmiu.framework.web.v1.system.service; import java.io.Serializable; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AccountException; 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.UnknownAccountException; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.cache.Cache; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.SimplePrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; import com.micmiu.framework.web.v1.system.entity.Role; import com.micmiu.framework.web.v1.system.entity.User; import com.micmiu.modules.captcha.CaptchaServlet; import com.micmiu.modules.support.shiro.CaptchaException; import com.micmiu.modules.support.shiro.UsernamePasswordCaptchaToken; /** * 演示用户和权限的认证,使用默认 的SimpleCredentialsMatcher * * @author <a href="http://www.micmiu.com">Michael Sun</a> */ public class ShiroDbRealm extends AuthorizingRealm { private UserService userService; /** * 认证回调函数, 登录时调用. */ @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken authcToken) throws AuthenticationException { UsernamePasswordCaptchaToken token = (UsernamePasswordCaptchaToken) authcToken; String username = token.getUsername(); if (username == null) { throw new AccountException( "Null usernames are not allowed by this realm."); } // 增加判断验证码逻辑 String captcha = token.getCaptcha(); String exitCode = (String) SecurityUtils.getSubject().getSession() .getAttribute(CaptchaServlet.KEY_CAPTCHA); if (null == captcha || !captcha.equalsIgnoreCase(exitCode)) { throw new CaptchaException("验证码错误"); } User user = userService.getUserByLoginName(username); if (null == user) { throw new UnknownAccountException("No account found for user [" + username + "]"); } return new SimpleAuthenticationInfo(new ShiroUser(user.getLoginName(), user.getName()), user.getPassword(), getName()); } /** * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用. */ @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { ShiroUser shiroUser = (ShiroUser) principals.fromRealm(getName()) .iterator().next(); User user = userService.getUserByLoginName(shiroUser.getLoginName()); if (user != null) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); for (Role role : user.getRoleList()) { // 基于Permission的权限信息 info.addStringPermissions(role.getAuthList()); } return info; } else { return null; } } /** * 更新用户授权信息缓存. */ public void clearCachedAuthorizationInfo(String principal) { SimplePrincipalCollection principals = new SimplePrincipalCollection( principal, getName()); clearCachedAuthorizationInfo(principals); } /** * 清除所有用户授权信息缓存. */ public void clearAllCachedAuthorizationInfo() { Cache<Object, AuthorizationInfo> cache = getAuthorizationCache(); if (cache != null) { for (Object key : cache.keys()) { cache.remove(key); } } } @Autowired public void setUserService(UserService userService) { this.userService = userService; } /** * 自定义Authentication对象,使得Subject除了携带用户的登录名外还可以携带更多信息. */ public static class ShiroUser implements Serializable { private static final long serialVersionUID = -1748602382963711884L; private String loginName; private String name; public ShiroUser(String loginName, String name) { this.loginName = loginName; this.name = name; } public String getLoginName() { return loginName; } /** * 本函数输出将作为默认的<shiro:principal/>输出. */ @Override public String toString() { return loginName; } public String getName() { return name; } } }
[三]、验证码工具类
CaptchaUtil.java:此工具类和servlet可以使用自己的定义
package com.micmiu.modules.captcha; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileOutputStream; import java.util.Random; import javax.imageio.ImageIO; /** * 验证码工具类 * * @author <a href="http://www.micmiu.com">Michael Sun</a> */ public class CaptchaUtil { // 随机产生的字符串 private static final String RANDOM_STRS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final String FONT_NAME = "Fixedsys"; private static final int FONT_SIZE = 18; private Random random = new Random(); private int width = 80;// 图片宽 private int height = 25;// 图片高 private int lineNum = 50;// 干扰线数量 private int strNum = 4;// 随机产生字符数量 /** * 生成随机图片 */ public BufferedImage genRandomCodeImage(StringBuffer randomCode) { // BufferedImage类是具有缓冲区的Image类 BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR); // 获取Graphics对象,便于对图像进行各种绘制操作 Graphics g = image.getGraphics(); // 设置背景色 g.setColor(getRandColor(200, 250)); g.fillRect(0, 0, width, height); // 设置干扰线的颜色 g.setColor(getRandColor(110, 120)); // 绘制干扰线 for (int i = 0; i <= lineNum; i++) { drowLine(g); } // 绘制随机字符 g.setFont(new Font(FONT_NAME, Font.ROMAN_BASELINE, FONT_SIZE)); for (int i = 1; i <= strNum; i++) { randomCode.append(drowString(g, i)); } g.dispose(); return image; } /** * 给定范围获得随机颜色 */ private Color getRandColor(int fc, int bc) { if (fc > 255) fc = 255; if (bc > 255) bc = 255; int r = fc + random.nextInt(bc - fc); int g = fc + random.nextInt(bc - fc); int b = fc + random.nextInt(bc - fc); return new Color(r, g, b); } /** * 绘制字符串 */ private String drowString(Graphics g, int i) { g.setColor(new Color(random.nextInt(101), random.nextInt(111), random .nextInt(121))); String rand = String.valueOf(getRandomString(random.nextInt(RANDOM_STRS .length()))); g.translate(random.nextInt(3), random.nextInt(3)); g.drawString(rand, 13 * i, 16); return rand; } /** * 绘制干扰线 */ private void drowLine(Graphics g) { int x = random.nextInt(width); int y = random.nextInt(height); int x0 = random.nextInt(16); int y0 = random.nextInt(16); g.drawLine(x, y, x + x0, y + y0); } /** * 获取随机的字符 */ private String getRandomString(int num) { return String.valueOf(RANDOM_STRS.charAt(num)); } public static void main(String[] args) { CaptchaUtil tool = new CaptchaUtil(); StringBuffer code = new StringBuffer(); BufferedImage image = tool.genRandomCodeImage(code); System.out.println(">>> random code =: " + code); try { // 将内存中的图片通过流动形式输出到客户端 ImageIO.write(image, "JPEG", new FileOutputStream(new File( "random-code.jpg"))); } catch (Exception e) { e.printStackTrace(); } } }
[四]、创建验证码的servlet
CaptchaServlet.java
package com.micmiu.modules.captcha; import java.awt.image.BufferedImage; import java.io.IOException; import javax.imageio.ImageIO; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** * * @author <a href="http://www.micmiu.com">Michael Sun</a> */ public class CaptchaServlet extends HttpServlet { private static final long serialVersionUID = -124247581620199710L; public static final String KEY_CAPTCHA = "SE_KEY_MM_CODE"; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 设置相应类型,告诉浏览器输出的内容为图片 resp.setContentType("image/jpeg"); // 不缓存此内容 resp.setHeader("Pragma", "No-cache"); resp.setHeader("Cache-Control", "no-cache"); resp.setDateHeader("Expire", 0); try { HttpSession session = req.getSession(); CaptchaUtil tool = new CaptchaUtil(); StringBuffer code = new StringBuffer(); BufferedImage image = tool.genRandomCodeImage(code); session.removeAttribute(KEY_CAPTCHA); session.setAttribute(KEY_CAPTCHA, code.toString()); // 将内存中的图片通过流动形式输出到客户端 ImageIO.write(image, "JPEG", resp.getOutputStream()); } catch (Exception e) { e.printStackTrace(); } } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
[五]、修改配置文件
在 web.xml 中增加配置:
<!-- captcha servlet config--> <servlet> <servlet-name>CaptchaServlet</servlet-name> <servlet-class>com.micmiu.modules.captcha.CaptchaServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>CaptchaServlet</servlet-name> <url-pattern>/servlet/captchaCode</url-pattern> </servlet-mapping>
修改 applicationContext-shiro.xml 中的配置如下:
<!-- Shiro Filter --> <bean id="myCaptchaFilter" class="com.micmiu.modules.support.shiro.FormAuthenticationCaptchaFilter"/> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/login.do" /> <property name="successUrl" value="/index.do" /> <property name="filters"> <map> <entry key="authc" value-ref="myCaptchaFilter"/> </map> </property> <property name="filterChainDefinitions"> <value> /login.do = authc /logout.do = logout /servlet/* = anon /images/** = anon /js/** = anon /css/** = anon /** = user </value> </property> </bean>
[六]、修改登录页面
login.jsp
<%@ page contentType="text/html;charset=UTF-8"%> <%@ page import="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"%> <%@ include file="/include/taglibs.jsp"%> <html> <head> <title>登录页</title> </head> <body style="width: 99%"> <div> <c:choose> <c:when test="${shiroLoginFailure eq 'com.hx.web.excep.CaptchaException'}"> <div class="error-msg prepend-top">验证码错误,请重试.</div> </c:when> <c:when test="${shiroLoginFailure eq 'org.apache.shiro.authc.UnknownAccountException'}"> <div class="error-msg prepend-top">该用户不存在.</div> </c:when> <c:when test="${shiroLoginFailure eq 'org.apache.shiro.authc.IncorrectCredentialsException'}"> <div class="error-msg prepend-top">用户或密码错误.</div> </c:when> <c:when test="${shiroLoginFailure ne null}"> <div class="error-msg prepend-top">登录认证错误,请重试.</div> </c:when> </c:choose> <form:form id="loginForm" action="${ctx}/login.do" method="post"> <fieldset class="prepend-top"> <legend>系统登录</legend> <div class="field"> <label for="username" class="field">名称:</label> <input type="text" id="username" name="username" size="25" value="${username}" class="required" /> </div> <div class="field"> <label for="password" class="field">密码:</label> <input type="password" id="password" name="password" size="25" class="required" /> </div> <div class="field"> <label for="captcha" class="field">验证码:</label> <input type="text" id="captcha" name="captcha" size="4" maxlength="4" class="required" /> </div> <div class="field"> <label for="codeImg" class="field"></label> <img title="点击更换" id="img_captcha" onclick="javascript:refreshCaptcha();" src="servlet/captchaCode">(看不清<a href="javascript:void(0)" onclick="javascript:refreshCaptcha()">换一张</a>) </div> </fieldset> <div> <input type="checkbox" id="rememberMe" name="rememberMe" /> <label for="rememberMe">记住我</label> <span style="padding-left: 10px;"><input id="submit" class="button" type="submit" value="登录" /></span> </div> <div> (管理员<b>admin/admin</b>, 普通用户<b>user/user</b>) </div> </form:form> </div> </body> <script type="text/javascript"> $(document).ready(function() { $("#loginForm").validate(); }); var _captcha_id = "#img_captcha"; function refreshCaptcha() { $(_captcha_id).attr("src","servlet/captchaCode?t=" + Math.random()); } </script> </html>
或者也可以这样获取异常信息,两种方式本质一样:
<div > <% String error = (String) request .getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME); %> <c:set var="exp_type" value="<%=error%>" /> <c:set var="tips" value=""></c:set> <c:if test="${fn:contains(exp_type,'CaptchaException')}"> <c:set var="tips" value="验证码错误,请确认后重新输入!"></c:set> </c:if> <c:if test="${fn:contains(exp_type,'UnknownAccountException')}"> <c:set var="tips" value="该账号不存在,请确认后重新输入!"></c:set> </c:if> <c:if test="${fn:contains(exp_type,'DisabledAccountException')}"> <c:set var="tips" value="该账号已被管理员限制,不允许登陆!"></c:set> </c:if> <c:if test="${fn:contains(exp_type,'IncorrectCredentialsException')}"> <c:set var="tips" value="账号或密码错误,请确认后重新输入!"></c:set> </c:if> <c:if test="${fn:contains(exp_type,'LockedAccountException')}"> <c:set var="tips" value="该账号已被锁定,请联系管理员!"></c:set> </c:if> </div>
[七]、验证测试
启动项目后会看到如下页面:
相关文章推荐
- Shiro在web应用中实现验证码功能
- [置顶] 实现网站应用钉钉扫码登录,及查询用户详细信息
- shiro实现APP、web统一登录认证和权限管理
- springboot整合shiro登录失败次数限制功能的实现代码
- javaweb登录验证码的实现
- web登录:随机验证码的设计和实现
- cookie在web应用中实现上次登录时间以及上次访问时间 小案例理解cookie
- 使用触发器实现记录oracle用户登录失败信息到alert.log日志文件
- shiro实现APP、web统一登录认证和权限管理
- 实现应用WebView组件加载使用HTML代码添加的帮助信息
- 第三百三十五节,web爬虫讲解2—Scrapy框架爬虫—豆瓣登录与利用打码接口实现自动识别验证码
- 用jsp实现一个简单的购物车web应用系统。实现的添加购物商品,删除购物商品并且显示购物车信息。
- SSM+Shiro系统登录验证码的实现
- Web应用多账号系统设计及微信扫码登录实现
- JavaWeb应用如何实现保持登录状态
- shiro实现APP、web统一登录认证和权限管理
- 【JavaWeb开发】初步实现网站应用钉钉扫码登录
- Web用户登录---验证码的设计与实现
- 使用shiro的的表单过滤器重写shiro默认的认证规则来实现先验证验证码再验证登录所遇到的问题
- shiro实现APP、web统一登录认证和权限管理