接着上次学习的《Spring Boot2(十二):手摸手教你搭建Shiro安全框架》,实现了Shiro的认证和授权。今天继续在这个基础上学习Shiro实现功能记住我rememberMe,以及登录时验证码Kaptcha。

Remember Me记住我:用户的登录状态会不会因为浏览器的关闭而失效,直到Cookie失效。关闭浏览器后,再次访问登录后的页面可以不用登录。因为用Cookie实现,故只在同一浏览器中有效。

Kaptcha验证码:是谷歌开源的验证码插件,实现登录的验证码验证拦截。

一、记住我rememberMe

用户的登录状态会不会因为浏览器的关闭而失效,直到Cookie失效。关闭浏览器后,再次访问登录后的页面可以不用登录。因为用Cookie实现,故只在同一浏览器中有效。

修改ShiroConfig

/**
* 路径过滤规则
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/index");
// 拦截器
LinkedHashMap<String, String> map = new LinkedHashMap<>();
// 配置不会被拦截的链接 顺序判断
// 对静态资源设置匿名访问
map.put("/static/**", "anon");
map.put("/css/**", "anon");
map.put("/js/**", "anon"); // 过滤链定义,从上向下顺序执行,一般将/**放在最为下边
// 进行身份认证后才能访问
// authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问
// user指的是用户认证通过或者配置了Remember Me记住用户登录状态后可访问
map.put("/**", "user");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}

因为对登录页面做了一些样式,新增了静态资源文件static,这时候遇到了坑,页面引用的jscss都无效了,然后发现时因为被拦截了,我们需要在Shiro的拦截器中允许对静态资源的匿名anon访问。

注意到将ShiroFilterFactoryBeanmap.put("/**", "authc");更改为map.put("/**", "user");user是指用户认证通过或配置了RememberMe记住用户登录状态后可访问。

解决过程查阅了一些资料,不光光只对cssjs的放开,还需要对static也放开

对静态资源的拦截相关问题可以参照这里了解学习一下:Spring Boot Shiro无法访问JS/CSS/IMG+自定义Filter无法访问完美方案

回来继续,调用SimpleCookie,配置Cookie的基本属性:名称和过期时间。

/**
* cookie对象
* @return
*/
public SimpleCookie rememberMeCookie() {
// 设置cookie名称,对应login.html页面的<input type="checkbox" name="rememberMe"/>
SimpleCookie cookie = new SimpleCookie("rememberMe");
// 设置cookie的过期时间,单位为秒,这里为一天
cookie.setMaxAge(86400);
return cookie;
}

SimleCookie参数中的名称为页面的name标签属性名称。

实现了Cookie对象属性配置,还需要通过CookieRememberMeManager进行管理起来。

/**
* cookie管理对象
* rememberMeManager()方法是生成rememberMe管理器,而且要将这个rememberMe管理器设置到securityManager中
* @return
*/
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
// rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
return cookieRememberMeManager;
}

接下来将cookie管理对象设置到SecurityManager中:

@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm
securityManager.setRealm(authRealm());
// 用户授权/认证信息Cache, 采用EhC//注入记住我管理器
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}

加密处理

《Spring Boot2(十二):手摸手教你搭建Shiro安全框架》这个项目中用的明文,这里我们升个级,使用MD5加密

新建MD5加密工具类。

public class MD5Utils {

    private static final String SALT = "niaobulashi";

    private static final String ALGORITH_NAME = "md5";

    private static final int HASH_ITERATIONS = 2;

    public static String encrypt(String pwd) {
String newPassword = new SimpleHash(ALGORITH_NAME, pwd, ByteSource.Util.bytes(SALT), HASH_ITERATIONS).toHex();
return newPassword;
} public static String encrypt(String username, String pwd) {
String newPassword = new SimpleHash(ALGORITH_NAME, pwd, ByteSource.Util.bytes(username + SALT),
HASH_ITERATIONS).toHex();
return newPassword;
} public static void main(String[] args) {
System.out.println("MD5加密后的密文为:" + MD5Utils.encrypt("root", "root"));
}
}

其中SALT是加密的盐,可自行定义。

main方法中,根据登录名和密码明文,输出最终加密的密文,将输出内容粘贴到我们的数据库中,待后续登录时使用。

新增登录页面和主页面

登录页login.html

