前言
本文是根据上篇《Spring Boot 整合Shiro(二)加密登录与密码加盐处理》进行修改,如有不明白的转上篇文章了解。
1.导入依赖
-
<!-- https://mvnrepository.com/artifact/com.github.penggle/kaptcha -->
-
<dependency>
-
<groupId>com.github.penggle
</groupId>
-
<artifactId>kaptcha
</artifactId>
-
<version>2.3.2
</version>
-
</dependency>
2.配置
创建KaptchaConfig.java
-
import com.google.code.kaptcha.impl.DefaultKaptcha;
-
import com.google.code.kaptcha.util.Config;
-
import org.springframework.context.annotation.Bean;
-
import org.springframework.stereotype.Component;
-
-
import java.util.Properties;
-
-
@Component
-
public
class KaptchaConfig {
-
@Bean
-
public DefaultKaptcha getDefaultKaptcha() {
-
com.google.code.kaptcha.impl.DefaultKaptcha defaultKaptcha =
new com.google.code.kaptcha.impl.DefaultKaptcha();
-
Properties properties =
new Properties();
-
// 图片边框
-
properties.setProperty(
"kaptcha.border",
"yes");
-
// 边框颜色
-
properties.setProperty(
"kaptcha.border.color",
"105,179,90");
-
// 字体颜色
-
properties.setProperty(
"kaptcha.textproducer.font.color",
"red");
-
// 图片宽
-
properties.setProperty(
"kaptcha.image.width",
"110");
-
// 图片高
-
properties.setProperty(
"kaptcha.image.height",
"40");
-
// 字体大小
-
properties.setProperty(
"kaptcha.textproducer.font.size",
"30");
-
// session key
-
properties.setProperty(
"kaptcha.session.key",
"code");
-
// 验证码长度
-
properties.setProperty(
"kaptcha.textproducer.char.length",
"4");
-
// 字体
-
properties.setProperty(
"kaptcha.textproducer.font.names",
"宋体,楷体,微软雅黑");
-
-
//可以设置很多属性,具体看com.google.code.kaptcha.Constants
-
// kaptcha.border 是否有边框 默认为true 我们可以自己设置yes,no
-
// kaptcha.border.color 边框颜色 默认为Color.BLACK
-
// kaptcha.border.thickness 边框粗细度 默认为1
-
// kaptcha.producer.impl 验证码生成器 默认为DefaultKaptcha
-
// kaptcha.textproducer.impl 验证码文本生成器 默认为DefaultTextCreator
-
// kaptcha.textproducer.char.string 验证码文本字符内容范围 默认为abcde2345678gfynmnpwx
-
// kaptcha.textproducer.char.length 验证码文本字符长度 默认为5
-
// kaptcha.textproducer.font.names 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize)
-
// kaptcha.textproducer.font.size 验证码文本字符大小 默认为40
-
// kaptcha.textproducer.font.color 验证码文本字符颜色 默认为Color.BLACK
-
// kaptcha.textproducer.char.space 验证码文本字符间距 默认为2
-
// kaptcha.noise.impl 验证码噪点生成对象 默认为DefaultNoise
-
// kaptcha.noise.color 验证码噪点颜色 默认为Color.BLACK
-
// kaptcha.obscurificator.impl 验证码样式引擎 默认为WaterRipple
-
// kaptcha.word.impl 验证码文本字符渲染 默认为DefaultWordRenderer
-
// kaptcha.background.impl 验证码背景生成器 默认为DefaultBackground
-
// kaptcha.background.clear.from 验证码背景颜色渐进 默认为Color.LIGHT_GRAY
-
// kaptcha.background.clear.to 验证码背景颜色渐进 默认为Color.WHITE
-
// kaptcha.image.width 验证码图片宽度 默认为200
-
// kaptcha.image.height 验证码图片高度 默认为50
-
Config config =
new Config(properties);
-
defaultKaptcha.setConfig(config);
-
-
return defaultKaptcha;
-
}
-
-
}
3.验证码生成/验证
-
import java.awt.image.BufferedImage;
-
import java.io.ByteArrayOutputStream;
-
import javax.imageio.ImageIO;
-
import javax.servlet.ServletOutputStream;
-
import javax.servlet.http.HttpServletRequest;
-
import javax.servlet.http.HttpServletResponse;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.stereotype.Controller;
-
import org.springframework.web.bind.annotation.RequestMapping;
-
import org.springframework.web.servlet.ModelAndView;
-
import com.google.code.kaptcha.impl.DefaultKaptcha;
-
-
-
@Controller
-
@RequestMapping(value =
"/kaptcha" )
-
public
class KaptchaController {
-
-
/**
-
* 1、验证码工具
-
*/
-
@Autowired
-
DefaultKaptcha defaultKaptcha;
-
-
/**
-
* 2、生成验证码
-
* @param httpServletRequest
-
* @param httpServletResponse
-
* @throws Exception
-
*/
-
@RequestMapping(
"/defaultKaptcha")
-
public void defaultKaptcha(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
-
throws Exception {
-
byte[] captchaChallengeAsJpeg =
null;
-
ByteArrayOutputStream jpegOutputStream =
new ByteArrayOutputStream();
-
try {
-
// 生产验证码字符串并保存到session中
-
String createText = defaultKaptcha.createText();
-
httpServletRequest.getSession().setAttribute(
"rightCode", createText);
-
// 使用生产的验证码字符串返回一个BufferedImage对象并转为byte写入到byte数组中
-
BufferedImage challenge = defaultKaptcha.createImage(createText);
-
ImageIO.write(challenge,
"jpg", jpegOutputStream);
-
}
catch (IllegalArgumentException e) {
-
httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND);
-
return;
-
}
-
-
// 定义response输出类型为image/jpeg类型,使用response输出流输出图片的byte数组
-
captchaChallengeAsJpeg = jpegOutputStream.toByteArray();
-
httpServletResponse.setHeader(
"Cache-Control",
"no-store");
-
httpServletResponse.setHeader(
"Pragma",
"no-cache");
-
httpServletResponse.setDateHeader(
"Expires",
0);
-
httpServletResponse.setContentType(
"image/jpeg");
-
ServletOutputStream responseOutputStream = httpServletResponse.getOutputStream();
-
responseOutputStream.write(captchaChallengeAsJpeg);
-
responseOutputStream.flush();
-
responseOutputStream.close();
-
}
-
-
/**
-
* 3、校对验证码
-
* @param httpServletRequest
-
* @param httpServletResponse
-
* @return
-
*/
-
@RequestMapping(
"/imgvrifyControllerDefaultKaptcha")
-
public ModelAndView imgvrifyControllerDefaultKaptcha(HttpServletRequest httpServletRequest,
-
HttpServletResponse httpServletResponse) {
-
ModelAndView andView =
new ModelAndView();
-
String rightCode = (String) httpServletRequest.getSession().getAttribute(
"rightCode");
-
String tryCode = httpServletRequest.getParameter(
"tryCode");
-
System.out.println(
"rightCode:"+rightCode+
" ———— tryCode:"+tryCode);
-
if (!rightCode.equals(tryCode)) {
-
andView.addObject(
"info",
"错误的验证码");
-
andView.setViewName(
"/login");
-
}
else {
-
andView.addObject(
"info",
"登录成功");
-
andView.setViewName(
"success");
-
}
-
return andView;
-
}
-
}
4.修改ShiroConfig
开放相应的验证码路径,避免拦截
-
@Bean(name =
"shiroFilter")
-
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
-
ShiroFilterFactoryBean shiroFilterFactoryBean =
new ShiroFilterFactoryBean();
-
// Shiro的核心安全接口,这个属性是必须的
-
shiroFilterFactoryBean.setSecurityManager(securityManager);
-
// 身份认证失败,则跳转到登录页面的配置
-
shiroFilterFactoryBean.setLoginUrl(
"/login");
-
// 权限认证失败,则跳转到指定页面
-
shiroFilterFactoryBean.setUnauthorizedUrl(
"/notRole");
-
Map<String, String> filterChainDefinitionMap =
new LinkedHashMap<>();
-
// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
-
filterChainDefinitionMap.put(
"/webjars/**",
"anon");
-
filterChainDefinitionMap.put(
"/login",
"anon");
-
filterChainDefinitionMap.put(
"/loginOut",
"anon");
-
filterChainDefinitionMap.put(
"/",
"anon");
-
filterChainDefinitionMap.put(
"/front/**",
"anon");
-
filterChainDefinitionMap.put(
"/api/**",
"anon");
-
filterChainDefinitionMap.put(
"/kaptcha/**",
"anon");
-
filterChainDefinitionMap.put(
"/success/**",
"anon");
-
-
filterChainDefinitionMap.put(
"/admin/**",
"authc");
-
filterChainDefinitionMap.put(
"/user/**",
"authc");
-
//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证
-
filterChainDefinitionMap.put(
"/**",
"authc");
-
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
-
return shiroFilterFactoryBean;
-
-
}
5.修改LoginController
-
@Controller
-
@RequestMapping
-
public
class LoginController {
-
-
private Logger logger = Logger.getLogger(
this.getClass().getName());
-
-
-
/**
-
* 界面
-
* @return
-
*/
-
@RequestMapping(value =
"/login", method = RequestMethod.GET)
-
public String defaultLogin() {
-
return
"login";
-
}
-
-
/**
-
* 退出
-
* @return
-
*/
-
@RequestMapping(value =
"/loginOut", method = RequestMethod.GET)
-
public String loginOut() {
-
Subject subject = SecurityUtils.getSubject();
-
subject.logout();
-
return
"login";
-
}
-
-
/**
-
* 登录提交
-
* @param username
-
* @param tryCode
-
* @param password
-
* @param redirectAttributes
-
* @return
-
*/
-
@RequestMapping(value =
"/login", method = RequestMethod.POST)
-
public String login(@RequestParam("username") String username,
-
@RequestParam("tryCode") String tryCode,
-
@RequestParam("password") String password,
-
RedirectAttributes redirectAttributes) {
-
//判断验证码
-
if(StringUtils.isBlank(tryCode)){
-
logger.info(
"验证码为空了!");
-
redirectAttributes.addFlashAttribute(
"message",
"验证码不能为空!");
-
return
"redirect:login";
-
}
-
Session session = SecurityUtils.getSubject().getSession();
-
String code = (String) session.getAttribute(
"rightCode");
-
System.out.println(code+
"*************"+tryCode);
-
if(!tryCode.equalsIgnoreCase(code)){
-
logger.info(
"验证码错误!");
-
redirectAttributes.addFlashAttribute(
"message",
"验证码错误!");
-
return
"redirect:login";
-
}
-
// 从SecurityUtils里边创建一个 subject
-
Subject subject = SecurityUtils.getSubject();
-
// 在认证提交前准备 token(令牌)
-
UsernamePasswordToken token =
new UsernamePasswordToken(username, password);
-
String attributeValue =
null;
-
// 执行认证登陆
-
try {
-
subject.login(token);
-
}
catch (UnknownAccountException uae) {
-
attributeValue=
"未知账户!";
-
-
}
catch (IncorrectCredentialsException ice) {
-
attributeValue=
"密码不正确!";
-
}
catch (LockedAccountException lae) {
-
attributeValue=
"账户已锁定";
-
}
catch (ExcessiveAttemptsException eae) {
-
attributeValue=
"用户名或密码错误次数过多";
-
}
catch (AuthenticationException ae) {
-
attributeValue=
"用户名或密码不正确!";
-
}
finally {
-
redirectAttributes.addFlashAttribute(
"message", attributeValue);
-
if (subject.isAuthenticated()) {
-
return
"success";
-
}
else {
-
token.clear();
-
return
"redirect:login";
-
}
-
}
-
-
-
}
-
-
}
6.界面
6.1整合thymeleaf模板
添加依赖;
-
<!--thymeleaf-->
-
<dependency>
-
<groupId>org.springframework.boot
</groupId>
-
<artifactId>spring-boot-starter-thymeleaf
</artifactId>
-
</dependency>
编辑application.yml
-
server:
-
port:
80
-
servlet:
-
context-path: /
-
-
-
#thymeleaf模板
-
spring:
-
thymeleaf:
-
cache:
true
-
prefix:
-
classpath: /templates/
-
suffix: .html
-
mode: HTML5
-
encoding: UTF
-8
-
servlet:
-
content-type: text/html
6.2创建
login.html
-
<!DOCTYPE html>
-
<!-- thymeleaf 提示功能 -->
-
<html xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html">
-
<head lang="en">
-
<meta charset="UTF-8">
</meta>
-
<title>验证码
</title>
-
</head>
-
<style type="text/css">
-
body {
-
padding:
10px
-
}
-
</style>
-
<body>
-
<!-- 提示 -->
-
<h3 th:text="${message}">
</h3>
-
<div>
-
<!-- 后面添加参数起到清除缓存作用 -->
-
<img alt="验证码" οnclick="this.src='/kaptcha/defaultKaptcha?d='+new Date()*1" src="/kaptcha/defaultKaptcha" />
-
</div>
-
<form action="/login" method="post">
-
账户:
<input type="text" name="username" />
</br>
-
密码:
<input type="text" name="password" />
</br>
-
验证码:
<input type="text" name="tryCode" />
-
<input type="submit" value="提交" >
</input>
-
</form>
-
</body>
-
</html>
-
success.html
-
<!DOCTYPE html>
-
<html>
-
<head>
-
<meta charset="UTF-8">
</meta>
-
<title>成功
</title>
-
</head>
-
<body>
-
<h1>登录成功
</h1>
-
<a href="/loginOut">退出
</a>
-
</body>
-
</html>
7. 实战演练
8.项目源码
下篇介绍Spring Boot 整合 Shiro(四)thymeleaf模板权限控制 附源码