思路
shiro使用FormAuthenticationFilter进行表单认证,验证校验的功能应该加在FormAuthenticationFilter中,在认证之前进行验证码校验。
需要写FormAuthenticationFilter的子类,继承FormAuthenticationFilter,改写它的认证方法,在认证之前进行验证码校验。
自定义FormAuthenticationFilter
package com.example.config.shiro; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; public class CustomFormAuthenticationFilter extends FormAuthenticationFilter{ @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { // 在这里进行验证码的校验 HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpSession session = httpServletRequest.getSession(); // 取出验证码 String validateCode = (String) session.getAttribute("validateCode"); // 取出页面的验证码 // 输入的验证和session中的验证进行对比 String randomcode = httpServletRequest.getParameter("randomcode"); if (randomcode != null && validateCode != null && !randomcode.equals(validateCode)) { // 如果校验失败,将验证码错误失败信息,通过shiroLoginFailure设置到request中 httpServletRequest.setAttribute("shiroLoginFailure", "kaptchaValidateFailed");//自定义登录异常 // 拒绝访问,不再校验账号和密码 return true; } return super.onAccessDenied(request, response); } }
在ShiroConfiguration.java中的shiroFilter方法注入自定义FormAuthenticationFilter
@Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { System.out.println("ShiroConfiguration.shiroFilter()"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();//获取filters filters.put("authc", new CustomFormAuthenticationFilter());//将自定义 的FormAuthenticationFilter注入shiroFilter中 // 必须设置SecuritManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 拦截器 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 配置退出过滤器,其中的具体代码Shiro已经替我们实现了 filterChainDefinitionMap.put("/logout", "logout"); //验证码可以匿名访问 filterChainDefinitionMap.put("/validatecodeServlet", "anon"); //配置记住我或认证通过可以访问的地址 filterChainDefinitionMap.put("/index", "user"); filterChainDefinitionMap.put("/", "user"); // <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了; // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> filterChainDefinitionMap.put("/**", "authc"); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl("/login"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/index"); // 未授权界面; shiroFilterFactoryBean.setUnauthorizedUrl("/403"); shiroFilterFactoryBean .setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; }
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();//获取filters
filters.put("authc", new CustomFormAuthenticationFilter());//将自定义 的FormAuthenticationFilter注入shiroFilter中
登录方法加入自定义的异常kaptchaValidateFailed
@RequestMapping(value = "/login", method = RequestMethod.POST) public String login(HttpServletRequest request, Map<String, Object> map) { System.out.println("HomeController.login"); // 登录失败从request中获取shiro处理的异常信息 // shiroLoginFailure:就是shiro异常类的全类名 String exception = (String) request.getAttribute("shiroLoginFailure"); String msg = ""; if (exception != null) { if (UnknownAccountException.class.getName().equals(exception)) { System.out.println("UnknownAccountException -->帐号不存在:"); msg = "UnknownAccountException -->帐号不存在:"; } else if (IncorrectCredentialsException.class.getName().equals(exception)) { System.out.println("IncorrectCredentialsException -- > 密码不正确:"); msg = "IncorrectCredentialsException -- > 密码不正确:"; } else if ("kaptchaValidateFailed".equals(exception)) { System.out.println("kaptchaValidateFailed -- > 验证码错误"); msg = "kaptchaValidateFailed -- > 验证码错误"; } else { msg = "else >> " + exception; System.out.println("else -- >" + exception); } } map.put("msg", msg); // 此方法不处理登录成功,由shiro进行处理. return "/login"; }
login.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <meta charset="UTF-8" /> <title>Insert title here</title> </head> <body> 错误信息:<h4 th:text="${msg}"></h4> <form action="" method="post"> <p>账号:<input type="text" name="username" value="admin"/></p> <p>密码:<input type="text" name="password" value="123456"/></p> <p>验证码:<input type="text" name="randomcode"/> <img th:src="@{/validatecodeServlet}" height="20px" width="60px" οnclick="random(this)"/></p> <P><input type="checkbox" name="rememberMe" />记住我</P> <p><input type="submit" value="登录"/></p> </form> <script th:inline="javascript"> function random(tmp){ tmp.src="/validatecodeServlet?rnd="+Math.random(); } </script> </body> </html>
验证码Servlet
package com.example.servlet; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.OutputStream; import java.util.Random; import javax.imageio.ImageIO; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; @WebServlet(urlPatterns="/validatecodeServlet") public class ValidatecodeServlet extends HttpServlet{ /** * */ private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println(">>>>>>>>>>doGet()<<<<<<<<<<<"); doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println(">>>>>>>>>>doPost()<<<<<<<<<<<"); int width = 60; int height = 32; //create the image BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics g = image.getGraphics(); // set the background color g.setColor(new Color(0xDCDCDC)); g.fillRect(0, 0, width, height); // draw the border g.setColor(Color.black); g.drawRect(0, 0, width - 1, height - 1); // create a random instance to generate the codes Random rdm = new Random(); String hash1 = Integer.toHexString(rdm.nextInt()); System.out.print(hash1); // make some confusion for (int i = 0; i < 50; i++) { int x = rdm.nextInt(width); int y = rdm.nextInt(height); g.drawOval(x, y, 0, 0); } // generate a random code String capstr = hash1.substring(0, 4); HttpSession session = req.getSession(true); //将生成的验证码存入session session.setAttribute("validateCode", capstr); g.setColor(new Color(0, 100, 0)); g.setFont(new Font("Candara", Font.BOLD, 24)); g.drawString(capstr, 8, 24); g.dispose(); //输出图片 resp.setContentType("image/jpeg"); OutputStream strm = resp.getOutputStream(); ImageIO.write(image, "jpeg", strm); strm.close(); } }
Application.java
package com.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletComponentScan; @ServletComponentScan @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }