Shiro
1.Shiro的介绍及其特点
1.1 Shiro能到底能做些什么呢?
- 验证用户身份
- 用户访问权限控制,比如:1、判断用户是否分配了一定的安全角色。2、判断用户是否被授予完成某个操作的权限
- 在非 Web 或 EJB 容器的环境下可以任意使用Session API
- 可以响应认证、访问控制,或者 Session 生命周期中发生的事件
- 可将一个或以上用户安全数据源数据组合成一个复合的用户 “view”(视图)
- 支持单点登录(SSO)功能
- 支持提供“Remember Me”服务,获取用户关联信息而无需登录
1.2 Shiro特点:
- 易于使用——易用性是项目的最终目标。应用程序安全非常令人困惑和沮丧,被认为是“不可避免的灾难”。如果你让它简化到新手都可以使用它,它就将不再是一种痛苦了。
- 全面——没有其他安全框架的宽度范围可以同Apache Shiro一样,它可以成为你的“一站式”为您的安全需求提供保障。
- 灵活——Apache Shiro可以在任何应用程序环境中工作。虽然在网络工作、EJB和IoC环境中可能并不需要它。但Shiro的授权也没有任何规范,甚至没有许多依赖关系。
- Web支持——Apache Shiro拥有令人兴奋的web应用程序支持,允许您基于应用程序的url创建灵活的安全策略和网络协议(例如REST),同时还提供一组JSP库控制页面输出。
- 低耦合——Shiro干净的API和设计模式使它容易与许多其他框架和应用程序集成。你会看到Shiro无缝地集成Spring这样的框架, 以及Grails, Wicket, Tapestry, Mule, Apache Camel, Vaadin…等。
- 被广泛支持——Apache Shiro是Apache软件基金会的一部分。项目开发和用户组都有友好的网民愿意帮助。这样的商业公司如果需要Katasoft还提供专业的支持和服务。
2.Shiro特性和架构
2.1Apache Shiro Features 特性
Authentication(认证), Authorization(授权), Session Management(会话管理), Cryptography(加密)被 Shiro 框架的开发团队称之为应用安全的四大基石。
- Authentication(认证):用户身份识别,通常被称为用户“登录”
- Authorization(授权):访问控制。比如某个用户是否具有某个操作的使用权限。
- Session Management(会话管理):特定于用户的会话管理,甚至在非web 或 EJB 应用程序。
- Cryptography(加密):在对数据源使用加密算法加密的同时,保证易于使用。
还有其他的功能来支持和加强这些不同应用环境下安全领域的关注点。特别是对以下的功能支持:
- Web支持:Shiro的Web支持API有助于保护Web应用程序。
- 缓存:缓存是Apache Shiro API中的第一级,以确保安全操作保持快速和高效。
- 并发性:Apache Shiro支持具有并发功能的多线程应用程序。
- 测试:存在测试支持,可帮助您编写单元测试和集成测试,并确保代码按预期得到保障。
- “运行方式”:允许用户承担另一个用户的身份(如果允许)的功能,有时在管理方案中很有用。
- “记住我”:记住用户在会话中的身份,所以用户只需要强制登录即可。
2.2Shiro架构
- Subject:当前用户,Subject 可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它–当前和软件交互的任何事件。
- SecurityManager:管理所有Subject,SecurityManager 是 Shiro 架构的核心,配合内部安全组件共同组成安全伞。
- Realms:用于进行权限信息的验证,我们自己实现。Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization)
3.SpringBoot整合Shiro环境搭建
新建项目springboot_shiro, 勾选如下,及其删除不需要的文件
导入jar包
<!-- Subject 用户-->
<!-- SecurityManager 管理所有用户-->
<!-- Realm 连接数据-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.1</version>
</dependency>
templates下创建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>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
</body>
</html>
创建cotorller,并创建MyController
package com.xiaozhi.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpSession;
@Controller
public class MyController {
@RequestMapping({"/","index"})
public String toIndex(Model model){
model.addAttribute("msg","hello,shiro");
return "index";
}
}
- Subject 用户
- SecurityManager 管理所有用户
- Realm 连接数据
shiro配置文件ShiroConfig
package com.xiaozhi.config;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
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.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//3
//shiroFileterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(defaultWebSecurityManager);
return bean;
}
//2
//DafaultWebSecurityManager
@Bean(name = "SecurityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//关联UserRealm
securityManager.setRealm(userRealm);
return securityManager;
}
//1
//创建 realm 对象,需要定义类
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
//整合shiroDialect:用来整合shiro thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
}
自定义的UserRealm
package com.xiaozhi.config;
import com.xiaozhi.mapper.UserMapper;
import com.xiaozhi.pojo.User;
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.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
//自定义的 UserRealm extends AuthorizingRealm
public class UserRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=》授权doGetAuthorizationInfo");
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了=》授权doGetAuthenticationInfo");
// return null;
}
}
登录拦截
在templates下加入login.html并创建文件夹user,并创建update.html、add.html
login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>登录页</h1>
<hr>
<form method="post" action="/login" >
<p th:text="${msg}"></p>
<label><input name="username" type="text" placeholder="用户名"></label>
<label><input name="password" type="text" placeholder="密码"></label>
<label><input type="submit" value="登录"></label>
</form>
<hr>
</body>
</html>
update.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2 th:text="${msg}"></h2>
</body>
</html>
add.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2 th:text="${msg}"></h2>
</body>
</html>
controller下加入
package com.xiaozhi.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpSession;
/**
* @Author Administrator
* @Date 2021/6/18 22:06
* @Version 1.0
*/
@Controller
public class MyController {
@RequestMapping({"/","index"})
public String toIndex(Model model){
model.addAttribute("msg","hello,shiro");
return "index";
}
@RequestMapping({"/user/add"})
public String add(Model model){
model.addAttribute("msg","hello,add");
return "user/add";
}
@RequestMapping({"/user/update"})
public String update(Model model){
model.addAttribute("msg","hello,update");
return "user/update";
}
@RequestMapping("/tologin")
public String tologin(){
return "login";
}
}
anon:无需认证就可以访问
authc:必须认证了才能访问
user:必须拥有记住我 功能才能访问
perms:拥有对某个资源的权限才能访问
role:拥有某个角色权限才能访问
//3
//shiroFileterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置过滤器
/*
anon:无需认证就可以访问
authc:必须认证了才能访问
user:必须拥有记住我 功能才能访问
perms:拥有对某个资源的权限才能访问
role:拥有某个角色权限才能访问
*/
//设置拦截
Map<String, String> filemap = new LinkedHashMap<>();
//授权,正常的情况下,没有授权会跳转到未授权的页面
filemap.put("/user/*","authc");
//设置没有权限登录跳转tologin
bean.setLoginUrl("/tologin");
shiro整合mybatis
jar包
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
application.yaml
spring:
messages:
basename: i18n.login
datasource:
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: root123
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
# filters: stat,wall,log4j
filters: stat,wall
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mybatis:
type-aliases-package: com.xiaozhi.pojo
mapper-locations: classpath:mybatis/mapper/*.xml
sql
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`name` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`pwd` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`perms` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 110 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, ‘小智‘, ‘123456‘, ‘user:update‘);
INSERT INTO `user` VALUES (2, ‘admin‘, ‘123456‘, ‘user:add‘);
INSERT INTO `user` VALUES (3, ‘root‘, ‘123456‘, NULL);
INSERT INTO `user` VALUES (4, ‘小欧‘, ‘123645‘, NULL);
INSERT INTO `user` VALUES (5, ‘张三‘, ‘123546‘, NULL);
INSERT INTO `user` VALUES (6, ‘张姐‘, ‘123456‘, NULL);
INSERT INTO `user` VALUES (7, ‘张小名‘, ‘1234562‘, NULL);
INSERT INTO `user` VALUES (8, ‘ouxinxin‘, ‘22222‘, NULL);
INSERT INTO `user` VALUES (9, ‘xiaoxss‘, ‘3333‘, NULL);
SET FOREIGN_KEY_CHECKS = 1;
结构图
User
package com.xiaozhi.pojo;
/**
* @Author Administrator
* @Date 2021/6/20 8:23
* @Version 1.0
*/
public class User {
private int id;
private String name;
private String pwd;
private String perms;
public User() {
}
public User(int id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name=‘" + name + ‘\‘‘ +
", pwd=‘" + pwd + ‘\‘‘ +
‘}‘;
}
public String getPerms() {
return perms;
}
public void setPerms(String perms) {
this.perms = perms;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
UserMapper
package com.xiaozhi.mapper;
import com.xiaozhi.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Map;
/**
* @Author Administrator
* @Date 2021/6/10 20:33
* @Version 1.0
*/
//表示这是一个mybatis的mapper类
@Mapper
@Repository
public interface UserMapper {
//用户登录
// List<Employee> login(@Param("name") String name,@Param("password") String password);
User login(@Param("name") String name);
}
UserService
package com.xiaozhi.service;
import com.xiaozhi.pojo.User;
import org.apache.ibatis.annotations.Param;
/**
* @Author Administrator
* @Date 2021/6/20 8:26
* @Version 1.0
*/
public interface UserService {
User login(String name);
}
UserServiceImpl
package com.xiaozhi.service;
import com.xiaozhi.mapper.UserMapper;
import com.xiaozhi.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @Author Administrator
* @Date 2021/6/20 8:26
* @Version 1.0
*/
@Service("UserService")
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User login(String name) {
return userMapper.login(name);
}
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xiaozhi.mapper.UserMapper">
<select id="login" resultType="User">
select * from mybatis.user where name=#{name}
</select>
</mapper>
controller中加入
@RequestMapping("/login")
public String login(String username,String password,Model model){
// 获取当前用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
// 执行登录方法
subject.login(token);
return "index";
}catch (UnknownAccountException e){
model.addAttribute("msg","用户名错误");
return "login";
}catch (IncorrectCredentialsException e){
//密码错误
model.addAttribute("msg","密码错误");
return "login";
}
}
UserRealm中doGetAuthenticationInfo方法加入
@Autowired
private UserMapper userMapper;
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了=》授权doGetAuthenticationInfo");
UsernamePasswordToken usernameToken = (UsernamePasswordToken) authenticationToken;
User user = userMapper.login(usernameToken.getUsername());
if (user==null){
return null;
}
Subject currentSubject = SecurityUtils.getSubject();
Session session = currentSubject.getSession();
session.setAttribute("loginUser",user);
//密码认证,shiro做
return new SimpleAuthenticationInfo(user,user.getPwd(),"");
}UsernamePasswordToken usernameToken = (UsernamePasswordToken) authenticationToken;
User user = userMapper.login(usernameToken.getUsername());
if (user==null){
return null;
}
Subject currentSubject = SecurityUtils.getSubject();
Session session = currentSubject.getSession();
session.setAttribute("loginUser",user);
//密码认证,shiro做
return new SimpleAuthenticationInfo(user,user.getPwd(),"");
shiro实现授权
//3
//shiroFileterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置过滤器
/*
anon:无需认证就可以访问
authc:必须认证了才能访问
user:必须拥有记住我 功能才能访问
perms:拥有对某个资源的权限才能访问
role:拥有某个角色权限才能访问
*/
//设置拦截
Map<String, String> filemap = new LinkedHashMap<>();
//授权,正常的情况下,没有授权会跳转到未授权的页面
filemap.put("/user/add","perms[user:add]");
filemap.put("/user/update","perms[user:update]");
filemap.put("/user/*","authc");
//设置没有权限登录跳转
bean.setLoginUrl("/tologin");
//未授权跳转页面
bean.setUnauthorizedUrl("/noauth");
bean.setFilterChainDefinitionMap(filemap);
return bean;
}
UserRealm下的doGetAuthorizationInfo方法
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=》授权doGetAuthorizationInfo");
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//拿到当前登录的这个对象
Subject subject = SecurityUtils.getSubject();
//拿到user对象
User currentUser = (User) subject.getPrincipal();
simpleAuthorizationInfo.addStringPermission(currentUser.getPerms());
return simpleAuthorizationInfo;
}System.out.println("执行了=》授权doGetAuthorizationInfo");
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//拿到当前登录的这个对象
Subject subject = SecurityUtils.getSubject();
//拿到user对象
User currentUser = (User) subject.getPrincipal();
simpleAuthorizationInfo.addStringPermission(currentUser.getPerms());
return simpleAuthorizationInfo;
shiro整合thymeleaf
<!-- https://mvnrepository.com/artifact/com.github.theborakompanioni/thymeleaf-extras-shiro -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
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>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
<!--从session中判断值-->
<div th:if="${session.loginUser==null}">
<a th:href="@{/tologin}">登录</a>
</div>
<div th:if="${session.loginUser!=null}">
<a th:href="@{/loginout}">注销</a>
</div>
<p th:text="${msg}"></p>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}" >add</a>
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">update</a>
</div>
</body>
</html>
4.springboot整合jdbcreaml
4.1JdbcRealm介绍
|
4.2如果使用JdbcRealm,则必须提供JdbcRealm所需的表结构(权限设计)
4.3JdbcRealm规定的表结构
- 用户信息表: users
create table users(
id int primary key auto_increment,
username varchar(60) not null unique,
password varchar(20) not null,
password_salt varchar(20)
);
insert into users(username,password) values(‘zhangsan‘,‘123456‘);
insert into users(username,password) values(‘lisi‘,‘123456‘);
insert into users(username,password) values(‘wangwu‘,‘123456‘);
insert into users(username,password) values(‘zhaoliu‘,‘123456‘);
insert into users(username,password) values(‘chenqi‘,‘123456‘);
- 角色信息表: user_roles
create table user_roles(
id int primary key auto_increment,
username varchar(60) not null,
role_name varchar(100) not null
);
-- admin系统管理员
-- cmanager 库管人员
-- xmanager 销售人员
-- kmanager 客服人员
-- zmanager 行政人员
insert into user_roles(username,role_name) values(‘zhangsan‘,‘admin‘);
insert into user_roles(username,role_name) values(‘lisi‘,‘cmanager‘);
insert into user_roles(username,role_name) values(‘wangwu‘,‘xmanager‘);
insert into user_roles(username,role_name) values(‘zhaoliu‘,‘kmanager‘);
insert into user_roles(username,role_name) values(‘chenqi‘,‘zmanager‘);
- 权限信息表:roles_permissions
create table roles_permissions(
id int primary key auto_increment,
role_name varchar(100) not null,
permission varchar(100) not null
);
-- 权限 sys:c:save sys:c:delete...
-- 管理员具备所有权限
insert into roles_permissions(role_name,permission) values("admin","*");
-- 库管人员
insert into roles_permissions(role_name,permission) values("cmanager","sys:c:save");
insert into roles_permissions(role_name,permission) values("cmanager","sys:c:delete");
insert into roles_permissions(role_name,permission) values("cmanager","sys:c:update");
insert into roles_permissions(role_name,permission) values("cmanager","sys:c:find");
-- 销售人员
insert into roles_permissions(role_name,permission) values("xmanager","sys:c:find");
insert into roles_permissions(role_name,permission) values("xmanager","sys:x:save");
insert into roles_permissions(role_name,permission) values("xmanager","sys:x:delete");
insert into roles_permissions(role_name,permission) values("xmanager","sys:x:update");
insert into roles_permissions(role_name,permission) values("xmanager","sys:x:find");
insert into roles_permissions(role_name,permission) values("xmanager","sys:k:save");
insert into roles_permissions(role_name,permission) values("xmanager","sys:k:delete");
insert into roles_permissions(role_name,permission) values("xmanager","sys:k:update");
insert into roles_permissions(role_name,permission) values("xmanager","sys:k:find");
-- 客服人员
insert into roles_permissions(role_name,permission) values("kmanager","sys:k:find");
insert into roles_permissions(role_name,permission) values("kmanager","sys:k:update");
-- 新增人员
insert into roles_permissions(role_name,permission) values("zmanager","sys:*:find");
4.4整合shiro
-
创建SpringBoot应用
-
整合Druid和MyBatis
-
整合shiro
- 添加依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency>
- 配置shiro
-
@Configuration public class ShiroConfig { /*** * * JdbcRealm * **/ @Bean public JdbcRealm getJdbcRealm(DataSource dataSource){ JdbcRealm jdbcRealm = new JdbcRealm(); //JdbcRealm会自行从数据库查询用户及权限数据(数据库的表结构要符合JdbcRealm的规范) jdbcRealm.setDataSource(dataSource); //JdbcRealm默认开启认证功能,需要手动开启授权功能 jdbcRealm.setPermissionsLookupEnabled(true); return jdbcRealm; } /*** * * JdbcRealm * **/ @Bean public DefaultWebSecurityManager getdefaultWebSecurityManager(JdbcRealm jdbcRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //securityManager要完成校验,需要realm securityManager.setRealm(jdbcRealm); return securityManager; } @Bean public ShiroFilterFactoryBean shiroFilterFactory(DefaultWebSecurityManager securityManager){ ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean(); //过滤器就是shiro就行权限校验的核心,进行认证和授权是需要securityManager filter.setSecurityManager(securityManager); //设置shiro的拦截规则 // anon:无需认证即可访问,游客身份。 // authc:必须认证(登录)才能访问。 // authcBasic:需要通过 httpBasic 认证。 // user:rememberMe的用户访问 // perms:对应的权限可访问 // role:对应的角色可访问 HashMap<String, String> filterMap = new HashMap<>(); filterMap.put("/","anon"); filterMap.put("/login.html","anon"); filterMap.put("/index.html","anon"); filterMap.put("/welcome.html","anon"); filterMap.put("/regist.html","anon"); filterMap.put("/user/login","anon"); filterMap.put("/user/regist","anon"); filterMap.put("/static/**","anon"); filterMap.put("/x-admin/**","anon"); filterMap.put("/**","authc"); filter.setFilterChainDefinitionMap(filterMap); //设置登录页 filter.setLoginUrl("/"); //设置没有权限页面 filter.setUnauthorizedUrl("/login.html"); return filter; } }
5.Shiro的标签使用
当用户认证进入到主页面之后,需要显示用户信息及当前用户的权限信息;Shiro就提供了一套标签用于在页面来进行权限数据的呈现
-
Shiro提供了可供JSP使用的标签以及Thymeleaf中标签
-
JSP页面中引用:
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
-
Thymeleaf模版中引用:
- 在pom.xml文件中导入thymeleaf模版对shiro标签支持的依赖
<dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
- 在ShiroConfig中配置Shiro的
@Configuration public class ShiroConfig { @Bean public ShiroDialect getShiroDialect(){ return new ShiroDialect(); } //... }
- Thymeleaf模版中引入shiro的命名空间
<html xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> ... </html>
-
常用标签
-
guest,判断用户是否是游客身份,如果是游客身份则显示此标签内容
<shiro:guest> 欢迎游客访问,<a href="login.html">登录</a> </shiro:guest>
-
user,判断用户是否是认证身份,如果是认证身份则显示此标签内容
-
principal,获取当前登录用户名
<shiro:user> 用户[<shiro:principal/>]欢迎您! </shiro:user>
-
notAuthenticated/authenticated
-
hasRole
-
hasPermission
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
index
<hr/>
<shiro:guest>
欢迎游客访问,<a href="login.html">登录</a>
</shiro:guest>
<shiro:user>
用户[<shiro:principal/>]欢迎您!
当前用户为<shiro:hasRole name="admin">超级管理员</shiro:hasRole>
<shiro:hasRole name="cmanager">仓管人员</shiro:hasRole>
<shiro:hasRole name="xmanager">销售人员</shiro:hasRole>
<shiro:hasRole name="kmanager">客服人员</shiro:hasRole>
<shiro:hasRole name="zmanager">行政人员</shiro:hasRole>
</shiro:user>
<hr/>
仓库管理
<ul>
<shiro:hasPermission name="sys:c:save"><li><a href="#">入库</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:c:delete"><li><a href="#">出库</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:c:update"><li><a href="#">修改</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:c:find"><li><a href="#">查询</a></li></shiro:hasPermission>
</ul>
订单管理
<ul>
<shiro:hasPermission name="sys:x:save"><li><a href="#">添加订单</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:x:delete"><li><a href="#">删除订单</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:x:update"><li><a href="#">修改订单</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:x:find"><li><a href="#">查询订单</a></li></shiro:hasPermission>
</ul>
客户管理
<ul>
<shiro:hasPermission name="sys:k:save"><li><a href="#">添加客户</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:k:delete"><li><a href="#">删除客户</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:k:update"><li><a href="#">修改客户</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:k:find"><li><a href="#">查询客户</a></li></shiro:hasPermission>
</ul>
</body>
</html>
6.SpringBoot整合Shiro完成权限管理案例—自定义Realm
使用JdbcRealm可以完成用户权限管理,但是我们必须提供JdbcRealm规定的数据表结构;如果在我们的项目开发中 ,这个JdbcRealm规定的数据表结构不能满足开发需求,该如何处理呢?
- 自定义数据库表结构
- 自定义Realm实现认证和授权
|
6.1 数据库设计
- RBAC基于角色的访问控制
-- 用户信息表
create table tb_users(
user_id int primary key auto_increment,
username varchar(60) not null unique,
password varchar(20) not null,
password_salt varchar(60)
);
insert into tb_users(username,password) values(‘zhangsan‘,‘123456‘);
insert into tb_users(username,password) values(‘lisi‘,‘123456‘);
insert into tb_users(username,password) values(‘wangwu‘,‘123456‘);
insert into tb_users(username,password) values(‘zhaoliu‘,‘123456‘);
insert into tb_users(username,password) values(‘chenqi‘,‘123456‘);
-- 角色信息表
create table tb_roles(
role_id int primary key auto_increment,
role_name varchar(60) not null
);
insert into tb_roles(role_name) values(‘admin‘);
insert into tb_roles(role_name) values(‘cmanager‘); -- 仓管
insert into tb_roles(role_name) values(‘xmanager‘); -- 销售
insert into tb_roles(role_name) values(‘kmanager‘); -- 客服
insert into tb_roles(role_name) values(‘zmanager‘); -- 行政
-- 权限信息表
create table tb_permissions(
permission_id int primary key auto_increment, -- 1
permission_code varchar(60) not null, -- sys:c:find
permission_name varchar(60) -- 仓库查询
);
insert into tb_permissions(permission_code,permission_name) values(‘sys:c:save‘,‘入库‘);
insert into tb_permissions(permission_code,permission_name) values(‘sys:c:delete‘,‘出库‘);
insert into tb_permissions(permission_code,permission_name) values(‘sys:c:update‘,‘修改‘);
insert into tb_permissions(permission_code,permission_name) values(‘sys:c:find‘,‘查询‘);
insert into tb_permissions(permission_code,permission_name) values(‘sys:x:save‘,‘新增订单‘);
insert into tb_permissions(permission_code,permission_name) values(‘sys:x:delete‘,‘删除订单‘);
insert into tb_permissions(permission_code,permission_name) values(‘sys:x:update‘,‘修改订单‘);
insert into tb_permissions(permission_code,permission_name) values(‘sys:x:find‘,‘查询订单‘);
insert into tb_permissions(permission_code,permission_name) values(‘sys:k:save‘,‘新增客户‘);
insert into tb_permissions(permission_code,permission_name) values(‘sys:k:delete‘,‘删除客户‘);
insert into tb_permissions(permission_code,permission_name) values(‘sys:k:update‘,‘修改客户‘);
insert into tb_permissions(permission_code,permission_name) values(‘sys:k:find‘,‘查询客户‘);
-- 用户角色表
create table tb_urs(
uid int not null,
rid int not null
-- primary key(uid,rid),
-- constraint FK_user foreign key(uid) references tb_users(user_id),
-- constraint FK_role foreign key(rid) references tb_roles(role_id)
);
insert into tb_urs(uid,rid) values(1,1);
insert into tb_urs(uid,rid) values(1,2);
insert into tb_urs(uid,rid) values(1,3);
insert into tb_urs(uid,rid) values(1,4);
insert into tb_urs(uid,rid) values(1,5);
insert into tb_urs(uid,rid) values(2,2);
insert into tb_urs(uid,rid) values(3,3);
insert into tb_urs(uid,rid) values(4,4);
insert into tb_urs(uid,rid) values(5,5);
-- 角色权限表
create table tb_rps(
rid int not null,
pid int not null
);
-- 给仓管角色分配权限
insert into tb_rps(rid,pid) values(2,1);
insert into tb_rps(rid,pid) values(2,2);
insert into tb_rps(rid,pid) values(2,3);
insert into tb_rps(rid,pid) values(2,4);
-- 给销售角色分配权限
insert into tb_rps(rid,pid) values(3,4);
insert into tb_rps(rid,pid) values(3,5);
insert into tb_rps(rid,pid) values(3,6);
insert into tb_rps(rid,pid) values(3,7);
insert into tb_rps(rid,pid) values(3,8);
insert into tb_rps(rid,pid) values(3,9);
insert into tb_rps(rid,pid) values(3,10);
insert into tb_rps(rid,pid) values(3,11);
insert into tb_rps(rid,pid) values(3,12);
-- 给客服角色分配权限
insert into tb_rps(rid,pid) values(4,11);
insert into tb_rps(rid,pid) values(4,12);
-- 给行政角色分配权限
insert into tb_rps(rid,pid) values(5,4);
insert into tb_rps(rid,pid) values(5,8);
insert into tb_rps(rid,pid) values(5,12);
6.2DAO实现
-
Shiro进行认证需要用户信息:
- 根据用户名查询用户信息
-
Shiro进行授权管理需要当前用户的角色和权限
- 根据用户名查询当前用户的角色列表(3张表连接查询)
- 根据用户名查询当前用户的权限列表(5张表连接查询)
6.21创建SpringBoot项目,整合MyBatis
6.22根据用户名查询用户信息
- 创建BeanBean
@Data
public class User {
private Integer userId;
private String userName;
private String userPwd;
private String pwdSalt;
}
- 创建DAO
public interface UserDAO {
public User queryUserByUsername(String username);
}
-
映射配置
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.xiaozhi.dao.UserDao"> <select id="queryUserByUsername" resultType="user"> select * from shiro1.tb_users where username=#{username} </select> </mapper>
6.23根据用户名查询角色名列表
-
创建DAO
public interface RoleDAO { public Set<String> queryRoleNamesByUsername(String username); }
-
映射配置
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.xiaozhi.dao.RoleDao"> <select id="queryRoleNamesByUsername" resultSets="java.util.Set" resultType="string"> select role_name from shiro1.tb_users inner join shiro1.tb_urs on tb_users.user_id = tb_urs.uid inner join shiro1.tb_roles on tb_urs.rid = tb_roles.role_id where tb_users.username=#{username} </select> </mapper>
6.24根据用户名查询权限列表
-
创建DAO
public interface PermissionDAO { public Set<String> queryPermissionsByUsername(String username); }
-
映射配置
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.xiaozhi.dao.PermissionDao"> <select id="queryPermissionsByUsername" resultSets="java.util.Set" resultType="string"> select tb_permissions.permission_code from shiro1.tb_users inner join shiro1.tb_urs on tb_users.user_id=tb_urs.uid inner join shiro1.tb_roles on tb_urs.rid=tb_roles.role_id inner join shiro1.tb_rps on tb_roles.role_id=tb_rps.rid inner join shiro1.tb_permissions on tb_rps.pid=tb_permissions.permission_id where tb_users.username=#{username} </select> </mapper>
6.3整合Shiro
-
导入依赖
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
-
配置Shiro-基于Java配置方式
package com.xiaozhi.config; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import com.xiaozhi.realm.MyRealm; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; /** * @Author Administrator * @Date 2021/8/15 23:06 * @Version 1.0 */ @Configuration public class ShiroConfig { //配置spring-shrio-thymeleaf的使用 @Bean public ShiroDialect getShiroDialect(){ return new ShiroDialect(); } //自定义Realm @Bean public MyRealm getMyRealm(){ return new MyRealm(); } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myRealm); return securityManager; } @Bean public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager){ ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean(); //过滤器就是shiro就行权限校验的核心,进行认证和授权是需要SecurityManager的 filter.setSecurityManager(securityManager); Map<String,String> filterMap = new HashMap<>(); filterMap.put("/","anon"); filterMap.put("/index.html","anon"); filterMap.put("/login.html","anon"); filterMap.put("/regist.html","anon"); filterMap.put("/user/login","anon"); filterMap.put("/user/regist","anon"); filterMap.put("/layui/**","anon"); filterMap.put("/**","authc"); filter.setFilterChainDefinitionMap(filterMap); filter.setLoginUrl("/login.html"); //设置未授权访问的页面路径() filter.setUnauthorizedUrl("/login.html"); return filter; } }
-
自定义Realm
/** * 1.创建一个类继承AuthorizingRealm类(实现了Realm接口的类) * 2.重写doGetAuthorizationInfo和doGetAuthenticationInfo方法 * 3.重写getName方法返回当前realm的一个自定义名称 */ public class MyRealm extends AuthorizingRealm { @Resource private UserDAO userDAO; @Resource private RoleDAO roleDAO; @Resource private PermissionDAO permissionDAO; public String getName() { return "myRealm"; } /** * 获取授权数据(将当前用户的角色及权限信息查询出来) */ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //获取用户的用户名 String username = (String) principalCollection.iterator().next(); //根据用户名查询当前用户的角色列表 Set<String> roleNames = roleDAO.queryRoleNamesByUsername(username); //根据用户名查询当前用户的权限列表 Set<String> ps = permissionDAO.queryPermissionsByUsername(username); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setRoles(roleNames); info.setStringPermissions(ps); return info; } /** * 获取认证的安全数据(从数据库查询的用户的正确数据) */ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //参数authenticationToken就是传递的 subject.login(token) // 从token中获取用户名 UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String username = token.getUsername(); //根据用户名,从数据库查询当前用户的安全数据 User user = userDAO.queryUserByUsername(username); AuthenticationInfo info = new SimpleAuthenticationInfo( username, //当前用户用户名 user.getUserPwd(), //从数据库查询出来的安全密码 getName()); return info; } }
7.加密
-
明文-----(加密规则)-----密文
-
加密规则可以自定义,在项目开发中我们通常使用BASE64和MD5编码方式
- BASE64:可反编码的编码方式(对称)
- 明文----密文
- 密文----明文
- MD5: 不可逆的编码方式(非对称)
- 明文----密文
- BASE64:可反编码的编码方式(对称)
-
如果数据库用户的密码存储的密文,Shiro该如何完成验证呢?
-
使用Shiro提供的加密功能,对输入的密码进行加密之后再进行认证。
7.1Shiro使用加密认证
-
配置matcher
@Configuration public class ShiroConfig { //... @Bean public HashedCredentialsMatcher getHashedCredentialsMatcher(){ HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); //matcher就是用来指定加密规则 //加密方式 matcher.setHashAlgorithmName("md5"); //hash次数 matcher.setHashIterations(1); //此处的循环次数要与用户注册是密码加密次数一致 return matcher; } //自定义Realm @Bean public MyRealm getMyRealm( HashedCredentialsMatcher matcher ){ MyRealm myRealm = new MyRealm(); myRealm.setCredentialsMatcher(matcher); return myRealm; } //... }
7.2用户注册密码加密处理
-
registh.html
<form action="/user/regist" method="post"> <p>帐号:<input type="text" name="userName"/></p> <p>密码:<input type="text" name="userPwd"/></p> <p><input type="submit" value="提交注册"/></p> </form>
-
UserController
@Controller @RequestMapping("user") public class UserController { @Resource private UserServiceImpl userService; @RequestMapping("/regist") public String regist(String userName,String userPwd) { System.out.println("------注册"); //注册的时候要对密码进行加密存储 Md5Hash md5Hash = new Md5Hash(userPwd); System.out.println("--->>>"+ md5Hash.toHex()); //加盐加密 int num = new Random().nextInt(90000)+10000; //10000—99999 String salt = num+""; Md5Hash md5Hash2 = new Md5Hash(userPwd,salt); System.out.println("--->>>"+md5Hash2); //加盐加密+多次hash Md5Hash md5Hash3 = new Md5Hash(userPwd,salt,3); System.out.println("--->>>"+md5Hash3); //SimpleHash hash = new SimpleHash("md5",userPwd,num,3); //将用户信息保存到数据库时,保存加密后的密码,如果生成的随机盐,盐也要保存 return "login"; } }
7.3密码进行了加盐处理,则Realm在返回认证数据时需要返回盐
-
自定义Realm
public class MyRealm extends AuthorizingRealm { @Resource private UserDAO userDAO; @Resource private RoleDAO roleDAO; @Resource private PermissionDAO permissionDAO; public String getName() { return "myRealm"; } /** * 获取认证的安全数据(从数据库查询的用户的正确数据) */ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //参数authenticationToken就是传递的 subject.login(token) // 从token中获取用户名 UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String username = token.getUsername(); //根据用户名,从数据库查询当前用户的安全数据 User user = userDAO.queryUserByUsername(username); // AuthenticationInfo info = new SimpleAuthenticationInfo( // username, //当前用户用户名 // user.getUserPwd(), //从数据库查询出来的安全密码 // getName()); //如果数据库中用户的密码是加了盐的 AuthenticationInfo info = new SimpleAuthenticationInfo( username, //当前用户用户名 user.getUserPwd(), //从数据库查询出来的安全密码 ByteSource.Util.bytes(user.getPwdSalt()), getName()); return info; } }
8.退出登录和授权验证
8.1退出登录
- 在Shiro过滤器中进行配置,配置logut对应的路径
//定义退出页面
filterMap.put("/loginout","logout");
- 在页面的“退出”按钮上,跳转到logout对应的url
<a href="loginout">退出</a>
8.2授权验证
用户登录成功之后,要进行响应的操作就需要有对应的权限;在进行操作之前对权限进行检查—授权
权限控制通常有两类做法:
- 不同身份的用户登录,我们现在不同的操作菜单(没有权限的菜单不现实)
- 对所有用户显示所有菜单,当用户点击菜单以后再验证当前用户是否有此权限,如果没有则提示权限不足
8.21HTML授权
- 在菜单页面只显示当前用户拥有权限操作的菜单
- shiro标签
<shiro:hasPermission name="sys:c:save">
<dd><a href="javascript:;">入库</a></dd>
</shiro:hasPermission>
8.22过滤器授权
-
在shiro过滤器中对请求的url进行权限设置
filterMap.put("/c_add.html","perms[sys:c:save]"); //设置未授权访问的页面路径—当权限不足时显示此页面 filter.setUnauthorizedUrl("/lesspermission.html");
8.23注解授权
-
配置Spring对Shiro注解的支持:ShiroConfig.java
@Bean public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator(); autoProxyCreator.setProxyTargetClass(true); return autoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor( DefaultWebSecurityManager securityManager){ AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; }
-
在请求的控制器添加权限注解
@Controller @RequestMapping("customer") public class CustomerController { @RequestMapping("/list") @ResponseBody //如果没有 sys:k:find 权限,则不允许执行此方法 @RequiresPermissions("sys:k:find") // @RequiresRoles("") public String list(){ System.out.println("----------->查询客户信息"); return "ok"; } }
-
通过全局异常处理,指定权限不足时的页面跳转
@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler public String doException(Exception e){ if(e instanceof AuthorizationException){ return "lesspermission"; } return null; } }
8.24手动授权
Subject subject = SecurityUtils.getSubject();
if(subject.isPermitted("sys:k:find")){
System.out.println("----------->查询客户信息");
return "customer_list";
}else{
return "lesspermission";
}
9.缓存使用和session管理、RememberMe
9.1缓存使用
使用Shiro进行权限管理过程中,每次授权都会访问realm中的doGetAuthorizationInfo方法查询当前用户的角色及权限信息,如果系统的用户量比较大则会对数据库造成比较大的压力 |
---|
Shiro支持缓存以降低对数据库的访问压力(缓存的是授权信息) |
9.11导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
9.12配置缓存策略
-
在resources目录下创建一个xml文件(ehcache.xml)
<?xml version="1.0" encoding="UTF-8"?> <ehcache updateCheck="false" dynamicConfig="false"> <!--diskStore:缓存数据持久化的目录 地址 --> <diskStore path="C:\TEMP" /> <cache name="users" timeToLiveSeconds="300" maxEntriesLocalHeap="1000"/> <defaultCache name="defaultCache" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false" maxElementsOnDisk="100000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"/> <!--缓存淘汰策略:当缓存空间比较紧张时,我们要存储新的数据进来,就必然要删除一些老的数据 LRU 最近最少使用 FIFO 先进先出 LFU 最少使用 --> </ehcache>
-
配置说明
maxElementsInMemory 内存中最大缓存对象数,看着自己的heap大小来设置
eternal:true表示对象永不过期,此时会忽略timeToIdleSeconds和timeToLiveSeconds属性,默认为false
overflowToDisk:true表示当内存缓存的对象数目达到了maxElementsInMemory界限后,
会把溢出的对象写到硬盘缓存中。注意:如果缓存的对象要写入到硬盘中的话,则该对象必须实现了Serializable接口才行。
maxElementsOnDisk:硬盘中最大缓存对象数,若是0表示无穷大。
diskSpoolBufferSizeMB:磁盘缓存区大小,默认为30MB。每个Cache都应该有自己的一个缓存区。
diskPersistent:是否缓存虚拟机重启期数据。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认为120秒。
timeToIdleSeconds: 设定允许对象处于空闲状态的最长时间,以秒为单位。当对象自从最近一次被访问后,如果处于空闲状态的时间超过了timeToIdleSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清空。只有当eternal属性为false,该属性才有效。如果该属性值为0, 则表示对象可以无限期地处于空闲状态。
timeToLiveSeconds:设定对象允许存在于缓存中的最长时间,以秒为单位。当对象自从被存放到缓存中后, 如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期, EHCache将把它从缓存中清除。只有当eternal属性为false,该属性才有效。如果该属性值为0,则表示对象可以无限期地存在于缓存中。timeToLiveSeconds必须大于timeToIdleSeconds属性,才有意义。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
9.13加入缓存管理
ShiroConfig.java
@Bean
public EhCacheManager getEhCacheManager(){
EhCacheManager ehCacheManager = new EhCacheManager();
ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return ehCacheManager;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
securityManager.setCacheManager(getEhCacheManager());
return securityManager;
}
9.2session管理
Shiro进行认证和授权是基于session实现的,Shiro包含了对session的管理
-
如果我们需要对session进行管理
- 自定义session管理器
- 将自定义的session管理器设置给SecurityManager
-
配置自定义SessionManager:ShiroConfig.java
@Bean
public DefaultWebSessionManager getDefaultWebSessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
System.out.println("----------"+sessionManager.getGlobalSessionTimeout()); // 1800000
//配置sessionManager
sessionManager.setGlobalSessionTimeout(5*60*1000);
return sessionManager;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
securityManager.setCacheManager(getEhCacheManager());
securityManager.setSessionManager(getDefaultWebSessionManager());
return securityManager;
}
9.3RememberMe
- 过滤器中设置“记住我”可访问的url
// anon 表示未认证可访问的url
// user 表示记住我可访问的url(已认证也可以访问)
//authc 表示已认证可访问的url
//perms 表示必须具备指定的权限才可访问
//logout 表示指定退出的url
filterMap.put("/","anon");
filterMap.put("/index.html","user");
filterMap.put("/login.html","anon");
filterMap.put("/regist.html","anon");
filterMap.put("/user/login","anon");
filterMap.put("/user/regist","anon");
filterMap.put("/layui/**","anon");
filterMap.put("/**","authc");
filterMap.put("/c_add.html","perms[sys:c:save]");
filterMap.put("/exit","logout");
- ShiroConfig.java中配置基于cookie的rememberMe管理器
@Bean
public CookieRememberMeManager cookieRememberMeManager(){
CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
//cookie必须设置name
SimpleCookie cookie = new SimpleCookie("rememberMe");
cookie.setMaxAge(30*24*60*60);
rememberMeManager.setCookie(cookie);
return rememberMeManager;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
securityManager.setCacheManager(getEhCacheManager());
securityManager.setSessionManager(getDefaultWebSessionManager());
//设置remember管理器
securityManager.setRememberMeManager(cookieRememberMeManager());
return securityManager;
}
9.31登录认证时设置token“记住我”
- 登录页面
<form action="/user/login" method="post">
<p>帐号:<input type="text" name="userName"/></p>
<p>密码:<input type="text" name="userPwd"/></p>
<p>记住我:<input type="checkbox" name="rememberMe"/></p>
<p><input type="submit" value="登录"/></p>
</form>
- 控制器
@Controller
@RequestMapping("user")
public class UserController {
@Resource
private UserServiceImpl userService;
@RequestMapping("login")
public String login(String userName,String userPwd,boolean rememberMe){
try {
userService.checkLogin(userName,userPwd,rememberMe);
System.out.println("------登录成功!");
return "index";
} catch (Exception e) {
System.out.println("------登录失败!");
return "login";
}
}
//...
}
- service
@Service
public class UserServiceImpl {
public void checkLogin(String userName, String userPwd,boolean rememberMe) throws Exception {
//Shiro进行认证 ——入口
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(userName,userPwd);
token.setRememberMe(rememberMe);
subject.login(token);
}
}
10.Shiro多Realm配置
10.1使用场景
- 当shiro进行权限管理,数据来自于不同的数据源时,我们可以给SecurityManager配置多个Realm
10.2 多个Realm的处理方式
10.21 链式处理
- 多个Realm依次进行认证
10.22分支处理
- 根据不同的条件从多个Realm中选择一个进行认证处理
10.3 多Realm配置(链式处理)
-
定义多个Realm
-
UserRealm
public class UserRealm extends AuthorizingRealm { Logger logger = LoggerFactory.getLogger(UserRealm.class); @Override public String getName() { return "UserRealm"; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { logger.info("--------------------------------UserRealm"); //从token中获取username UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String username = token.getUsername(); //根据username从users表中查询用户信息 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,"123456",getName()); return info; } }
-
ManagerRealm
public class ManagerRealm extends AuthorizingRealm { Logger logger = LoggerFactory.getLogger(ManagerRealm.class); @Override public String getName() { return "ManagerRealm"; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { logger.info("--------------------------------ManagerRealm"); //从token中获取username UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String username = token.getUsername(); //根据username从吗managers表中查询用户信息 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,"222222",getName()); return info; } }
-
在ShiroConfig.java中为SecurityManager配置多个Realm
@Configuration public class ShiroConfig { @Bean public UserRealm userRealm(){ UserRealm userRealm = new UserRealm(); return userRealm; } @Bean public ManagerRealm managerRealm(){ ManagerRealm managerRealm = new ManagerRealm(); return managerRealm; } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //securityManager中配置多个realm Collection<Realm> realms = new ArrayList<>(); realms.add(userRealm()); realms.add(managerRealm()); securityManager.setRealms(realms); return securityManager; } //... }
-
-
测试代码:
-
login.html
<form action="user/login" method="post"> <p>帐号:<input type="text" name="userName"/></p> <p>密码:<input type="text" name="userPwd"/></p> <p><input type="radio" name="loginType" value="User"/>普通用户 <input type="radio" name="loginType" value="Manager"/>管理员</p> <p><input type="submit" value="登录"/></p> </form>
-
UserController.java
@Controller @RequestMapping("user") public class UserController { Logger logger = LoggerFactory.getLogger(UserController.class); @RequestMapping("login") public String login(String userName,String userPwd, String loginType){ logger.info("~~~~~~~~~~~~~UserController-login"); try{ UsernamePasswordToken token = new UsernamePasswordToken(userName,userPwd); Subject subject = SecurityUtils.getSubject(); subject.login(token); return "index"; }catch (Exception e){ return "login"; } } }
-
-
-
10.4Shiro认证处理源码分析
|
shiro自带的org.apache.shiro.authc.pam.ModularRealmAuthenticator,其中决定使用的Realm的是doAuthenticate()方法,源代码如下
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
this.assertRealmsConfigured();
Collection<Realm> realms = this.getRealms();
// this.doMultiRealmAuthentication(realms, authenticationToken);中的realms参数就是认证会执行的Realm
return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
}
10.5多个Realm(分支处理)
实现案例:用户不同身份登录执行不同的Realm
-
自定义Realm(UserRealm\ManagerRealm)
- 当在登录页面选择“普通用户”登录,则执行UserRealm的认证
- 当在登录页面选择“管理员”登录,则执行ManagerRealm的认证
-
Realm的声明及配置
-
自定义Token
public class MyToken extends UsernamePasswordToken { private String loginType; public MyToken(String userName,String userPwd, String loginType) { super(userName,userPwd); this.loginType = loginType; } public String getLoginType() { return loginType; } public void setLoginType(String loginType) { this.loginType = loginType; } }
-
自定义认证器
public class MyModularRealmAuthenticator extends ModularRealmAuthenticator { Logger logger = LoggerFactory.getLogger(MyModularRealmAuthenticator.class); @Override protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { logger.info("------------------------------MyModularRealmAuthenticator"); this.assertRealmsConfigured(); Collection<Realm> realms = this.getRealms(); MyToken token = (MyToken) authenticationToken; String loginType = token.getLoginType(); // User logger.info("------------------------------loginType:"+loginType); Collection<Realm> typeRealms = new ArrayList<>(); for(Realm realm:realms){ if(realm.getName().startsWith(loginType)){ //UserRealm typeRealms.add(realm); } } if(typeRealms.size()==1){ return this.doSingleRealmAuthentication((Realm)typeRealms.iterator().next(), authenticationToken); }else{ return this.doMultiRealmAuthentication(typeRealms, authenticationToken); } } }
-
配置自定义认证器
@Configuration public class ShiroConfig { @Bean public UserRealm userRealm(){ UserRealm userRealm = new UserRealm(); return userRealm; } @Bean public ManagerRealm managerRealm(){ ManagerRealm managerRealm = new ManagerRealm(); return managerRealm; } @Bean public MyModularRealmAuthenticator myModularRealmAuthenticator(){ MyModularRealmAuthenticator myModularRealmAuthenticator = new MyModularRealmAuthenticator(); return myModularRealmAuthenticator; } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //配置自定义认证器(放在realms设置之前) securityManager.setAuthenticator(myModularRealmAuthenticator()); //securityManager中配置多个realm Collection<Realm> realms = new ArrayList<>(); realms.add(userRealm()); realms.add(managerRealm()); securityManager.setRealms(realms); return securityManager; } //... }
-
测试: 控制器接受数据进行认证
- login.html
<form action="user/login" method="post"> <p>帐号:<input type="text" name="userName"/></p> <p>密码:<input type="text" name="userPwd"/></p> <p><input type="radio" name="loginType" value="User" checked/>普通用户 <input type="radio" name="loginType" value="Manager"/>管理员</p> <p><input type="submit" value="登录"/></p> </form>
-
UserController.java
@Controller @RequestMapping("user") public class UserController { Logger logger = LoggerFactory.getLogger(UserController.class); @RequestMapping("login") public String login(String userName,String userPwd, String loginType){ logger.info("~~~~~~~~~~~~~UserController-login"); try{ //UsernamePasswordToken token = new UsernamePasswordToken(userName,userPwd); MyToken token = new MyToken(userName,userPwd,loginType); Subject subject = SecurityUtils.getSubject(); subject.login(token); return "index"; }catch (Exception e){ return "login"; } } }