一篇文章理解Shiro
简介
是一款主流的 Java 安全框架,不依赖任何容器,可以运行在 Java SE
和 Java EE 项目中,它的主要作用是对访问系统的用户进行身份认证、
授权、会话管理、加密等操作。
Shiro 就是用来解决安全管理的系统化框架。
这里是shiro官网,详情看官网操作
安装下载
点击download进行下载
点击这里
前往这个github官网进行下载
核心组件讲解
shiro主要由这三个部分组成
用户、角色、权限
会给角色赋予权限,给用户赋予角色
1、UsernamePasswordToken,Shiro 用来封装用户登录信息,使用用户的登录信息来创建令牌 Token。
2、SecurityManager,Shiro 的核心部分,负责安全认证和授权。
3、Suject,Shiro 的一个抽象概念,包含了用户信息。
4、Realm,开发者自定义的模块,根据项目的需求,验证和授权的逻辑全部写在 Realm 中。
5、AuthenticationInfo,用户的角色信息集合,认证时使用。
6、AuthorzationInfo,角色的权限信息集合,授权时使用。
7、DefaultWebSecurityManager,安全管理器,开发者自定义的 Realm 需要注入到 DefaultWebSecurityManager 进行管理才能生效。
8、ShiroFilterFactoryBean,过滤器工厂,Shiro 的基本运行机制是开发者定制规则,Shiro 去执行,具体的执行操作就是由 ShiroFilterFactoryBean 创建的一个个 Filter 对象来完成。
代码演示
创建项目
搜索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.7.8</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>shiroDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>shiroDemo</name>
<description>shiroDemo</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.11.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
自定义过滤器
* 数据库建表语句
-- MySQL dump 10.13 Distrib 8.0.19, for Win64 (x86_64)
--
-- Host: 127.0.0.1 Database: test
-- ------------------------------------------------------
-- Server version 8.0.19
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `account`
--
DROP TABLE IF EXISTS `account`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `account` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(20) DEFAULT NULL,
`password` varchar(20) DEFAULT NULL,
`perms` varchar(20) DEFAULT NULL,
`role` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `account`
--
LOCK TABLES `account` WRITE;
/*!40000 ALTER TABLE `account` DISABLE KEYS */;
INSERT INTO `account` VALUES (1,'zs','123123','',''),(2,'ls','123123','manage','administrator'),(3,'ww','123123','','');
/*!40000 ALTER TABLE `account` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2020-06-04 14:10:15
创建好了之后的样子
perms是职位,role是角色
- 配置数据库参数
application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/projectdatabase?serverTimezone=UTC
thymeleaf:
prefix: classpath:/templates/
suffix: .html
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
创建Account,AccountService与测试
- 创建Account类
package com.example.shirodemo.Bean;
import lombok.Data;
@Data
public class Account {
private Integer id;
private String username;
private String password;
private String perms;
private String role;
}
创建AccountMapper
完整代码
package com.example.shirodemo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.shirodemo.Bean.Account;
import org.springframework.stereotype.Repository;
@Repository
public interface AccountMapper extends BaseMapper<Account> {
}
编写测试类
对于启动类设置进行扫包
测试成功
- 编写AccountService接口
- 创建测试类,测试AccountService
完整代码
package com.example.shirodemo.service;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
void findByUsername() {
System.out.println(accountService.findByUsername("ls"));
}
}
测试结果
编写AccountRealm
项目结构
- AccountRealm
package com.example.shirodemo.realm;
import com.example.shirodemo.Bean.Account;
import com.example.shirodemo.service.AccountService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
public class AccountRealm extends AuthorizingRealm {
@Autowired
private AccountService accountService;
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取当前登录的用户信息
Subject subject = SecurityUtils.getSubject();
Account account = (Account) subject.getPrincipal();
//设置角色
Set<String> roles = new HashSet<>();
roles.add(account.getRole());
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
//设置权限
info.addStringPermission(account.getPerms());
return info;
}
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 首先是外部的页面把用户的账号和密码这些信息传递到这个token里面
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
Account account = accountService.findByUsername(token.getUsername());
// 先判断一下这个account对象是否为空
if (account != null){
// 如果不为空 再验证账号和密码
// 这个SimpleAuthenticationInfo会自行帮忙验证的
return new SimpleAuthenticationInfo(account, account.getPassword(), getName());
}
// 如果为空直接返回null
return null;
}
}
编写shiroConfig
文件结构
shiroConfig
package com.example.shirodemo.config;
import com.example.shirodemo.realm.AccountRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class shiroConfig{
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);
return factoryBean;
}
@Bean
public DefaultWebSecurityManager securityManager(@Qualifier("accountRealm") AccountRealm accoutRealm){
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(accoutRealm);
return manager;
}
@Bean
public AccountRealm accountRealm(){
return new AccountRealm();
}
}
编写演示认证与授权过滤器
重新设置一下数据库,设置成这个样子
编写认证和授权规则:
认证过滤器
anon:无需认证。
authc:必须认证。
authcBasic:需要通过 HTTPBasic 认证。
user:不一定通过认证,只要曾经被 Shiro 记录即可,比如:记住我。
授权过滤器
perms:必须拥有某个权限才能访问。
role:必须拥有某个角色才能访问。
port:请求的端口必须是指定值才可以。
rest:请求必须基于 RESTful,POST、PUT、GET、DELETE。
ssl:必须是安全的 URL 请求,协议 HTTPS。
创建 3 个页面,main.html、manage.html、administrator.html
访问权限如下:
1、必须登录才能访问 main.html
2、当前用户必须拥有 manage 授权才能访问 manage.html
3、当前用户必须拥有 administrator 角色才能访问 administrator.html
shiroConfig
package com.example.shirodemo.config;
import com.example.shirodemo.realm.AccountRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
@Configuration
public class shiroConfig{
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);
// 权限设置
Map<String, String> map = new HashMap<>();
map.put("/main", "authc"); // 设置进入main.html必须认证
map.put("/manage", "perms[manage]"); // 设置进入manage.html需要manage权限
map.put("/administrator", "roles[administrator]"); // 设置只有administator角色才可以进入administator.html
factoryBean.setFilterChainDefinitionMap(map);
return factoryBean;
}
@Bean
public DefaultWebSecurityManager securityManager(@Qualifier("accountRealm") AccountRealm accoutRealm){
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(accoutRealm);
return manager;
}
@Bean
public AccountRealm accountRealm(){
return new AccountRealm();
}
}
配置指向器
完整代码
package com.southwind.controller;
import com.southwind.entity.Account;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class AccountController {
@GetMapping("/{url}")
public String redirect(@PathVariable("url") String url){
return url;
}
@PostMapping("/login")
public String login(String username, String password, Model model){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
try {
subject.login(token);
Account account = (Account) subject.getPrincipal();
subject.getSession().setAttribute("account",account);
return "index";
} catch (UnknownAccountException e) {
e.printStackTrace(); // 用户名不存在
model.addAttribute("msg","用户名错误!");
return "login";
} catch (IncorrectCredentialsException e){ // 用户密码错误
model.addAttribute("msg","密码错误!");
e.printStackTrace();
return "login";
}
}
// 设置未授权的页面情况
@GetMapping("/unauth")
@ResponseBody
public String unauth(){
return "未授权,无法访问!";
}
// 退出页面
@GetMapping("/logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "login";
}
}
添加网页进行演示
对于index.html进行设置,shiro:hasRole设置,可以实现当这个用户登录之后才会显示标签中的内容
index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="shortcut icon" href="#"/>
</head>
<body>
<h1>index</h1>
<div th:if="${session.account != null}">
<span th:text="${session.account.username}+'欢迎回来!'"></span><a href="/logout">退出</a>
</div>
<a href="/main">main</a> <br/>
<div shiro:hasPermission="manage">
<a href="manage">manage</a> <br/>
</div>
<div shiro:hasRole="administrator">
<a href="/administrator">administrator</a>
</div>
</body>
</html>
main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="shortcut icon" href="#"/>
</head>
<body>
<h1>main</h1>
</body>
</html>
manage.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="shortcut icon" href="#"/>
</head>
<body>
<h1>manage</h1>
</body>
</html>
administator.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="shortcut icon" href="#"/>
</head>
<body>
<h1>administator</h1>
</body>
</html>
运行结果,检查页面情况
首先默认进入的页面是index.html
在这个默认的index.html页面中,除了这个main以外就没有啥了,点击main就可以进行登录了
- 登录演示
如果登录账号错误,那么显示用户名错误
如果密码错误显示,密码错误
当登录成功之后
对着数据库来看,不同用户权限的显示的不一样
最后就是这个退出如何编写了
代码如下
@GetMapping("/logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "login";
}