目录结构:

概述

扩展shiro认证

验证码工具

验证码servlet

配置文件修改

修改登录页面

测试验证

[一]、概述

本文简单讲述在web应用整合shiro后,如何实现登录验证码认证的功能。

[二]、扩展shiro的认证

创建验证码异常类:CaptchaException.java

package com.micmiu.modules.support.shiro;

import org.apache.shiro.authc.AuthenticationException;

/**

*

* @author Michael Sun

*/

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 Michael Sun

*/

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 Michael Sun

*/

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 Michael Sun

*/

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 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;

}

/**

* 本函数输出将作为默认的输出.

*/

@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 Michael Sun

*/

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 Michael Sun

*/

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 中增加配置:

CaptchaServlet

com.micmiu.modules.captcha.CaptchaServlet

CaptchaServlet

/servlet/captchaCode

修改applicationContext-shiro.xml 中的配置如下:

/login.do = authc

/logout.do = logout

/servlet/* = anon

/images/** = anon

/js/** = anon

/css/** = anon

/** = user

[六]、修改登录页面

login.jsp

登录页

test="${shiroLoginFailure eq 'com.hx.web.excep.CaptchaException'}">

验证码错误,请重试.

test="${shiroLoginFailure eq 'org.apache.shiro.authc.UnknownAccountException'}">

该用户不存在.

test="${shiroLoginFailure eq 'org.apache.shiro.authc.IncorrectCredentialsException'}">

用户或密码错误.
登录认证错误,请重试.

系统登录

名称:

id="username" name="username" size="25" value="${username}"

class="required" />

密码:

type="password" id="password" name="password" size="25"

class="required" />

验证码:

id="captcha" name="captcha" size="4" maxlength="4"

class="required" />

οnclick="javascript:refreshCaptcha();"

src="servlet/captchaCode">(看不清换一张)

for="rememberMe">记住我

id="submit" class="button" type="submit" value="登录" />

(管理员admin/admin, 普通用户user/user)

$(document).ready(function() {

$("#loginForm").validate();

});

var _captcha_id = "#img_captcha";

function refreshCaptcha() {

$(_captcha_id).attr("src","servlet/captchaCode?t=" + Math.random());

}

或者也可以这样获取异常信息,两种方式本质一样:

String error = (String) request

.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);

%>

test="${fn:contains(exp_type,'IncorrectCredentialsException')}">

test="${fn:contains(exp_type,'LockedAccountException')}">

[七]、验证测试

启动项目后会看到如下页面:

shiro访问html没有验证码,Shiro在web应用中实现验证码、回显登录失败信息相关推荐

  1. Web 攻防之业务安全:登录失败信息测试.

    Web 攻防之业务安全:登录失败信息测试 业务安全是指保护业务系统免受安全威胁的措施或手段.广义的业务安全应包括业务运行的软硬件平台(操作系统.数据库,中间件等).业务系统自身(软件或设备).业务所提 ...

  2. web网页中的验证码实现

    前言 技巧点总结非常重要,等到在自己做项目中突然想用到了,可以来看看自己的博客,算是积累自己代码库的一种方式.下面小编对于牛腩新闻系统中的"验证码功能"方法做一下总结. 一.代码段 ...

  3. java访问错误404_如何解决 Java web 项目中的 404 错误

    在使用 Tomcat 进行 Java Web 开发的时候,经常会遇到以下 HTTP 404 错误: 错误代码为 HTTP 404(未找到),描述信息是: "The origin server ...

  4. java web短信验证码_在Web项目中手机短信验证码实现的全过程记录

    前言 最近在做远程智能水表管理系统这个过程有一个功能是在注册页面可以使用手机注册,找了许久才大致了解了手机验证码实现流程,今天在此和大家分享一下.下面话不多说了,来一起看看详细的介绍吧. 短信验证码实 ...

  5. 前端接收java验证码_在Web项目中手机短信验证码实现的全过程记录

    前言 最近在做远程智能水表管理系统这个过程有一个功能是在注册页面可以使用手机注册,找了许久才大致了解了手机验证码实现流程,今天在此和大家分享一下.下面话不多说了,来一起看看详细的介绍吧. 短信验证码实 ...

  6. 网页里显示访问的那台服务器,在web服务器中把网页放在那里,才能被访问

    上传的定义 上传就是将信息从个人计算机(本地计算机)传递到中央计算机(远程计算机)系统上,让网络上的人都能看到.将制作好的网页.文字.图片等发布到互联网上去,以便让其他人浏览.欣赏.这一过程称为上传. ...

  7. Java Web之SpringMVC 进行数据回显

    基本介绍 数据回显:模型数据导向视图(模型数据 ---> Controller ---> 视图) 说明:SpringMVC在调用方法前会创建一个隐含的模型对象,作为模型数据的存储容器(隐含 ...

  8. 彻底解决web开发中遇到的路径问题(上)

    注:本文部分引用了网络上的文章,以及动力节点老师的讲解内容,感谢老师,嘻嘻. 为了举例方便,我新建了pathTest项目: 关于tomcat的配置,eclipse访问项目的路径一般是localhost ...

  9. 登录工程:传统 Web 应用中的身份验证技术

    标题中 "传统 Web 应用" 这一说法也并没有什么官方定义,只是为了与"现代化 Web 应用"形成比较而自拟的一个概念.所谓现代化 Web 应用指的是那些基于 ...

最新文章

  1. 主机信息收集工具DMitry
  2. JQUERY GET
  3. 我的2012年度总结
  4. PyCharm的高效使用技巧
  5. 主题模型 LDA,Dirichlet分布 和朴素贝叶斯算法
  6. 关于下一代IM服务器的一点想法
  7. springboot项目和云服务器,以及域名的申请和使用(后续持续更新)
  8. linux集群100道单选题面试试题系统工程师中级试题(4)
  9. mui ajax请求 登录
  10. 模电、数电、电力电子、开关电源基础知识总结
  11. Baby-Step-Giant-Step算法
  12. Creo二次开发 Creo4.0 Qt5 动态部署发布
  13. vuerouter4报错:Discarded invalid param(s) “name“, “age“ when navigating.
  14. 乱码html文档怎么恢复,乱码word文档怎么恢复
  15. vue中实现点击复制文本内容之clipboard
  16. 查询速度至少为160MHz的PC的制造商
  17. 聚合架构-晓岩企业架构系列讲座整理(20-29)
  18. Leetcode刷题33. 搜索旋转排序数组
  19. linux桌面版 磁盘管理工具,Paragon ExtFS for Windows
  20. 最新联发科MTK射频芯片资料集锦

热门文章

  1. easypoi 导入失败返回错误文件_从Excel批量导入数据说到ForkJoin的原理
  2. cpython vm_【协程原理】 - cPython的VM真变态
  3. Java 并发编程之 LockSupport
  4. matplotlib 添加偏移量
  5. Fiddler 详尽教程与抓取移动端数据包
  6. oracle 全局搜索字符串,oracle操作字符串:拼接、替换、截取、查找 _ 学编程-免费技术教程分享平台...
  7. HiveQL:文件格式和压缩方法
  8. 怎么样使element ui 的table某列变色
  9. quartz集群报错but has failed to stop it. This is very likely to create a memory leak.
  10. Initializing Java Tooling 30% 停住不动了