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

目录结构:

概述

扩展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范围类保存了用户登录失败时的错误信息:

0818b9ca8b590ca3270a3433284dd417.png

由于在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')}">

[七]、验证测试

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

0818b9ca8b590ca3270a3433284dd417.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值