前言
本文是根据上篇《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="验证码" onclick="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模板权限控制 附源码
评论 (0)