结合springMVC,shiro,redis,kaptcha进行验证码登录完整随笔
自己在做项目的时候总结的配置全部流程,用作记录也希望能帮助大家。
一、进行kaptha的依赖配置
1 <!--验证码生成工具--> 2 <dependency> 3 <groupId>com.github.penggle</groupId> 4 <artifactId>kaptcha</artifactId> 5 <version>2.3.2</version> 6 </dependency>
二、web.xml配置,我们只需要简单配置一个 Servlet,页面通过 IMG 标签就可以展现图形验证码。
<servlet> <servlet-name>Kaptcha</servlet-name> <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class> <init-param> <param-name>kaptcha.image.width</param-name> <param-value>200</param-value> </init-param> <init-param> <param-name>kaptcha.image.height</param-name> <param-value>50</param-value> </init-param> <init-param> <param-name>kaptcha.textproducer.char.length</param-name> <param-value>4</param-value> </init-param> <init-param> <param-name>kaptcha.noise.impl</param-name> <param-value>com.google.code.kaptcha.impl.NoNoise</param-value> </init-param> <init-param> <param-name>kaptcha.session.key</param-name> <param-value>rand</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>Kaptcha</servlet-name> <url-pattern>/kaptcha.jpg</url-pattern> </servlet-mapping>
三、扩展 UsernamePasswordTokenShiro 表单认证,页面提交的用户名密码等信息,用 UsernamePasswordToken 类来接收,很容易想到,要接收页面验证码的输入,我们需要扩展此类:
package com.caa.shiro.filter; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; public class CaptchaUsernamePasswordToken extends UsernamePasswordToken { //验证码字符串 private String captcha; public CaptchaUsernamePasswordToken(String username, String password, boolean rememberMe, String captcha) { super(username, password, rememberMe); this.captcha = captcha; } public String getCaptcha() { return captcha; } public void setCaptcha(String captcha) { this.captcha = captcha; } }
四、
扩展 FormAuthenticationFilter
接下来我们扩展 FormAuthenticationFilter 类
这里贴一个全部的扩展方法,不过实际上我使用的是shiro自带的登录验证 后添加的验证码的校验方法(如果也是只需要添加验证码功能那只需要在你的控制层调用doCaptchaValidate方法即可
package com.caa.shiro.filter; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; 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); } } // 验证码校验 public void doCaptchaValidate(HttpServletRequest request, CaptchaUsernamePasswordToken token) { //session中的图形码字符串 HttpSession session = request.getSession(); String captcha = (String)session.getAttribute("rand"); //比对 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, rememberMe, 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,之所以需要扩展一个新的异常类,为的是在页面能更精准显示错误提示信息。
package com.caa.shiro.filter; import org.apache.shiro.authc.AuthenticationException; public class IncorrectCaptchaException extends AuthenticationException { /** * */ private static final long serialVersionUID = 1L; public IncorrectCaptchaException() { super(); } public IncorrectCaptchaException(String message, Throwable cause) { super(message, cause); } public IncorrectCaptchaException(String message) { super(message); } public IncorrectCaptchaException(Throwable cause) { super(cause); } }
七、shiro配置(Filter的配置及使用)这段因为我的配置文件东西较多 在网上贴了一段 核心东西都有
<?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加vue的,不过前天东西不多可以根据自己的页面来修改)
<body class="hold-transition login-page" style="background: black url(statics/images/login-bg.jpg) no-repeat fixed top;"> <div class="login-box" id="rrapp" style="margin-top: 12%" v-cloak> <div class="login-box-body"> <p class="login-box-msg" style="font-size: 25px;font-weight:bold">中拍协后台管理系统</p> <div v-if="error" class="alert alert-danger alert-dismissible"> <h4 style="margin-bottom: 0px;"><i class="fa fa-exclamation-triangle"></i> {{errorMsg}}</h4> </div> <div class="form-group has-feedback"> <input type="text" class="form-control" v-model="username" @keyup.enter="login" placeholder="账号" autofocus> <span class="fa fa-user form-control-feedback"></span> </div> <div class="form-group has-feedback"> <input type="password" class="form-control" v-model="password" @keyup.enter="login" placeholder="密码"> <span class="fa fa-lock form-control-feedback"></span> </div> <div > <input type="text" id="code" name="captcha" v-model="captcha" @keyup.enter="login" class="form-control" placeholder="验证码"> </div> <div > <img id="codeImg" src="kaptcha.jpg" class="img-responsive" style="display:inline" @click="refreshCaptcha">(看不清<a href="javascript:void(0)" @click="refreshCaptcha" style="display:inline">换一张</a>) </div> <div class="checkbox"> <label> <input type="checkbox" name="isRememberMe" v-model="isRememberMe">记住我,下次免登陆 </label> </div> <div class="row"> <div class="col-xs-12"> <button type="button" class="btn btn-block btn-success btn-lg" @click="login">登录</button> </div> </div> </div> </div> <script type="text/javascript"> var vm = new Vue({ el:'#rrapp', data:{ username: '', password: '', captcha: '', error: false, errorMsg: '', isRememberMe:false }, beforeCreate: function(){ if(self != top){ top.location.href = self.location.href; } }, methods: { login: function (event) { var data = "username="+vm.username+"&password="+vm.password+"&isRememberMe="+vm.isRememberMe+"&captcha="+vm.captcha; $.ajax({ type: "POST", url: "login", data: data, dataType: "json", success: function(result){ if(result.code == 0){//登录成功 parent.location.href ='index.html'; }else{ vm.error = true; vm.errorMsg = result.msg; document.getElementById("codeImg").src="kaptcha.jpg?t=" + Math.random(); } } }); }, refreshCaptcha: function () { document.getElementById("codeImg").src="kaptcha.jpg?t=" + Math.random(); } } }); </script> </body>