1.pom.xml
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
2.login登录页面
<!DOCTYPE html>
<html lang="zh_CN" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Login</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link type="text/css" rel="stylesheet" th:href="@{/AdminLTE/dist/css/adminlte.min.css}">
<style type="text/css">
html, body {
width: 100%;
height: 100%;
}
.input-group > .form-control {
box-shadow: none;
background-color: rgba(0, 0, 0, 0.2);
color: #1fc8e3;
}
.input-group-prepend > .input-group-text {
background-color: rgba(0, 0, 0, 0.2);
color: #1fc8e3;;
}
.card {
position: relative;
/*left: 487px;*/
right: -124%;
top: 45px;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50);
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=50);
background-color: rgba(0, 0, 0, 0.2);
}
</style>
</head>
<body class="hold-transition login-page">
<!--style="background-image:url(/AdminLTE/dist/img/login.jpg); background-size:contain;"-->
<img src="/AdminLTE/dist/img/login.jpg" width="100%" height="100%" style="z-index:-100;position:absolute;left:0;top:0">
<div class="login-box">
<div class="login-logo">
<a href="#"><b>权限管理系统</b></a>
</div>
<!--login-logo-->
<div class="card">
<div class="card-body login-card-body">
<form action="" method="post" onsubmit="return check()">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">账 号:</span>
</div>
<input type="text" class="form-control" id="username" name="username" value="admin" placeholder="账号"
autocomplete="off">
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">密 码:</span>
</div>
<input type="password" class="form-control" id="password" name="password" value="123456"
placeholder="密码">
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text">验证码:</span>
</div>
<input type="text" class="form-control" id="randomcode" name="randomcode" autocomplete="off">
<img th:src="@{/validatecodeServlet}" onclick="random(this)"/>
</div>
<div class="row">
<div class="col-8">
<div class="checkbox icheck">
<label style="font-size: 14px; font-weight: normal;color: #1fc8e3">
<input type="checkbox"> 记住我
</label>
</div>
</div>
<div class="col-4">
<button type="submit" class="btn btn-primary btn-block btn-flat">登 录</button>
</div>
</div>
</form>
</div>
</div>
</div>
<!-- jQuery -->
<script type="text/javascript" th:src="@{/AdminLTE/plugins/jquery/jquery.js}"></script>
<!-- Bootstrap 4-->
<script type="text/javascript" th:src="@{/AdminLTE/plugins/bootstrap/js/bootstrap.bundle.min.js}"></script>
<script type="text/javascript" th:src="@{/AdminLTE/plugins/tips/jquery.tips.js}"></script>
<script type="text/javascript" th:src="@{/js/login.js}"></script>
</body>
</html>
3.login.js 验证是否为空
function random(tmp) {
var pathName = window.document.location.pathname;
tmp.src = pathName.substring(0, pathName.substr(1).indexOf('/') + 1) + "/validatecodeServlet?rnd=" + Math.random();
}
function check() {
$("#username").val($("#username").val().trim());
$("#password").val($("#password").val().trim());
$("#randomcode").val($("#randomcode").val().trim());
if ($("#username").val() == "") {
$("#username").tips({
side: 2,
msg: '用户名不能为空',
bg: '#AE81FF',
time: 3
});
$("#username").focus();
return false;
} else {
$("#username").val(jQuery.trim($('#username').val()));
}
if ($("#password").val() == "") {
$("#password").tips({
side: 2,
msg: '密码不能为空',
bg: '#AE81FF',
time: 3
});
$("#password").focus();
return false;
}
if ($("#randomcode").val() == "") {
$("#randomcode").tips({
side: 1,
msg: '验证码不能为空',
bg: '#AE81FF',
time: 3
});
$("#randomcode").focus();
return false;
}
return true;
}
4.servlet
package com.wxbd.wb_mine.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
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 java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
@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;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
g.setColor(new Color(0xDCDBD2));
g.fillRect(0, 0, width, height);
g.setColor(Color.black);
g.drawRect(0, 0, width - 1, height - 1);
Random rdm = new Random();
String hash1 = Integer.toHexString(rdm.nextInt());
System.out.println(hash1);
for (int i = 0; i < 50; i++) {
int x = rdm.nextInt(width);
int y = rdm.nextInt(height);
g.drawOval(x, y, 0, 0);
}
String capstr = hash1.substring(0, 4);
Session session = SecurityUtils.getSubject().getSession();
//将生成的验证码存入session
session.setAttribute("validateCode", capstr);
g.setColor(new Color(53, 61, 255));
g.setFont(new Font("宋体", Font.PLAIN, 24));
g.drawString(capstr, 8, 24);
g.dispose();
//输出图片
resp.setContentType("image/jpeg");
OutputStream strm = resp.getOutputStream();
ImageIO.write(image, "jpeg", strm);
strm.close();
}
}
@RequestMapping("/login")
public String login(HttpServletRequest request, Map<String, Object> map) throws Exception {
System.out.println("HomeController.login()");
// 登录失败从request中获取shiro处理的异常信息。
// shiroLoginFailure:就是shiro异常类的全类名.
//------------------------------------------------
String exception = (String) request.getAttribute("shiroLoginFailure");
System.out.println("exception=" + exception);
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";
}
验证码拦截校验
package com.wxbd.wb_mine.filter;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
@Override //验证码校验拦截
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
// 在这里进行验证码的校验
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
Session session = SecurityUtils.getSubject().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);
}
}
5.设置验证码拦截权限
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/AdminLTE/**", "anon");
filterChainDefinitionMap.put("/image/**", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");//退出
filterChainDefinitionMap.put("/validatecodeServlet", "anon");//验证码
filterChainDefinitionMap.put("/favicon.ico", "anon");
//<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
//<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
filterChainDefinitionMap.put("/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
System.out.println("ShiroConfiguration.shirFilter() success");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
最终成果