目录结构:
- 概述创建验证码异常类:CaptchaException.java
- 扩展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
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);
}
}
修改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;
}
}
}
修改web.xml文件,加入
<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>
修改 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>
页面中加入
<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"
οnclick="javascript:refreshCaptcha();"
src="servlet/captchaCode">(看不清<a href="javascript:void(0)" οnclick="javascript:refreshCaptcha()">换一张</a>)
</div>
<script type="text/javascript">$(document).ready(function() {
var _captcha_id = "#img_captcha";
function refreshCaptcha() {
$(_captcha_id).attr("src","servlet/captchaCode?t=" + Math.random());
}
</script>