添加Remember Me checkbox

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录</title>
<link rel="stylesheet" th:href="@{/static/css/login.css}" type="text/css">
<script th:src="@{/static/js/jquery-1.11.1.min.js}"></script>
</head>
<body>
<div class="login-page">
<div class="form">
<input type="text" placeholder="用户名" name="account" required="required"/>
<input type="password" placeholder="密码" name="password" required="required"/>
<p><input type="checkbox" name="rememberMe"/>记住我</p>
<button onclick="login()">登录</button>
</div>
</div>
</body>
<script th:inline="javascript">var ctx = [[@{/}]];</script>
<script th:inline="javascript">
function login() {
var account = $("input[name='account']").val();
var password = $("input[name='password']").val();
var rememberMe = $("input[name='rememberMe']").is(':checked');
$.ajax({
type: "post",
url: ctx + "login",
data: {
"account": account,
"password": password,
"rememberMe": rememberMe
},
success: function(r) {
if (r.code == 100) {
location.href = ctx + 'index';
} else {
alert(r.message);
}
}
});
}
</script>
</html>

静态资源js和css可以在源码中查看

首页index.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body>
<p>你好![[${user.getUsername()}]]</p>
<a th:href="@{/logout}">注销</a>
</body>
</html>

Controller层

在原来的基础上,新增参数rememberMe,同时对用户名和明文密码进行MD5加密处理获得密文。

登录接口

/**
* 登录操作
* @param account
* @param password
* @param rememberMe
* @return
*/
@PostMapping("/login")
@ResponseBody
public ResponseCode login(String account, String password, Boolean rememberMe) {
logger.info("登录请求-start");
password = MD5Utils.encrypt(account, password);
Subject userSubject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(account, password, rememberMe);
try {
// 登录验证
userSubject.login(token);
return ResponseCode.success();
} catch (UnknownAccountException e) {
return ResponseCode.error(StatusEnums.ACCOUNT_UNKNOWN);
} catch (DisabledAccountException e) {
return ResponseCode.error(StatusEnums.ACCOUNT_IS_DISABLED);
} catch (IncorrectCredentialsException e) {
return ResponseCode.error(StatusEnums.INCORRECT_CREDENTIALS);
} catch (AuthenticationException e) {
return ResponseCode.error(StatusEnums.AUTH_ERROR);
} catch (Throwable e) {
e.printStackTrace();
return ResponseCode.error(StatusEnums.SYSTEM_ERROR);
}
}

注销接口

/**
* 登出
* @return
*/
@GetMapping("/logout")
public String logout() {
getSubject().logout();
return "login";
}

启动项目,进行测试可以看到效果如下:

二、验证码Kaptcha

kaptcha 是一个非常实用的验证码生成工具。有了它,你可以生成各种样式的验证码,因为它是可配置的。kaptcha工作的原理是调用 com.google.code.kaptcha.servlet.KaptchaServlet,生成一个图片。同时将生成的验证码字符串放到 HttpSession中。

Kaptcha官网:https://code.google.com/archive/p/kaptcha/

使用kaptcha可以方便的配置:

  • 验证码的字体
  • 验证码字体的大小
  • 验证码字体的字体颜色
  • 验证码内容的范围(数字,字母,中文汉字!)
  • 验证码图片的大小,边框,边框粗细,边框颜色
  • 验证码的干扰线(可以自己继承com.google.code.kaptcha.NoiseProducer写一个自定义的干扰线)
  • 验证码的样式(鱼眼样式、3D、普通模糊……当然也可以继承com.google.code.kaptcha.GimpyEngine自定义样式)

kaptcha配置详解

kaptcha对象属性 作用 默认值
kaptcha.border 是否有边框 默认为true
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

添加maven依赖

<!--验证码-->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>

新增验证码图片样式配置器

具体配置可以参考上面的kaptche配置详情,针对不同的常见配置。

