一、概述

前面一篇文章,我们已经总结了Shiro相关的一些概念以及架构知识,相信小伙伴们对Shiro安全框架都有了一定的认识。本篇文章我们将通过示例详细说明在日常工作中常见的-----用户身份认证功能。

什么是身份认证呢,简单理解,就是在应用中谁能证明他就是他本人。一般提供如他们的身份 ID 一些标识信息来表明他就是他本人,如提供身份证,用户名 / 密码来证明。

在 shiro 中,用户需要提供 principals (身份)和 credentials(证明)给 shiro,从而应用能验证用户身份:

  • principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个 Primary principals,一般是用户名 / 密码 / 手机号。
  • credentials:证明 / 凭证,即只有主体知道的安全值,如密码 / 数字证书等。

最常见的 principals 和 credentials 组合就是用户名 / 密码。

接下来,我们以SpringBoot为基础,通过整合Shiro来实现用户身份认证功能。

二、身份认证流程

在Spring Boot中集成Shiro进行用户的认证过程主要有以下三点:

  • 1、定义一个ShiroConfig,然后配置SecurityManager,SecurityManager是Shiro的安全管理器,管理着所有Subject;
  • 2、在ShiroConfig中配置ShiroFilterFactoryBean,其为Shiro过滤器工厂类,依赖于SecurityManager;
  • 3、自定义Realm实现;

接下来,我们创建一个SpringBoot项目:【springboot-shiro】,整体项目结构如下图所示:

前提:我们先创建一个 数据库shiro,然后创建一张用户表user并初始化一条用户信息,表sql如下:

