Shiro学习笔记(一)Shiro与Spring Boot的结合(两种方式)
后续篇:
Shiro学习笔记(二)后续内容记录
这篇文章里写了部分shiro功能的例子。
前言
本文是在b站学习Shiro时随笔记下,这个视频相比于网上一些文字教程还是比较详细,算是保姆级的教程。虽然时长较长,但对于初学还是比较推荐。
视频地址:https://www.bilibili.com/video/BV1pa4y1471s
本文旨在记录自己的学习过程,也是我学习笔记的第一篇记录,受我目前师父启发后发了这篇文章。我只是java小白一个,目前在实习,如有错误,欢迎指正。
一、JdbcRealm
1.MyBatis导入
<dependencies>
<!--druid starter-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.20</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
</dependencies>
2.ShiroConfig
@Configuration
public class ShiroConfig {
@Bean
public JdbcRealm getJdbcRealm(DataSource dataSource) {
JdbcRealm jdbcRealm = new JdbcRealm();
//JdbcRealm会自行去数据区查询用户及权限数据(数据库的表结构要符合JdbcRealm的规范)
jdbcRealm.setDataSource(dataSource);
//JdbcRealm默认开启认证功能,需要手动开启授权功能
jdbcRealm.setPermissionsLookupEnabled(true);
return jdbcRealm;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(JdbcRealm jdbcRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//securityManager要完成校验需要realm的支持
securityManager.setRealm(jdbcRealm);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean filter(SecurityManager securityManager) {
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
filterFactoryBean.setSecurityManager(securityManager);
//设置shiro的拦截规则
/*
anon 匿名用户可访问
authc 认证用户可访问
user 使用remenberme的用户可访问
perms 对应权限可访问
role 对应角色可访问
*/
Map<String, String> filterMap = new HashMap<>();
filterMap.put("/","anon");
filterMap.put("/login.html","anon");
filterMap.put("/index.html","anon");
filterMap.put("/regist.html","anon");
filterMap.put("/user/login","anon");
filterMap.put("/user/regist","anon");
filterMap.put("/static/**","anon");
filterMap.put("/**","authc");
filterFactoryBean.setFilterChainDefinitionMap(filterMap);
filterFactoryBean.setLoginUrl("/login.html");
//设置未授权访问的页面路径
filterFactoryBean.setUnauthorizedUrl("/login.html");
return filterFactoryBean;
}
}
3.前端页面标签使用
JSP页面中引用:
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
Thymeleaf模板中引用
(1)在pom.xml文件中导入thymeleaf模板对Shiro的标签支持的依赖
<!--thymeleaf对shiro的支持-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
(2)在ShiroConfig中配置Shiro的方言支持
public class ShiroConfig(){
@Bean
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}
//....
}
(3)Thymeleaf模板中引入Shiro的命名空间
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
</html>
常用的标签
1.shiro:guest
判断用户是否是游客身份,如果是游客身份则显示此标签内容
<shiro:guest>
欢迎游客登录访问,<a href="login.html">登录</a>
</shiro:guest>
2.shiro:user
判断用户是否是认证身份,如果是认证身份则显示此标签内容
与guest标签效果相反
<shiro:user>
已登录用户!
</shiro:user>
3.shiro:principal
取用户的登录信息
<shiro:user>
欢迎<shiro:principal/>!
</shiro:user>
4.shiro:notAuthenticated/shiro:authenticated
与1、2标签的效果相同,认证过程不同
5.shiro:hasRole
判断当前登录的用户是否有指定的角色,有则显示指定内容
<shiro:user>
欢迎【<shiro:principal/>】!
当前用户为
<shiro:hasRole name="admin">超级管理员</shiro:hasRole>
<shiro:hasRole name="cmanager">仓管人员</shiro:hasRole>
<shiro:hasRole name="smanager">销售人员</shiro:hasRole>
<shiro:hasRole name="kmanager">客服人员</shiro:hasRole>
<shiro:hasRole name="zmanager">行政人员</shiro:hasRole>
</shiro:user>
效果图:
6.shiro:hasPermission
判断当前登录的用户是否有指定的权限,有则显示指定内容
仓库管理
<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:s:save"><li><a href="#">添加订单</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:s:delete"><li><a href="#">删除订单</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:s:update"><li><a href="#">修改订单</a></li></shiro:hasPermission>
<shiro:hasPermission name="sys:s: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>
效果图:
二、自定义Realm
1.MyBatis导入
<dependencies>
<!--druid starter-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.20</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
</dependencies>
2.yml文件配置
此配置在JDBCRealm中也需要配置,配置基本相同,上面忘写了。
spring:
datasource:
druid:
url: jdbc:mysql://localhost:3306/shirostudy
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
initial-size: 1
min-idle: 1
max-active: 20
mybatis:
mapper-locations: classpath:mappers/*Mapper.xml
type-aliases-package: com.wxy.shiro4.beans
3.数据库的创建
直接创建就可,无需格式,推荐五张表起步。用户表,角色表,用户-角色表,权限表,角色-权限表。
user
CREATE TABLE `user` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户id',
`user_name` varchar(60) CHARACTER SET utf8 NOT NULL COMMENT '用户名',
`user_pwd` varchar(20) CHARACTER SET utf8 NOT NULL COMMENT '用户密码',
`pwd_salt` varchar(30) CHARACTER SET utf8 DEFAULT NULL COMMENT '加盐',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1 COMMENT='用户表';
role
CREATE TABLE `role` (
`role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色id',
`role_name` varchar(60) CHARACTER SET utf8 NOT NULL COMMENT '角色名',
`role_desc` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT '角色描述',
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1 COMMENT='角色表';
user_role
CREATE TABLE `user_role` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NOT NULL COMMENT '用户id',
`role_id` bigint(20) NOT NULL COMMENT '角色id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=latin1 COMMENT='用户角色表';
permission
CREATE TABLE `permission` (
`per_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '权限id',
`permission` varchar(255) CHARACTER SET utf8 NOT NULL COMMENT '权限名称',
`per_desc` varchar(255) CHARACTER SET utf8 DEFAULT NULL COMMENT '权限描述',
PRIMARY KEY (`per_id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=latin1 COMMENT='权限表';
role_per
CREATE TABLE `role_per` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`role_id` bigint(20) NOT NULL COMMENT '角色id',
`per_id` bigint(20) NOT NULL COMMENT '权限id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=latin1 COMMENT='角色权限表';
4.Dao实现
- Shiro进行需要用户信息
- 根据用户名查询用户信息
- Shiro进行授权管理需要当前用户的角色和权限
- 根据用户名查询当前用户的角色列表
- 根据用户名查询当前用户的权限列表
以上三个查询可以直接连接查询(教程也是连接查询)
但我建议采用分开查询,在代码逻辑中进行连接查询可以提高效率。
这里教学中查询结果为单列角色名与权限名,但是我直接查询出List然后循环使用.get()
方法。
以下为项目结构:
UserDao:
public interface UserDao {
public User queryUserByUserName(String userName) throws Exception;
public Integer queryUserIdByUserName(String userName);
}
RoleDao:
public interface RoleDao {
public Role queryRoleListByRoleId(Integer roleId);
}
UserRoleDao:
public interface UserRoleDao {
public List<Integer> queryRoleIdByUserId(Integer userId);
}
PermissionDao:
public interface PermissionDao {
public Permission queryPermissionByPerId(Integer perId);
}
RolePerDao:
public interface RolePerDao {
public List<Integer> queryPerIdByRoleId(Integer roleId);
}
UserMapper:
<resultMap id="userMap" type="com.wxy.shiro4.beans.User">
<id column="user_id" property="userId"/>
<result column="user_name" property="userName"/>
<result column="user_pwd" property="userPwd"/>
<result column="pwd_salt" property="pwdSalt"/>
</resultMap>
<select id="queryUserByUserName" resultMap="userMap">
SELECT
*
FROM
USER
WHERE
user_name = #{userName}
</select>
<select id="queryUserIdByUserName" resultType="integer">
SELECT
user_id
FROM
USER
WHERE
user_name = #{userName}
</select>
RoleMapper:
<resultMap id="roleMap" type="com.wxy.shiro4.beans.Role">
<id column="role_id" property="roleId"/>
<result column="role_name" property="roleName"/>
<result column="role_desc" property="roleDesc"/>
</resultMap>
<select id="queryRoleListByRoleId" resultMap="roleMap">
SELECT
*
FROM
role
WHERE
role_id = #{roleId}
</select>
UserRoleMapper:
<select id="queryRoleIdByUserId" resultType="integer">
SELECT
role_id
FROM
user_role
WHERE
user_id = #{userId}
</select>
PermissionMapper
<resultMap id="permissionMap" type="com.wxy.shiro4.beans.Permission">
<id column="per_id" property="perId"/>
<result column="permisson" property="permission"/>
<result column="per_desc" property="perDesc"/>
</resultMap>
<select id="queryPermissionByPerId" resultMap="permissionMap">
SELECT
*
FROM
permission
WHERE
per_id = #{perId}
</select>
RolePerMapper
<select id="queryPerIdByRoleId" resultType="integer">
SELECT
per_id
FROM
role_per
WHERE
role_id = #{roleId}
</select>
写完mapper之后,在启动类上加上@MapperScan(basePackages = "com.wxy.shiro4.dao")
注释,找到dao的位置。
5.Shiro整合
导入依赖
shiro-spring
是spring对shiro的支持依赖
thymeleaf-extras-shiro
是thymeleaf对shiro的支持依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
基于java配置Shiro,ShiroConfig类
@Configuration
public class ShiroConfig {
//Shiro的方言支持
@Bean
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}
//自定义Realm
@Bean
public MyRealm getMyRealm() {
MyRealm myRealm = new MyRealm();
return myRealm;
}
//SecurityManager(安全管理器)
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//securityManager要完成校验需要realm的支持
securityManager.setRealm(myRealm);
return securityManager;
}
//过滤器
@Bean
public ShiroFilterFactoryBean filter(SecurityManager securityManager) {
ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
//过滤器是Shiro
filterFactoryBean.setSecurityManager(securityManager);
//设置shiro的拦截规则
/*
anon 匿名用户可访问
authc 认证用户可访问
user 使用remenberme的用户可访问
perms 对应权限可访问
role 对应角色可访问
*/
Map<String, String> filterMap = new HashMap<>();
filterMap.put("/","anon");
filterMap.put("/login.html","anon");
filterMap.put("/index.html","anon");
filterMap.put("/regist.html","anon");
filterMap.put("/user/login","anon");
filterMap.put("/user/regist","anon");
filterMap.put("/static/**","anon");
filterMap.put("/**","authc");
filterFactoryBean.setFilterChainDefinitionMap(filterMap);
filterFactoryBean.setLoginUrl("/login.html");
//设置未授权访问的页面路径
filterFactoryBean.setUnauthorizedUrl("/login.html");
return filterFactoryBean;
}
}
自定义Realm
Realm对外提供合法数据。里面有两个方法,doGetAuthenticationInfo
为认证器提供数据,doGetAuthorizationInfo
为授权器提供数据。
可以自定义多个realm,只是要在ShiroConfig中使用securityManager.setRealms()
来设置多个Realm。
/**
* 1.创建一个类继承AuthorizingRealm才能称为一个Realm(实现了Realm接口的类)
* 2.重写doGetAuthorizationInfo和doGetAuthenticationInfo两个方法
* 3.重写getName()方法,返回当前realm的一个自定义名称
*/
public class MyRealm extends AuthorizingRealm {
@Resource
private UserDao userDao;
@Resource
private RoleDao roleDao;
@Resource
private UserRoleDao userRoleDao;
@Resource
private PermissionDao permissionDao;
@Resource
private RolePerDao rolePerDao;
@Override
public String getName() {
return "myRealm";
}
/**
* 获取授权数据(将当前用户的角色及权限信息查询出来)
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取当前用户的用户名
String username = (String) principalCollection.iterator().next();
//根据用户名查询当前用户的角色列表
Integer userId = userDao.queryUserIdByUserName(username);
List<Integer> roleIds = userRoleDao.queryRoleIdByUserId(userId);
Set<String> roleNames = new HashSet<>();
Set<String> perNames = new HashSet<>();
for (Integer roleId : roleIds) {
Role role = roleDao.queryRoleListByRoleId(roleId);
roleNames.add(role.getRoleName());
List<Integer> perIds = rolePerDao.queryPerIdByRoleId(roleId);
//根据用户名查询当前用户的权限列表
for (Integer perId : perIds) {
Permission permission = permissionDao.queryPermissionByPerId(perId);
perNames.add(permission.getPermission());
}
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(roleNames);
info.setStringPermissions(perNames);
return info;
}
/**
* 获取认证的安全数据(从数据库查询的用户的正确数据)
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//参数authenticationToken就是传递的 subject.login(token)中的token
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//从token中获取用户名
String username = token.getUsername();
//根据用户名从数据库查询当前用户的安全数据
User user = userDao.queryUserByUserName(username);
if (user == null) {
return null;
}
//把查询出来的安全信息放到AuthenticationInfo中
AuthenticationInfo info = new SimpleAuthenticationInfo(
username, //当前用户用户名
user.getUserPwd(), //从数据库查询出来的安全密码
getName() //当前Realm名
);
return info;
}
}
以上经测试可以成功,那么就实现了动态分配权限的效果。配合前端页面,可以实现不同用户登录看到不同页面的效果。
总结
在Spring Boot上使用Shiro其实非常方便,主要就是导入依赖,配置ShiroConfig,然后自定义Realm或者使用JDBCRealm;
使用JdbcRealm需要对数据库有严格要求,而自定义Realm只需要自己设计数据库就好了。
另外,我对于Shiro的理解为:Realm从数据库拿取合法数据,然后将数据提供给认证器和授权器,认证器和授权器拿到数据后返回给前台。
现在由于只是学习阶段,对这方面知识不够深入理解,以上只是我自己在看视频的时候的随笔笔记,其中部分思考都是我自己结合教学得出,如有错误,欢迎指正。
如有侵权,联系删除。