@Configuration
public class KaptchaConfig { @Bean(name="captchaProducer")
public DefaultKaptcha getKaptchaBean(){
DefaultKaptcha defaultKaptcha=new DefaultKaptcha();
Properties properties=new Properties();
//验证码字符范围
properties.setProperty("kaptcha.textproducer.char.string", "23456789");
//图片边框颜色
properties.setProperty("kaptcha.border.color", "245,248,249");
//字体颜色
properties.setProperty("kaptcha.textproducer.font.color", "black");
//文字间隔
properties.setProperty("kaptcha.textproducer.char.space", "1");
//图片宽度
properties.setProperty("kaptcha.image.width", "100");
//图片高度
properties.setProperty("kaptcha.image.height", "35");
//字体大小
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", "宋体,楷体,微软雅黑");
Config config=new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}

新增图片验证码Controller层

是一个创建文件图片流的过程,使用ServletOutPutStream输出最后的图片。

开头声明的@Resource(name = "captchaProducer"),是验证码图片样式配置器启动时配置的Bean:captchaProducer

@Controller
@RequestMapping("/captcha")
public class KaptchaController { private static final Logger logger = LoggerFactory.getLogger(KaptchaController.class); @Resource(name = "captchaProducer")
private Producer captchaProducer; @GetMapping("/captchaImage")
public ModelAndView getKaptchaImage(HttpServletRequest request, HttpServletResponse response) throws Exception {
ServletOutputStream out = response.getOutputStream();
try {
HttpSession session = request.getSession();
response.setDateHeader("Expires", 0);
// Set standard HTTP/1.1 no-cache headers.
response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
// Set IE extended HTTP/1.1 no-cache headers (use addHeader).
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
// Set standard HTTP/1.0 no-cache header.
response.setHeader("Pragma", "no-cache");
// return a jpeg
response.setContentType("image/jpeg");
// create the text for the image
String capText = captchaProducer.createText();
//将验证码存到session
session.setAttribute(Constants.KAPTCHA_SESSION_KEY, capText);
logger.info(capText);
// 创建一张文本图片
BufferedImage bi = captchaProducer.createImage(capText);
// 响应
out = response.getOutputStream();
// 写入数据
ImageIO.write(bi, "jpg", out); out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
}
catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}

注意最后都需要将流关闭out.close()

放开图片验证码的拦截

重启会发现,图片验证码的接口请求无法访问,还是跳转到了localhost:8081/login登录页面

因为Shiro配置的拦截器没有放开,需要再ShiroConfig中允许匿名访问改请求资源

map.put("/captcha/captchaImage**", "anon");

登录页面添加图片验证码

<div class="login-page">
<div class="form">
<input type="text" placeholder="用户名" name="account" required="required"/>
<input type="password" placeholder="密码" name="password" required="required"/>
<p>
<label>验证码<br/>
<input type="text" name="validateCode" id="validateCode" class="validateCode" required="required"/>
<a href="javascript:void(0);">
<img src="/captcha/captchaImage" onclick="this.src='/captcha/captchaImage?'+Math.random()"/>
</a>
</label>
</p>
<br>
<p><input type="checkbox" name="rememberMe"/>记住我</p>
<button onclick="login()">登录</button>
</div>
</div>

上面div为body的全部部分

我在请求/captcha/captchaImage后面添加随机值Math.random()。是因为客户浏览器会缓存URL相同的资源,故使用随机数来重新请求。这和前端上线时,请求后缀都会变更一个版本号一样,不需要让客户手动刷新浏览器就可以获取最新资源一样。

修改登录请求接口

主要是验证后台生成的验证码,与前台输入的验证码进行比较,验证是否相同

这里只粘贴出验证码验证的逻辑,源码在文章最后。

可以看出validateCode是前端请求过来的参数,先校验是否为空。

然后从session中获取后台生成的验证码。

最后通过比较前端输入的验证码和后台生成的是否一致。

//1、检验验证码
if(validateCode == null || validateCode == ""){
return ResponseCode.error(StatusEnums.PARAM_NULL);
}
Session session = SecurityUtils.getSubject().getSession();
//转化成小写字母
validateCode = validateCode.toLowerCase();
String v = (String) session.getAttribute(Constants.KAPTCHA_SESSION_KEY);
//还可以读取一次后把验证码清空,这样每次登录都必须获取验证码
//session.removeAttribute("_come");
if(!validateCode.equals(v)){
return ResponseCode.error(StatusEnums.VALIDATECODE_ERROR);
}

下图是登录校验验证码的debug过程。

三、源码

源码地址:spring-boot-23-shiro-remember

欢迎star、fork,给作者一些鼓励


菜鸟也要成为架构师,一起努力

欢迎关注我微信公众号【鸟不拉屎】

谢谢,一起学习,共同进步,成为优秀的人。

Spring Boot2(十五):Shiro记住我rememberMe、验证码Kaptcha的更多相关文章

  1. Spring Boot(十五):spring boot+jpa+thymeleaf增删改查示例

    Spring Boot(十五):spring boot+jpa+thymeleaf增删改查示例 一.快速上手 1,配置文件 (1)pom包配置 pom包里面添加jpa和thymeleaf的相关包引用 ...

  2. spring boot(十四)shiro登录认证与权限管理

    这篇文章我们来学习如何使用Spring Boot集成Apache Shiro.安全应该是互联网公司的一道生命线,几乎任何的公司都会涉及到这方面的需求.在Java领域一般有Spring Security ...