CREATE TABLE `user` (`id` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '用户id',`username` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '用户名',`password` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '用户密码',`status` char(1) COLLATE utf8_bin DEFAULT '1' COMMENT '用户状态',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=COMPACT

并初始化一条用户信息:

INSERT INTO `user` VALUES ('1', 'admin', '123456', '1');

【a】引入一些相关的依赖:mysql、mybatis、thymeleaf、shiro等。具体pom.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.10.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.wsh.springboot</groupId><artifactId>springboot-shiro</artifactId><version>0.0.1-SNAPSHOT</version><name>springboot-shiro</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-web-starter</artifactId><version>1.4.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.3</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

【b】定义User实体类,接口UserMapper外加上UserMapper.xml配置实现

public class User {private String id;private String username;private String password;private String status;public String getId() {return id;}public void setId(String id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getStatus() {return status;}public void setStatus(String status) {this.status = status;}
}

UserMapper.java:

@Mapper
public interface UserMapper {/*** 根据用户名查找用户信息** @param name 用户名* @return*/User findUserByName(@Param("name") String name);
}

UserMapper.xml:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wsh.springboot.springbootshiro.mapper.UserMapper"><resultMap type="com.wsh.springboot.springbootshiro.entity.User" id="baseUser"><id column="id" property="id" javaType="java.lang.String" jdbcType="VARCHAR"/><id column="username" property="username" javaType="java.lang.String" jdbcType="VARCHAR"/><id column="password" property="password" javaType="java.lang.String" jdbcType="VARCHAR"/><id column="status" property="status" javaType="java.lang.String" jdbcType="VARCHAR"/></resultMap><select id="findUserByName" resultMap="baseUser" parameterType="String">SELECT * FROM USER twhere t.username = #{name}</select>
</mapper>

【c】配置文件 application.yml,配置数据源信息、mybatis扫描的包等

server:port: 8080
spring:datasource:driver-class-name: com.mysql.jdbc.Driverusername: rootpassword: rooturl: jdbc:mysql://localhost:3306/shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
mybatis:mapper-locations: classpath:mapper/*Mapper.xmltype-aliases-package: com.wsh.springboot.springbootshiro.entityconfiguration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

【d】自定义Realm,通过继承AuthenticatingRealm 实现

需要重写doGetAuthenticationInfo方法。

package com.wsh.springboot.springbootshiro.realm;import com.wsh.springboot.springbootshiro.entity.User;
import com.wsh.springboot.springbootshiro.mapper.UserMapper;
import org.apache.shiro.authc.*;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;public class MyShiroRealm extends AuthenticatingRealm {private static final Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);@Autowiredprivate UserMapper userMapper;/*** 认证相关方法*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;//1.判断用户名, token中的用户信息是登录时候传进来的String username = usernamePasswordToken.getUsername();char[] password = usernamePasswordToken.getPassword();logger.info("username:" + username);logger.info("password:" + new String(password));//通过账号查找用户信息User user = userMapper.findUserByName(username);if (null == user) {logger.error("用户不存在..");throw new UnknownAccountException("用户不存在!");}if ("0".equals(user.getStatus())) {throw new LockedAccountException("账号已被锁定,请联系管理员!");}//数据库中查询的用户名Object principal = user.getUsername();//数据库中查询的密码Object credentials = user.getPassword();String realmName = getName();//2.判断密码return new SimpleAuthenticationInfo(principal, credentials, null, realmName);}
}

【e】创建Shiro的全局配置类

import com.wsh.springboot.springbootshiro.realm.MyShiroRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;import java.util.LinkedHashMap;
import java.util.Map;/*** @Description: Shiro全局配置类* @author: weishihuai* @Date: 2020/11/3 09:23* <p>* 三大组件:* 1. Subject: 用户主体(把操作交给SecurityManager)* 2. SecurityManager:安全管理器(关联Realm)* 3. Realm:Shiro连接数据的桥梁*/
@Configuration
public class ShiroConfiguration {/*** 将Realm注册到securityManager中** @return*/@Bean("securityManager")public DefaultWebSecurityManager securityManager() {return new DefaultWebSecurityManager(myShiroRealm());}/*** 配置自定义的Realm*/@Beanpublic MyShiroRealm myShiroRealm() {return new MyShiroRealm();}/*** 如果没有此name,将会找不到shiroFilter的Bean* <p>* Shiro内置过滤器,可以实现权限相关的拦截器* 常用的过滤器:* anon: 无需认证(登录)可以访问* authc: 必须认证才可以访问* user: 如果使用rememberMe的功能可以直接访问* perms: 该资源必须得到资源权限才可以访问* role: 该资源必须得到角色权限才可以访问*/@Beanpublic ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);//表示指定登录页面shiroFilterFactoryBean.setLoginUrl("/userLogin");//登录成功后要跳转的链接shiroFilterFactoryBean.setSuccessUrl("/success");//未授权页面shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");//拦截器, 配置不会被拦截的链接 顺序判断Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();//所有匿名用户均可访问到Controller层的该方法下filterChainDefinitionMap.put("/index", "anon");filterChainDefinitionMap.put("/userLogin", "anon");//authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问filterChainDefinitionMap.put("/**", "authc");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}/*** SpringShiroFilter首先注册到spring容器* 然后被包装成FilterRegistrationBean* 最后通过FilterRegistrationBean注册到servlet容器** @return*/@Beanpublic FilterRegistrationBean delegatingFilterProxy() {FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();DelegatingFilterProxy proxy = new DelegatingFilterProxy();proxy.setTargetFilterLifecycle(true);proxy.setTargetBeanName("shiroFilter");filterRegistrationBean.setFilter(proxy);return filterRegistrationBean;}
}

【f】在resource/templates文件夹下创建几个html文件:

index.html:

<!doctype html><!--注意:引入thymeleaf的名称空间-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title>
</head>
<body>
<h3 style="color: red" th:text="${msg}"></h3><br/>
<form method="post" action="/userLogin">用户名: <input type="text" name="username"><br/>密码: <input type="password" name="password"><br/><input type="submit" name="submit"><br/>
</form>
</body>
</html>

success.html:

<!doctype html><!--注意:引入thymeleaf的名称空间-->
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title>
</head>
<body>
你好,这是登录成功的页面<br/>
</div>
</body>
</html>

unauthorized.html:

<!doctype html><!--注意:引入thymeleaf的名称空间-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title>
</head>
<body>
抱歉,你暂未授权访问此页面
</body>
</html>

【g】编写UserController

package com.wsh.springboot.springbootshiro.controller;import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;@Controller
public class UserController {private static final Logger logger = LoggerFactory.getLogger(UserController.class);@GetMapping("/index")public String index() {//返回index.htmlreturn "index";}@GetMapping("/success")public String success() {return "success";}@RequestMapping(value = "/userLogin", method = RequestMethod.POST)public String toLogin(String username, String password, Model model) {//1.获取SubjectSubject subject = SecurityUtils.getSubject();//2.封装用户数据UsernamePasswordToken token = new UsernamePasswordToken(username, password);try {//3.执行登录方法subject.login(token);//4.登录成功,然后跳转到success.htmlreturn "redirect:/success";} catch (UnknownAccountException e) {logger.error("msg:该账号不存在");model.addAttribute("msg", "该账号不存在");return "index";} catch (IncorrectCredentialsException e) {logger.error("msg: 密码错误,请重试");model.addAttribute("msg", "密码错误,请重试");return "index";} catch (LockedAccountException e) {logger.error("msg:该账户已被锁定,请联系管理员");model.addAttribute("msg", "该账户已被锁定,请联系管理员");return "index";} catch (Exception e) {model.addAttribute("msg", "登录失败");logger.error("msg: 登录失败");return "index";}}@RequestMapping("/unauthorized")public String unauthorized() {return "unauthorized";}}

【h】主启动类

@SpringBootApplication
@MapperScan("com.wsh.springboot.springbootshiro.mapper")
public class SpringbootShiroApplication {public static void main(String[] args) {SpringApplication.run(SpringbootShiroApplication.class, args);}}

【i】启动项目,进行测试

浏览器访问:http://localhost:8080/index,输入admin/123456:

可以看到,因为此时用户名和密码跟数据库中是一致的,所以登录成功,并跳转到success.html中。

重新访问http://localhost:8080/index,输入admin11111/123456,如下图:

可以看到,由于用户表中不存在此用户,所以提示账号不存在。

重新访问http://localhost:8080/index,输入admin/111111,如下图:

可见,当输入错误的密码时,shiro提示密码错误。

三、总结

以上就是关于Shiro提供了用户身份认证功能,总结一下大体步骤:

  • 自定义Realm,继承AuthenticatingRealm ,重写方法doGetAuthenticationInfo(AuthenticationToken authenticationToken),通过subject.login方法传入的token,去数据库中查询用户数据,将用户密码传入shiro进行比对;
  • 自定义Shiro配置,注入SecurityManager安全管理器、ShiroFilterFactoryBean过滤器工厂等对象;

存在问题:在实际工作中,密码不可能明文传输,为了演示方便,上面的示例使用的明文密码比对,实际上Shiro也提供了加密功能,下一篇文章我们将优化成密文比对。

【Shiro第二篇】SpringBoot + Shiro实现用户身份认证功能相关推荐

  1. shiro中的验证用户身份认证以及授权

    目录 1.运用shiro进行用户身份认证: 1.1导入基于Shiro的数据库脚本 1.2.引入依赖(shiro-1.4.1) shiro-core  shiro-web  shiro-spring 1 ...

  2. 构建具有用户身份认证的 React + Flux 应用程序

    序言:这是一篇内容详实的 React + Flux 教程,文章主要介绍了如何使用 API 获取远程数据以及如何使用 JSON Web Tokens 进行用户身份认证.在阅读本文之后,我一直使用文章介绍 ...

  3. 构建具有用户身份认证的 Ionic 应用

    序言:本文主要介绍了使用 Ionic 和 Cordova 开发混合应用时如何添加用户身份认证.教程简易,对于 Ionic 入门学习有一定帮助.因为文章是去年发表,所以教程内关于 Okta 的一些使用步 ...

  4. 区块链BaaS云服务(36)欧盟“用户身份认证”ESSIF

    1.用户身份认证 ESSIF (European Self Sovereign identity framework ) 1.1 目前问题 数据孤岛.数据共享. 1.2 目前解决方法 2. 区块链解决 ...

  5. 转:实例学习PHP程序对用户身份认证实现两种方法

    用户在设计和维护站点的时候,经常需要限制对某些重要文件或信息的访问.通常,我们可以采用内置于WEB服务器的基于HTTP协议的用户身份验证机制. 当访问者浏览受保护页面时,客户端浏览器会弹出对话窗口要求 ...

  6. 使用数据库进行用户身份认证

    //使用数据库进行用户身份认证 //1.加载驱动程序 Class.forName("com.mysql.jdbc.Driver"); System.out.println(&quo ...

  7. 移动 APP 端与服务器端用户身份认证的安全方案

    公司的 mobile app 是外包给其他公司做的,所以现在他们需要我们提供 API 接口进行调试,由于没有 API 开发的经验,所以现在一个比较难把握的问题就是如何实现服务器端与移动 APP 端通信 ...

  8. Python控制流程语句实现各种小功能(用户身份认证、百分制转换等级制、英寸与厘米的转换、三角形的周长和面积、九九乘法表……)

    目录 控制流程语句 用户身份认证 百分制转换等级制 英寸与厘米的转换 输入三条边长,计算三角形的周长和面积 计算1~100相加 猜数字游戏 电脑生成1-10之间的数字 九九乘法表 打印三角形嵌套for ...

  9. HDFS权限管理、用户身份认证和数据访问授权、UGO权限管理、umask权限掩码、UGO权限相关命令、Web页面修改UGO权限

    HDFS权限管理 1.1 总览概述 作为分布式文件系统,HDFS也集成了一套兼容POSIX的权限管理系统.客户端在进行每次文件操时,系统会从用户身份认证和数据访问授权两个环节进行验证: 客户端的操作请 ...

  10. Shiro第二篇【介绍Shiro、认证流程、自定义realm、自定义realm支持md5】

    什么是Shiro shiro是apache的一个开源框架,是一个权限管理的框架,实现 用户认证.用户授权. spring中有spring security (原名Acegi),是一个权限框架,它和sp ...

最新文章

  1. windows mobile设置插移动卡没反应_ipad pro外接移动硬盘ipados
  2. 引用次数在 15000 次以上的都是什么神仙论文?
  3. PCIe配置空间和PCI设备中的寄存器
  4. Java数据结构和算法:数组、单链表、双链表
  5. scipy 笔记:solve_triangular
  6. 操作系统 --- [笔记]功能、组成
  7. distutils.errors.DistutilsError: Could not find suitable distribution for Requirement.parse(‘setupto
  8. python import如何使用_python之import引用
  9. 五月数据库技术通讯丨Oracle 12c因新特性引发异常Library Cache Lock等待
  10. docker container
  11. MySQL 5.7原生JSON格式支持
  12. Golang 入门系列(十三)用Beego开发web应用
  13. SQL恢复挂起的原因和措施解析
  14. 网络通信协议层的七个部分
  15. credential provider filter注意
  16. 如何搭建一个站内搜索引擎(二) 第2章 概述
  17. 通信领域的专有名词释义
  18. 手机迅雷下载的文件在哪里
  19. 自动透视校正为四边形对象
  20. 大数定理详解(转载)

热门文章

  1. C/C++[1928, ]日期处理
  2. 算法:Regular Expression Matching(正则表达式匹配)
  3. 深度图像配准_巧解图像处理经典难题之图像配准
  4. 剑指offer之扑克牌的顺子
  5. python的符号lt和gt怎么输入_lt;lt;Python基础教程gt;gt;学习笔记 | 第04章 | 字典...
  6. BAT[阿里、百度、腾讯]等互联网公司数据结构面试题(一) python分析实现
  7. 再谈KMP/BM算法(I)
  8. 使用JSPanda扫描客户端原型污染漏洞
  9. php 前后端 不对称加密,AES前后端对称加密
  10. idea 编译时提示找不到符号