声明:本文是博主观看bilibili上Java涛哥视频学习的Shiro框架所整理笔记,学完思路清晰,原文代码大致相同,原视频链接附上,方便以后学习查看。 本文项目源码加数据库
https://download.csdn.net/download/qq_45299673/69324339
? Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。
一、权限管理
不同身份的人进入系统所完成的操作不同,如下图,当一个系统包含左侧所有菜单功能时,要求不同人员登录展示不同权限下的菜单,这时候就需要学习权限管理,看完本文就可轻松掌握。
1.1 基于主页的权限管理
设计多种页面,当不同权限的用户访问时,跳转不同的用户,如下图,用户1登录跳转到index1.html,用户2登录跳转到index2.html,缺陷比较大且繁琐,不建议使用。
缺点:适用于用户级别较少的,不能动态分配权限
1.2 基于用户和权限的权限管理
利用用户表、用户权限表、权限表三张表,每个用户在中间表分配权限 实现权限的动态分配,但是每个用户对应一个用户权限,10个人就得10个,数据比较冗余,这样就衍生了下面的五张表
1.3 基于角色的访问控制(RBAC)
创建用户表、角色表、权限表和两张中间表用户角色表、角色权限表五张表,每次新加用户时只需要授予角色就可获得相对的权限。 给同类人员的其中一个人授予权限可以再加一张用户权限表,直接授予权限。
二、Shiro
2.1 认证和授权
认证:对用户的身份进行检查(登录验证)
授权:对用户的权限进行检查(是否有对应的操作权限)
2.2 常见的安全框架
shiro:Apache Shrio是功能强大并且易用的Java安全框架
Spring Security:基于Spring的安全框架,依赖Spring
OAuth2:第三方授权框架(QQ、微信)
自定义安全认证中心
2.3 shrio核心功能
Anthentication 认证,验证用户是否有相应的身份 登录认证 Authorization 授权,权限验证,通过认证的用户检查是否有权限或者角色 Session Management 会话管理,用户在认证成功后创建会话,在没有退出之前,当前用户的所有信息都会保存在这个会话中 Cryptograsphy 加密,对敏感信息加密
支持的特性: 1.Web Support Shrio提供了过滤器,可以通过过滤器拦截web请求来处理web应用的访问控制 2.Caching 缓存支持,shiro可以缓存用户信息以及用户的角色权限信息,可以提高执行效率 3.concurrency shiro支持多线程应用 4.Run As 允许一个用户以另一种身份去访问 5.Remeber Me 记住密码 6.Testing 提供测试支持 Shiro是一个安全框架,不提供用户、权限的维护,用户的权限管理需要我们去设计
2.4 Shiro核心组件
Subject、Security Manager、Realms
- Subject:表示待认证和授权的用户
- Security Manager:Shiro框架的核心,Shiro就是通过Security Manager来进行内部实例的管理,并通过它来提供安全管理的各种服务
- Realm:相当于shiro进行认证和授权的数据源
2.5 shiro的使用
1.添加maven依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
2.创建shiro的配置文件
在resource下创建ini后缀文件
[users]
zhangsan=123456,seller
lisi=123123,ckmgr
admin=admin,admin
[roles]
admin=*
seller=order-add,order-del,order-list
ckmgr=ck-add,ck-del,ck-list
3.使用测试
public class ShiroTest {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
System.out.println("请输入账号:");
String username = sc.next();
System.out.println("请输入密码:");
String password = sc.next();
DefaultSecurityManager securityManager=new DefaultSecurityManager();
IniRealm iniRealm=new IniRealm("classpath:shiro.ini");
securityManager.setRealm(iniRealm);
SecurityUtils.setSecurityManager(securityManager);
Subject subject=SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken(username,password);
boolean flag=false;
try{
subject.login(token);
flag=true;
}catch (IncorrectCredentialsException e){
flag=false;
}
System.out.println(flag?"登录成功":"登录失败");
System.out.println(subject.hasRole("seller"));
System.out.println(subject.isPermitted("order-del"));
}
}
2.6 shrio认证流程
三、SpringBoot整合Shiro
3.1 导入依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
</dependencies>
application.yml配置
spring:
datasource:
druid:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
initial-size: 1
min-idle: 1
max-active: 20
mybatis:
type-aliases-package: com.zx.entity
mapper-locations: classpath:com/zx/dao/*Mapper.xml
3.2 Shiro配置
SpringBoot没有提供对Shiro的自动配置,因为Spring家族有自己的安全框架
import org.apache.shiro.realm.text.IniRealm;
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;
@Configuration
public class ShiroConfig {
@Bean
public IniRealm getIniRealm(){
IniRealm iniRealm=new IniRealm("classpath:shiro.ini");
return iniRealm;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(IniRealm iniRealm){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
securityManager.setRealm(iniRealm);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean filter=new ShiroFilterFactoryBean();
filter.setSecurityManager(defaultWebSecurityManager);
Map<String,String> filterMap=new HashMap<>();
filterMap.put("/","anon");
filterMap.put("/login.html","anon");
filterMap.put("/register.html","anon");
filterMap.put("/user/login","anon");
filterMap.put("/user/register","anon");
filterMap.put("/static/**","anon");
filterMap.put("/**","authc");
filter.setFilterChainDefinitionMap(filterMap);
filter.setLoginUrl("/login.html");
filter.setUnauthorizedUrl("/login.html");
return filter;
}
}
3.3 测试
- UserServiceImpl.java
@Service
public class UserServiceImpl {
public void checkLogin(String username,String Password) throws Exception{
Subject subject= SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken(username,Password);
subject.login(token);
}
}
- UserController.java
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
UserServiceImpl userService;
@RequestMapping("/login")
public String login(String username,String password){
try {
userService.checkLogin(username,password);
return "index";
} catch (Exception e) {
System.out.println("密码错误");
return "login";
}
}
}
- login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
login页面
<form action="user/login" method="get">
账号:<input type="text" name="username">
密码:<input type="text" name="password">
<input type="submit" value="登录">
</form>
</body>
</html>
-
通过shiro.ini定义正确的登录密码 [user]
admin=admin
密码正确跳转到index.html 密码错误跳转到login.html
3.4 内置JDBCRealm作为数据源
如果使用JdbcRealm,则必须提供JdbcRealm所需要的表结构(权限设计)
JdbcRealm规定的表结构
表中的字段必须保持一致,可以加字段
-
用户信息表: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)
);
-
角色信息表: user_roles create table user_roles(
id int primary key auto_increment,
username varchar(60) not null,
role_name varchar(100) not null
);
-
权限信息表:roles_permissions create table roles_permissions(
id int primary key auto_increment,
role_name varchar(100) not null,
permission varchar(100) not null
);
从图中可以看出用户所对应的权限
步骤
创建SpringBoot整合Mybatis 添加shiro依赖
配置Shiro
@Configuration
public class ShiroConfig {
@Bean
public JdbcRealm getJDBCRealm(DataSource dataSource){
JdbcRealm jdbcRealm=new JdbcRealm();
jdbcRealm.setDataSource(dataSource);
jdbcRealm.setPermissionsLookupEnabled(true);
return jdbcRealm;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(JdbcRealm jdbcRealm){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
securityManager.setRealm(jdbcRealm);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean filter=new ShiroFilterFactoryBean();
filter.setSecurityManager(defaultWebSecurityManager);
Map<String,String> filterMap=new HashMap<>();
filterMap.put("/","anon");
filterMap.put("/login.html","anon");
filterMap.put("/register.html","anon");
filterMap.put("/user/login","anon");
filterMap.put("/user/register","anon");
filterMap.put("/static/**","anon");
filterMap.put("/**","authc");
filter.setFilterChainDefinitionMap(filterMap);
filter.setLoginUrl("/login.html");
filter.setUnauthorizedUrl("/login.html");
return filter;
}
}
测试结果同上
3.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();
}
}
<html xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
</html>
常用标签
<shiro:guest>标签,登录后不显示,游客状态下显示
<shiro:user>标签 ,登录后显示
<shiro:principal/> 显示登录用户的用户名
<shiro:hasRole name="#">##</shiro:hasRole> 显示用户角色
<shiro:hasPermission name="#">##</shiro:hasPermission> 显示用户权限
具体用法如下: 数据库添加如下信息
“zhangsan”是管理员admin角色,拥有所有*权限
“lisi”是仓库管理员cmanager角色,拥有sys:c:*权限
“wangwu”是行政人员xmanager角色,拥有sys:X:*权限
实现当不同用户登录后显示不同的权限菜单
<!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页面
<shiro:guest>
欢迎游客访问,去<a href="login.html">登录</a>
</shiro:guest>
<shiro:user>
<p>已登录用户访问</p>
<p>用户:<shiro:principal/>欢迎你!</p>
<p>当前用户为<shiro:hasRole name="admin">超级管理员</shiro:hasRole>
<shiro:hasRole name="xmanager">行政人员</shiro:hasRole>
<shiro:hasRole name="cmanager">仓管人员</shiro:hasRole></p>
</shiro:user>
仓库管理
<ul>
<shiro:hasPermission name="sys:c:add"><li>入库</li></shiro:hasPermission>
<shiro:hasPermission name="sys:c:del"><li>出库</li></shiro:hasPermission>
<shiro:hasPermission name="sys:c:update"><li>修改</li></shiro:hasPermission>
<shiro:hasPermission name="sys:c:list"><li>查询</li></shiro:hasPermission>
</ul>
订单管理
<ul>
<shiro:hasPermission name="sys:x:add"><li>添加订单</li></shiro:hasPermission>
<shiro:hasPermission name="sys:x:del"><li>删除订单</li></shiro:hasPermission>
<shiro:hasPermission name="sys:x:update"><li>修改订单</li></shiro:hasPermission>
<shiro:hasPermission name="sys:x:list"><li>查询查询</li></shiro:hasPermission>
</ul>
</body>
</html>
当游客身份登录 当admin管理员登录 当仓管人员登录 当行政人员登录
四、自定义Realm实现权限管理
当要使用自己定义的数据结构表,就需要告诉Realm,更改为自己设计的数据源
4.1 数据库设计
SQL语句
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `tb_permissions`;
CREATE TABLE `tb_permissions` (
`permission_id` int NOT NULL AUTO_INCREMENT,
`permission_code` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`permission_name` varchar(60) DEFAULT NULL,
PRIMARY KEY (`permission_id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `tb_permissions` VALUES ('1', 'sys:c:save', '入库');
INSERT INTO `tb_permissions` VALUES ('2', 'sys:c:delete', '出库');
INSERT INTO `tb_permissions` VALUES ('3', 'sys:c:update', '修改');
INSERT INTO `tb_permissions` VALUES ('4', 'sys:c:find', '查询');
INSERT INTO `tb_permissions` VALUES ('5', 'sys:x:save', '新增订单');
INSERT INTO `tb_permissions` VALUES ('6', 'sys:x:delete', '删除订单');
INSERT INTO `tb_permissions` VALUES ('7', 'sys:x:update', '修改订单');
INSERT INTO `tb_permissions` VALUES ('8', 'sys:x:find', '查询订单');
INSERT INTO `tb_permissions` VALUES ('9', 'sys:k:save', '新增客户');
INSERT INTO `tb_permissions` VALUES ('10', 'sys:k:delete', '删除客户');
INSERT INTO `tb_permissions` VALUES ('11', 'sys:k:update', '修改客户');
INSERT INTO `tb_permissions` VALUES ('12', 'sys:k:find', '查询客户');
DROP TABLE IF EXISTS `tb_roles`;
CREATE TABLE `tb_roles` (
`role_id` int NOT NULL AUTO_INCREMENT,
`role_name` varchar(60) NOT NULL,
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `tb_roles` VALUES ('1', 'admin');
INSERT INTO `tb_roles` VALUES ('2', 'cmanager');
INSERT INTO `tb_roles` VALUES ('3', 'xmanager');
INSERT INTO `tb_roles` VALUES ('4', 'kmanager');
INSERT INTO `tb_roles` VALUES ('5', 'zmanager');
DROP TABLE IF EXISTS `tb_roles_permission`;
CREATE TABLE `tb_roles_permission` (
`rid` int NOT NULL,
`pid` int NOT NULL,
KEY `FK_pid` (`pid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `tb_roles_permission` VALUES ('2', '1');
INSERT INTO `tb_roles_permission` VALUES ('2', '2');
INSERT INTO `tb_roles_permission` VALUES ('2', '3');
INSERT INTO `tb_roles_permission` VALUES ('2', '4');
INSERT INTO `tb_roles_permission` VALUES ('3', '5');
INSERT INTO `tb_roles_permission` VALUES ('3', '6');
INSERT INTO `tb_roles_permission` VALUES ('3', '7');
INSERT INTO `tb_roles_permission` VALUES ('3', '8');
INSERT INTO `tb_roles_permission` VALUES ('3', '9');
INSERT INTO `tb_roles_permission` VALUES ('3', '10');
INSERT INTO `tb_roles_permission` VALUES ('3', '11');
INSERT INTO `tb_roles_permission` VALUES ('3', '12');
INSERT INTO `tb_roles_permission` VALUES ('3', '4');
INSERT INTO `tb_roles_permission` VALUES ('4', '11');
INSERT INTO `tb_roles_permission` VALUES ('4', '12');
INSERT INTO `tb_roles_permission` VALUES ('5', '4');
INSERT INTO `tb_roles_permission` VALUES ('5', '8');
INSERT INTO `tb_roles_permission` VALUES ('5', '12');
DROP TABLE IF EXISTS `tb_users`;
CREATE TABLE `tb_users` (
`user_id` int NOT NULL AUTO_INCREMENT,
`username` varchar(20) NOT NULL,
`password` varchar(20) NOT NULL,
`password_salt` varchar(60) DEFAULT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `tb_users` VALUES ('1', 'zhangsan', '123456', null);
INSERT INTO `tb_users` VALUES ('2', 'lisi', '123456', null);
INSERT INTO `tb_users` VALUES ('3', 'wangwu', '123456', null);
INSERT INTO `tb_users` VALUES ('4', 'zhaoliu', '123456', null);
INSERT INTO `tb_users` VALUES ('5', 'chenqi', '123456', null);
DROP TABLE IF EXISTS `tb_users_roles`;
CREATE TABLE `tb_users_roles` (
`uid` int NOT NULL,
`rid` int NOT NULL,
KEY `FK_role` (`rid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO `tb_users_roles` VALUES ('1', '1');
INSERT INTO `tb_users_roles` VALUES ('2', '2');
INSERT INTO `tb_users_roles` VALUES ('3', '3');
INSERT INTO `tb_users_roles` VALUES ('4', '4');
INSERT INTO `tb_users_roles` VALUES ('5', '5');
INSERT INTO `tb_users_roles` VALUES ('1', '2');
INSERT INTO `tb_users_roles` VALUES ('1', '3');
INSERT INTO `tb_users_roles` VALUES ('1', '4');
INSERT INTO `tb_users_roles` VALUES ('1', '5');
从表中就可以查询出lisi的角色为cmanager,权限有[入库,出库,修改,查询]
一个人可以关联多个角色,权限取多个角色的并集
4.2 创建DAO层
@Mapper
public interface UserDao {
User getUserByUsername(String username);
}
@Mapper
public interface RoleDao {
Set<String> getRoleNamesByUsername(String username);
}
@Mapper
public interface PermissionDao {
Set<String> getPermissionByUsername(String username);
}
编写Mapper映射
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.zx.shiro2.dao.UserDao">
<resultMap id="userMap" type="User">
<id column="user_id" property="userId"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="password_salt" property="passwordSalt"/>
</resultMap>
<select id="getUserByUsername" resultMap="userMap">
select * from tb_users where username=#{username}
</select>
</mapper>
RoleMapper.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.zx.shiro2.dao.RoleDao">
<select id="getRoleNamesByUsername" resultSets="java.util.Set" resultType="String">
select role_name from tb_users inner join tb_users_roles on user_id=uid inner join tb_roles
on rid=role_id where username=#{username}
</select>
</mapper>
PermissionMapper.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.zx.shiro2.dao.PermissionDao">
<select id="getPermissionByUsername" resultSets="java.util.Set" resultType="String">
select permission_code from tb_users inner join tb_users_roles on user_id=uid inner join tb_roles on tb_users_roles.rid=role_id inner join tb_roles_permission on role_id=tb_roles_permission.rid inner join tb_permissions on pid=permission_id where username=#{username}
</select>
</mapper>
4.3 application.yml
spring:
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
initial-size: 1
min-idle: 1
max-active: 20
mybatis:
type-aliases-package: com/zx/shiro2/beans
mapper-locations: classpath:mappers/*.xml
4.4 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.6.1</version>
<relativePath/>
</parent>
<groupId>com.zx</groupId>
<artifactId>shiro2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>shiro2</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-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</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>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<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>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
4.5 自定义Realm配置类
要使用自己设计的权限数据库,这个时候的Realm就要继承AuthorizingRealm类使它成为一个Realm类。
在config配置包里创建MyRealm.java
public class MyRealm extends AuthorizingRealm {
@Resource
private UserDao userDao;
@Resource
private RoleDao roleDao;
@Resource
private PermissionDao permissionDao;
@Override
public String getName() {
return "myRealm";
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String username = (String) principalCollection.iterator().next();
Set<String> roles = roleDao.getRoleNamesByUsername(username);
Set<String> permissions = permissionDao.getPermissionByUsername(username);
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
info.setRoles(roles);
info.setStringPermissions(permissions);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
User user = userDao.getUserByUsername(username);
AuthenticationInfo info=new SimpleAuthenticationInfo(username,user.getPassword(),getName());
return info;
}
}
4.6 shiro配置
ShiroConfig.java
@Configuration
public class ShiroConfig {
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
@Bean
public MyRealm getMyRealm(){
MyRealm myRealm=new MyRealm();
return myRealm;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean filter=new ShiroFilterFactoryBean();
filter.setSecurityManager(defaultWebSecurityManager);
Map<String,String> filterMap=new HashMap<>();
filterMap.put("/","anon");
filterMap.put("/login.html","anon");
filterMap.put("/register.html","anon");
filterMap.put("/user/login","anon");
filterMap.put("/user/register","anon");
filterMap.put("/static/**","anon");
filterMap.put("/**","authc");
filter.setFilterChainDefinitionMap(filterMap);
filter.setLoginUrl("/login.html");
filter.setUnauthorizedUrl("/login.html");
return filter;
}
}
4.7 shiro业务层
@Service
public class UserServiceImpl {
public void checkLogin(String username,String Password){
Subject subject= SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken(username,Password);
subject.login(token);
}
}
4.8 Controller层
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
UserServiceImpl userService;
@RequestMapping("/login")
public String login(String username,String password){
try {
userService.checkLogin(username,password);
return "index";
} catch (Exception e) {
System.out.println("密码错误");
return "login";
}
}
}
4.9 整合layUI
导入layui包,官网复制后台框架代码
<!DOCTYPE html>
<html xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>Shiro管理系统</title>
<link rel="stylesheet" href="/layui/css/layui.css">
</head>
<body>
<div class="layui-layout layui-layout-admin">
<div class="layui-header">
<div class="layui-logo layui-hide-xs layui-bg-black">菜单显示</div>
<ul class="layui-nav layui-layout-left">
<li class="layui-nav-item layui-show-xs-inline-block layui-hide-sm" lay-header-event="menuLeft">
<i class="layui-icon layui-icon-spread-left"></i>
</li>
<li class="layui-nav-item layui-hide-xs"><a href="">nav 1</a></li>
<li class="layui-nav-item layui-hide-xs"><a href="">nav 2</a></li>
<li class="layui-nav-item layui-hide-xs"><a href="">nav 3</a></li>
<li class="layui-nav-item">
<a href="javascript:;">nav groups</a>
<dl class="layui-nav-child">
<dd><a href="">menu 11</a></dd>
<dd><a href="">menu 22</a></dd>
<dd><a href="">menu 33</a></dd>
</dl>
</li>
</ul>
<ul class="layui-nav layui-layout-right">
<li class="layui-nav-item layui-hide layui-show-md-inline-block">
<a href="javascript:;">
<img src="//tva1.sinaimg.cn/crop.0.0.118.118.180/5db11ff4gw1e77d3nqrv8j203b03cweg.jpg" class="layui-nav-img">
<shiro:principal/>
</a>
<dl class="layui-nav-child">
<dd><a href="">Your Profile</a></dd>
<dd><a href="">Settings</a></dd>
<dd><a href="">Sign out</a></dd>
</dl>
</li>
<li class="layui-nav-item" lay-header-event="menuRight" lay-unselect>
<a href="javascript:;">
<i class="layui-icon layui-icon-more-vertical"></i>
</a>
</li>
</ul>
</div>
<div class="layui-side layui-bg-black">
<div class="layui-side-scroll">
<ul class="layui-nav layui-nav-tree" lay-filter="test">
<li class="layui-nav-item layui-nav-itemed">
<a class="" href="javascript:;">仓库管理</a>
<dl class="layui-nav-child">
<shiro:hasPermission name="sys:c:save"><dd><a href="javascript:;">入库</a></dd></shiro:hasPermission>
<shiro:hasPermission name="sys:c:delete"><dd><a href="javascript:;">出库</a></dd></shiro:hasPermission>
<shiro:hasPermission name="sys:c:update"><dd><a href="javascript:;">修改</a></dd></shiro:hasPermission>
<shiro:hasPermission name="sys:c:find"><dd><a href="javascript:;">查询</a></dd></shiro:hasPermission>
</dl>
</li>
<li class="layui-nav-item layui-nav-itemed">
<a class="" href="javascript:;">订单管理</a>
<dl class="layui-nav-child">
<shiro:hasPermission name="sys:x:save"><dd><a href="javascript:;">新增订单</a></dd></shiro:hasPermission>
<shiro:hasPermission name="sys:x:delete"><dd><a href="javascript:;">删除订单</a></dd></shiro:hasPermission>
<shiro:hasPermission name="sys:x:update"><dd><a href="javascript:;">修改订单</a></dd></shiro:hasPermission>
<shiro:hasPermission name="sys:x:find"><dd><a href="javascript:;">查询订单</a></dd></shiro:hasPermission>
</dl>
</li>
<li class="layui-nav-item layui-nav-itemed">
<a class="" href="javascript:;">客户管理</a>
<dl class="layui-nav-child">
<shiro:hasPermission name="sys:k:save"><dd><a href="javascript:;">新增客户</a></dd></shiro:hasPermission>
<shiro:hasPermission name="sys:k:delete"><dd><a href="javascript:;">删除客户</a></dd></shiro:hasPermission>
<shiro:hasPermission name="sys:k:update"><dd><a href="javascript:;">修改客户</a></dd></shiro:hasPermission>
<shiro:hasPermission name="sys:k:find"><dd><a href="javascript:;">查询客户</a></dd></shiro:hasPermission>
</dl>
</li>
</ul>
</div>
</div>
<div class="layui-body">
<div style="padding: 15px;">内容主体区域。记得修改 layui.css 和 js 的路径</div>
</div>
<div class="layui-footer">
底部固定区域
</div>
</div>
<script src="/layui/layui.js"></script>
<script>
layui.use(['element', 'layer', 'util'], function(){
var element = layui.element
,layer = layui.layer
,util = layui.util
,$ = layui.$;
util.event('lay-header-event', {
menuLeft: function(othis){
layer.msg('展开左侧菜单的操作', {icon: 0});
}
,menuRight: function(){
layer.open({
type: 1
,content: '<div style="padding: 15px;">处理右侧面板的操作</div>'
,area: ['260px', '100%']
,offset: 'rt'
,anim: 5
,shadeClose: true
});
}
});
});
</script>
</body>
</html>
实现不同权限的管理员登录,展示的菜单不同
zhangsan 超级管理员 显示全部菜单 wangwu xmanager管理员,对仓库管理只有查看的权限
layui实现点击左边菜单,内容右边显示
五、加密
用户密码在存储到数据库之前根据一定的加密规则加密为密文。加密规则可以自定义,在项目开发中通常使用BASE64和MD5编码方式进行加密
? BASE64:可以反编码的编码方式。
? MD5:不可逆的编码方式(网站上常见的MD5解密,只不过是列出常见密码的密文进行查询,密码一旦复杂就解密不出来了,为了防止暴力穷举破解,可以加盐加密)
Shiro提供了加密功能,对输入的密码进行加密后再认证
5.1 Shiro加密
```java
@RequestMapping("/register")
public String register(String username,String password){
//注册时对密码进行加密
Md5Hash md5Hash=new Md5Hash(password);
//加盐加密
int salt=new Random().nextInt(90000)+10000; //10000-99999
Md5Hash md5Hash1=new Md5Hash(password,salt+"");
//加盐加密+Hash次数
Md5Hash md5Hash2=new Md5Hash(password,salt+"",1);
//TODO 此处省略存储到数据库代码
return "login";
}
```
5.2 密码认证
在ShiroConfig中配置加密规则
修改上文的ShiroConfig.java,设置加密规则认证时就生效,没有设置加密规则,默认明文校验
.
.
.
@Bean
public HashedCredentialsMatcher getHashedCredentialsMatcher(){
HashedCredentialsMatcher matcher=new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("md5");
matcher.setHashIterations(1);
return matcher;
}
@Bean
public MyRealm getMyRealm(HashedCredentialsMatcher matcher){
MyRealm myRealm=new MyRealm();
myRealm.setCredentialsMatcher(matcher);
return myRealm;
}
.
.
.
可以正常登录,如果密码加盐了,Realm在返回认证信息时需要返回盐数据,在自定义Realm中修改
ByteSource.Util.bytes(user.getPasswordSalt()),
六、Shiro的退出登录
- 在Shiro过滤器中进行配置,配置logout对应的路径
- 在页面的退出按钮上添加路径/logout
当点击退出登录按钮时,shiro会拦截logout的路径调用subject.logout()登出。
七、授权
? 一般的授权方式有两种:一、HTML授权,即用户登录只显示自己权限的菜单,没有权限的菜单不显示。二、过滤器授权,对所有用户显示所有菜单功能,当用户点击菜单后再验证是否有此权限,无权限提示权限不足。
7.1 HTML授权
<shiro:hasPermission name="sys:c:save"><dd><a href="/c_add.html" target="mainFrame">入库</a></dd></shiro:hasPermission>
7.2 过滤器授权
7.3 注解授权
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator autoProxyCreator=new DefaultAdvisorAutoProxyCreator();
autoProxyCreator.setProxyTargetClass(true);
return autoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultWebSecurityManager defaultWebSecurityManager){
AuthorizationAttributeSourceAdvisor advisor=new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(defaultWebSecurityManager);
return advisor;
}
@RequiresPermissions("sys:k:find")
@RequestMapping("/list")
public String list(){
return "list";
}
当无权人员访问时会抛出AuthorizationException异常,可以配置全局异常类。
@ControllerAdvice
public class GlobalException {
@ExceptionHandler
public String doException(Exception e){
if(e instanceof AuthorizationException){
return "error";
}
return null;
}
}
7.4 手动授权
在代码中进行权限校验,一般写在业务层
Subject subject=SecurityUtils.getSubject();
if(subject.isPermitted("sys:k:find")){
}else{
}
|