  3. Spring Boot2(十二):手摸手教你搭建Shiro安全框架

    一.前言 SpringBoot+Shiro+Mybatis完成的. 之前看了一位小伙伴的Shiro教程,跟着做了,遇到蛮多坑的(´இ皿இ`) 修改整理了一下,成功跑起来了.可以通过postman进行测 ...

  4. 面渣逆袭:Spring三十五问,四万字+五十图详解

    大家好,我是老三啊,面渣逆袭 继续,这节我们来搞定另一个面试必问知识点--Spring. 有人说,"Java程序员都是Spring程序员",老三不太赞成这个观点,但是这也可以看出S ...

  5. (转)Spring Boot (十五): Spring Boot + Jpa + Thymeleaf 增删改查示例

    http://www.ityouknow.com/springboot/2017/09/23/spring-boot-jpa-thymeleaf-curd.html 这篇文章介绍如何使用 Jpa 和 ...

  6. spring boot(十五)spring boot+thymeleaf+jpa增删改查示例

    快速上手 配置文件 pom包配置 pom包里面添加jpa和thymeleaf的相关包引用 <dependency> <groupId>org.springframework.b ...

  7. Spring Boot (十五): Spring Boot + Jpa + Thymeleaf 增删改查示例

    这篇文章介绍如何使用 Jpa 和 Thymeleaf 做一个增删改查的示例. 先和大家聊聊我为什么喜欢写这种脚手架的项目,在我学习一门新技术的时候,总是想快速的搭建起一个 Demo 来试试它的效果,越 ...

  8. spring学习 十五 spring的自动注入

    一  :在 Spring 配置文件中对象名和 ref=”id” ,id 名相同使用自动注入,可以不配置<property/>,对应的注解@Autowired的作用 二: 两种配置办法 (1 ...

  9. Spring(十五):通过注解配置 Bean

    在ClassPath中扫描组件 1)组件扫描(component scanning):Spring能够从classpath下自动扫描,侦测和实例化具有特定注解的组件: 2)特定组件包含: --- @C ...

随机推荐

  1. Codility--- Distinct

    Task description Write a function class Solution { public int solution(int[] A); } that, given a zer ...

  2. HTML连载13-CSS基本格式以及文字相关的属性

    一.CSS格式 1.注意点: (1)style标签必须写在head标签的开始标签和结束标签之间(也就是必须和title标签师兄弟关系) (2)style标签中的属性type属性其实可以不写,默认就是t ...

  3. Postman支持的几种数据类型请求方式

    一.postman作为web应用开发工具,可以用于模拟多种请求方式,但是支持的传参类型又不尽相同.根据面板上的几种数据打包方式来选择合适的请求数据类型. form-data 就是http请求中的mul ...

  4. 【搜索引擎】Solr全文检索近实时查询优化

    设置多个搜索建议查找算法 <searchComponent name="suggest" class="solr.SuggestComponent"> ...

  5. sql关联查询—将一个表的查询结果作为新表进行查询操作

    例题:#  各个部门中 最高工资中最低的那个部门的 最低工资是多少? 先考虑取得各个部门最高工资 SELECT MAX(salary) AS max_salary,e.`department_id` ...

  6. Hive 学习之路(六)—— Hive 视图和索引

    一.视图 1.1 简介 Hive 中的视图和RDBMS中视图的概念一致,都是一组数据的逻辑表示,本质上就是一条SELECT语句的结果集.视图是纯粹的逻辑对象,没有关联的存储(Hive 3.0.0引入的 ...

  7. mysql group_concat的长度问题

    mysql group_concat的长度问题 show variables like 'group_concat_max_len';+----------------------+-------+| ...

  8. laravel配置不同环境的配置文件

    //在入口bootstrap/App.php中 $env = $app->detectEnvironment( function () use ($app) { $uname = php_una ...

  9. SSH不能连接并提示REMOTE HOST IDENTIFICATION HAS CHANGED

    ssh连接报错: 解决: vi /root/.ssh/known_hosts 找到要连接的主机的ip,把它的那行所有信息删除(就是一行)

  10. Appium+python自动化(十七)- 你难道猴哥失散多年的混血弟弟 - Monkey简介之开山篇(超详解)

    简介 今天由宏哥给小伙伴们来介绍猴哥的混血弟弟=Monkey.Monkey 是Android SDK提供的一个命令行工具, 可以简单,方便地运行在任何版本的Android模拟器和实体设备上. Monk ...