使用springboot + mybatis-plus + thymleaf模板实现快速入门
添加依赖
注意: shiro-spring 和 thymeleaf-extras-shiro
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!-- shiro-thymeleaf 2.0.0-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
</dependency>
</dependencies>
配置文件
application.properties
# 配置数据源
#spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/testshiro?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
# 关闭默认模板引擎缓存
spring.thymeleaf.cache=false
# mybatis-plus 配置
mybatis-plus.mapper-locations=classpath*:mapper/*.xml
server.port=18080
# 配置日志文件
logging.config=classpath:config/logback.xml
logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="./logs" />
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg %n</pattern>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/runtime.log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>
脚本初始化
数据库创建以下几个表(用户表,角色表,用户-角色表,权限表,角色-权限表)
user_info.sql(用户表)
DROP TABLE IF EXISTS `user_info`;
CREATE TABLE `user_info` (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT '' COMMENT '用户名',
`password` varchar(50) DEFAULT NULL COMMENT '登录密码',
`name` varchar(50) DEFAULT NULL COMMENT '用户真实姓名',
`id_card_num` varchar(50) DEFAULT NULL COMMENT '用户身份证号',
`state` char(1) DEFAULT '0' COMMENT '用户状态 0:正常 1:锁定',
PRIMARY KEY (`uid`),
UNIQUE KEY `username` (`username`) USING BTREE,
UNIQUE KEY `id_card_num` (`id_card_num`) USING BTREE
) ENGINE=INNODB DEFAULT CHARSET=utf8;
sys_role.sql(角色表)
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`available` char(1) DEFAULT '0' COMMENT '是否可用 0:可用 1:不可用',
`role` varchar(20) DEFAULT NULL COMMENT '角色标识程序中判断使用,如admin',
`description` varchar(100) DEFAULT NULL COMMENT '角色描述,UI界面显示使用',
PRIMARY KEY (`id`),
UNIQUE KEY `role` (`role`) USING BTREE
) ENGINE=INNODB DEFAULT CHARSET=utf8;
sys_user_role.sql(用户-角色表)
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`uid` int(11) DEFAULT NULL COMMENT '用户id',
`role_id` int(11) DEFAULT NULL COMMENT '角色id',
KEY `uid` (`uid`) USING BTREE,
KEY `role_id` (`role_id`) USING BTREE
) ENGINE=INNODB DEFAULT CHARSET=utf8;
sys_permission.sql(权限表)
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`parent_id` int(11) DEFAULT NULL COMMENT '父编号,本权限可能是该父编号权限的子权限',
`parent_ids` int(20) DEFAULT NULL COMMENT '父编号列表',
`permission` varchar(100) DEFAULT NULL COMMENT '权限字符串,menu例子:role:* -- button例子:role:create,role:update,role:delete,role:view',
`resource_type` varchar(20) DEFAULT NULL COMMENT '资源类型 [menu | button]',
`url` varchar(100) DEFAULT NULL COMMENT '资源路径 如:/userinfo/list',
`name` varchar(50) DEFAULT NULL COMMENT '权限名称',
`available` char(1) DEFAULT '0' COMMENT '是否可用 0:可用 1:不可用',
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
sys_role_permission.sql(角色-权限表)
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission` (
`role_id` int(11) DEFAULT NULL COMMENT '角色id',
`permission_id` int(11) DEFAULT NULL COMMENT '权限id',
KEY `role_id` (`role_id`) USING BTREE,
KEY `permission_id` (`permission_id`) USING BTREE
) ENGINE=INNODB DEFAULT CHARSET=utf8;
添加数据
# 插入用户信息表
INSERT INTO `user_info`(`uid`, `username`, `password`, `name`, id_card_num) VALUES (null, 'admin', '123456', '宁缺', '133333333333333333');
INSERT INTO `user_info`(`uid`, `username`, `password`, `name`, id_card_num) VALUES (null, 'test', '123456', '桑桑', '155555555555555555');
INSERT INTO `user_info`(`uid`, `username`, `password`, `name`, id_card_num) VALUES (null, 'ray', '123456', '马云', '188888888888888888');
# 插入角色表
INSERT INTO `sys_role` (`id`, `available`, `description`, `role`) VALUES (null, 0, '管理员', 'admin');
INSERT INTO `sys_role` (`id`, `available`, `description`, `role`) VALUES (null, 0, 'VIP会员', 'vip');
INSERT INTO `sys_role` (`id`, `available`, `description`, `role`) VALUES (null, 1, '测试', 'test');
# 插入用户角色关联表
INSERT INTO `sys_user_role` (`role_id`, `uid`) VALUES (1, 1);
INSERT INTO `sys_user_role` (`role_id`, `uid`) VALUES (2, 2);
INSERT INTO `sys_user_role` (`role_id`, `uid`) VALUES (3, 3);
# 插入权限表
INSERT INTO `sys_permission` (`id`, `available`, `name`, `parent_id`, `parent_ids`, `permission`, `resource_type`, `url`) VALUES (null, 0, '用户管理', 0, '0/', 'userInfo:view', 'menu', 'userInfo/view');
INSERT INTO `sys_permission` (`id`, `available`, `name`, `parent_id`, `parent_ids`, `permission`, `resource_type`, `url`) VALUES (null, 0, '用户添加', 1, '0/1', 'userInfo:add', 'button', 'userInfo/add');
INSERT INTO `sys_permission` (`id`, `available`, `name`, `parent_id`, `parent_ids`, `permission`, `resource_type`, `url`) VALUES (null, 0, '用户删除', 1, '0/1', 'userInfo:del', 'button', 'userInfo/del');
#插入角色_权限表
INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (1,1);
INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (2,1);
INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (3,2);
Entity
注意:不存在的字段,统一使用注解@TableField(exist = false)
用户信息
/**
* Created by 李新宇
* 2019-07-06 11:43
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class UserInfo implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "uid", type = IdType.AUTO)
private Integer uid;
/**
* 用户名
*/
private String username;
/**
* 登录密码
*/
private String password;
/**
* 用户真实姓名
*/
private String name;
/**
* 用户身份证号
*/
private String idCardNum;
/**
* 用户状态 0:正常 1:锁定
*/
private String state;
@TableField(exist = false)
private Set<SysRole> roles = new HashSet<>();
}
角色信息
/**
* Created by 李新宇
* 2019-07-06 11:03
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class SysRole implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 是否可用 0:可用 1:不可用
*/
private String available;
/**
* 角色标识程序中判断使用,如admin
*/
private String role;
/**
* 角色描述,UI界面显示使用
*/
private String description;
@TableField(exist = false)
private Set<UserInfo> users = new HashSet<>();
@TableField(exist = false)
private Set<SysPermission> permissions = new HashSet<>();
}
权限信息
/**
* Created by 李新宇
* 2019-07-06 11:42
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class SysPermission implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 父编号,本权限可能是该父编号权限的子权限
*/
private Integer parentId;
/**
* 父编号列表
*/
private String parentIds;
/**
* 权限字符串,menu例子:role:* -- button例子:role:create,role:update,role:delete,role:view
*/
private String permission;
/**
* 资源类型 [menu | button]
*/
private String resourceType;
/**
* 资源路径 如:/userinfo/list
*/
private String url;
/**
* 权限名称
*/
private String name;
/**
* 是否可用 0:可用 1:不可用
*/
private String available;
@TableField(exist = false)
private Set<SysRole> sysRoles = new HashSet<>();
}
角色权限关系表
/**
* Created by 李新宇
* 2019-07-06 11:42
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class SysRolePermission implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 角色id
*/
private Integer roleId;
/**
* 权限id
*/
private Integer permissionId;
}
Mapper
注意:这里为了学习Shiro,加上了 my 前缀,不使用BaseMapper
UserInfoMapper
/**
* Created by 李新宇
* 2019-07-06 11:47
*/
public interface UserInfoMapper extends BaseMapper<UserInfo> {
/**
* 根据username查询信息
*/
UserInfo myFindByUserInfoName(String username);
/**
* 根据userInfo插入信息
*/
int myInsert(UserInfo userInfo);
/**
* 根据username删除信息
*/
int myDel(@Param("username") String username);
}
SysRoleMapper
/**
* Created by 李新宇
* 2019-07-06 11:59
*/
public interface SysRoleMapper extends BaseMapper<SysRole> {
/**
* 根据uid查询角色信息 - 一个用户可能存在多个角色,故用Set
*/
Set<SysRole> myFindSysRolesByUserInfoId(@Param("uid") Integer id);
}
SysPermissionMapper
/**
* Created by 李新宇
* 2019-07-06 11:43
*/
public interface SysPermissionMapper extends BaseMapper<SysPermission> {
/**
* 根据sysRoles查询权限 - 一个角色可能存在多个权限,故用Set
*/
Set<SysPermission> myFindSysPermissionsBySysRoleId(@Param("sysRoles") Set<SysRole> sysRoles);
}
SysRolePermissionMapper
/**
* Created by 李新宇
* 2019-07-06 11:43
*/
public interface SysRolePermissionMapper extends BaseMapper<SysRolePermission> {
}
MapperXML
UserInfoMapper.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.ray.shiro_demo_01.mapper.UserInfoMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.ray.shiro_demo_01.entity.UserInfo">
<id column="uid" property="uid"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="name" property="name"/>
<result column="id_card_num" property="idCardNum"/>
<result column="state" property="state"/>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
uid, username, password, name, id_card_num, state
</sql>
<insert id="myInsert" parameterType="com.ray.shiro_demo_01.entity.UserInfo">
INSERT INTO user_info
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="uid != null">
uid,
</if>
<if test="username != null and username != ''">
username,
</if>
<if test="password != null and password != ''">
password,
</if>
<if test="name != null and name != ''">
`name`,
</if>
<if test="idCardNum != null and idCardNum != ''">
id_card_num,
</if>
<if test="state != null and state != ''">
state,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="uid != null">
#{uid},
</if>
<if test="username != null and username != ''">
#{username},
</if>
<if test="password != null and password != ''">
#{password},
</if>
<if test="name != null and name != ''">
#{name},
</if>
<if test="idCardNum != null and idCardNum != ''">
#{idCardNum},
</if>
<if test="state != null and state != ''">
#{state},
</if>
</trim>
</insert>
<delete id="myDel">
DELETE FROM user_info WHERE username = #{username}
</delete>
<select id="myFindByUserInfoName" resultType="com.ray.shiro_demo_01.entity.UserInfo">
SELECT * FROM user_info WHERE username = #{username}
</select>
</mapper>
SysRoleMapper.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.ray.shiro_demo_01.mapper.SysRoleMapper">
<select id="myFindSysRolesByUserInfoId" resultType="com.ray.shiro_demo_01.entity.SysRole">
SELECT
r.*
FROM
sys_role r
LEFT JOIN
sys_user_role ur
ON
r.id = ur.role_id
WHERE
ur.uid = #{uid}
</select>
</mapper>
SysPermissionMapper.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.ray.shiro_demo_01.mapper.SysPermissionMapper">
<select id="myFindSysPermissionsBySysRoleId" resultType="com.ray.shiro_demo_01.entity.SysPermission">
SELECT
p.*
FROM
sys_permission p
LEFT JOIN
sys_role_permission rp
ON
p.id = rp.permission_id
WHERE
rp.role_id
IN
<foreach collection="sysRoles" index="index" item="item" open="(" close=")" separator=",">
#{item.id}
</foreach>
</select>
</mapper>
SysRolePermissionMapper.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.ray.shiro_demo_01.mapper.SysRolePermissionMapper">
</mapper>
Shiro 配置
自定义ShiroRealm
在认证、授权内部实现机制中都有提到,最终处理都将交给Real进行处理。因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO。
/**
* Created by 李新宇
* 2019-07-06 11:32
* 在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的
* 在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO.
*/
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private UserInfoMapper userInfoMapper;
@Autowired
private SysRoleMapper sysRoleMapper;
@Autowired
private SysPermissionMapper sysPermissionMapper;
/**
* 验证用户身份
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取用户名密码 第一种方式
// String username = (String) authenticationToken.getPrincipal();
// String password = new String((char[]) authenticationToken.getCredentials());
// 第二种方式
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
String username = usernamePasswordToken.getUsername();
String password = new String(usernamePasswordToken.getPassword());
//从数据库查询用户信息
UserInfo user = this.userInfoMapper.myFindByUserInfoName(username);
//可以在这里直接对用户名校验,或者调用 CredentialsMatcher 校验
//调用 CredentialsMatcher 校验 还需要创建一个类 继承CredentialsMatcher 如果在上面校验了,这个就不需要了
if (user == null) {
throw new UnknownAccountException("用户名或密码错误!");
}
if (!password.equals(user.getPassword())) {
throw new IncorrectCredentialsException("用户名或密码错误!");
}
if ("1".equals(user.getState())) {
throw new LockedAccountException("账号已被锁定,请联系管理员!");
}
//配置自定义权限登录器 参考博客:
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
return info;
}
/**
* 授权用户权限
* 授权的方法是在碰到<shiro:hasPermission name=''></shiro:hasPermission>标签的时候调用的
* 它会去检测shiro框架中的权限(这里的permissions)是否包含有该标签的name值,如果有,里面的内容显示
* 如果没有,里面的内容不予显示(这就完成了对于权限的认证.)
* <p>
* shiro的权限授权是通过继承AuthorizingRealm抽象类,重载doGetAuthorizationInfo();
* 当访问到页面的时候,链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行
* 所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回null即可。
* <p>
* 在这个方法中主要是使用类:SimpleAuthorizationInfo 进行角色的添加和权限的添加。
* authorizationInfo.addRole(role.getRole()); authorizationInfo.addStringPermission(p.getPermission());
* <p>
* 当然也可以添加set集合:roles是从数据库查询的当前用户的角色,stringPermissions是从数据库查询的当前用户对应的权限
* authorizationInfo.setRoles(roles); authorizationInfo.setStringPermissions(stringPermissions);
* <p>
* 就是说如果在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "perms[权限添加]");
* 就说明访问/add这个链接必须要有“权限添加”这个权限才可以访问
* <p>
* 如果在shiro配置文件中添加了filterChainDefinitionMap.put("/add", "roles[100002],perms[权限添加]");
* 就说明访问/add这个链接必须要有 "权限添加" 这个权限和具有 "100002" 这个角色才可以访问
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 获取用户
UserInfo user = (UserInfo) SecurityUtils.getSubject().getPrincipal();
// 获取用户角色
Set<SysRole> roles = this.sysRoleMapper.myFindSysRolesByUserInfoId(user.getUid());
// 添加角色
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
for (SysRole role : roles) {
authorizationInfo.addRole(role.getRole());
}
// 获取用户权限
Set<SysPermission> permissions = this.sysPermissionMapper.myFindSysPermissionsBySysRoleId(roles);
// 添加权限
for (SysPermission permission : permissions) {
authorizationInfo.addStringPermission(permission.getPermission());
}
return authorizationInfo;
}
Shiro配置类
/**
* Created by 李新宇
* 2019-07-06 11:50
* Shiro 配置类
*/
@Configuration
public class ShiroConfig {
/**
* 开启shiro 注解模式
* 可以在controller中的方法前添加注解
* 如 @RequiresPermissions("userInfo:add")
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
* Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截
* @param securityManager
* @return
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager, Shiro的核心安全接口
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 这里的 /login 是后台的接口名,非页面,如果不设置默认会自动寻找Web工程根目录下的 "/login.html" 页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 这里的 /index 是后台的接口名,非页面,登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权跳转的链接
shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
//自定义拦截器限制并发人数,参考博客:
//LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();
//限制同一帐号同时在线的个数
//filtersMap.put("kickout", kickoutSessionControlFilter());
//shiroFilterFactoryBean.setFilters(filtersMap);
// 配置访问权限 必须是LinkedHashMap,因为它必须保证有序
// 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 一定要注意顺序,否则就不好使了
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//配置不登录可以访问的资源,anon 表示资源都可以匿名访问
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
// filterChainDefinitionMap.put("/druid/**", "anon");
// logout 是 shiro提供的过滤器
filterChainDefinitionMap.put("/logout", "logout");
//此时访问/userInfo/del需要del权限,在自定义Realm中为用户授权。
filterChainDefinitionMap.put("/userInfo/del", "perms[userInfo:del]");
filterChainDefinitionMap.put("/userInfo/add", "perms[userInfo:add]");
filterChainDefinitionMap.put("/userInfo/view", "perms[userInfo:view]");
//其他资源都需要认证 authc 表示需要认证才能进行访问
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 配置核心安全事务管理器
* @param shiroRealm
* @return
*/
@Bean(name = "securityManager")
public SecurityManager securityManager(@Qualifier("shiroRealm") ShiroRealm shiroRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置自定义realm
securityManager.setRealm(shiroRealm);
//配置记住我 参考博客:
//securityManager.setRememberMeManager(rememberMeManager());
//配置 redis缓存管理器 参考博客:
//securityManager.setCacheManager(getEhCacheManager());
//配置自定义session管理,使用redis 参考博客:
//securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* 配置Shiro生命周期处理器
* @return
*/
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 身份认证realm; (这个需要自己写,账号密码校验;权限等)
* @return
*/
@Bean
public ShiroRealm shiroRealm() {
ShiroRealm shiroRealm = new ShiroRealm();
return shiroRealm;
}
/**
* 必须(thymeleaf页面使用shiro标签控制按钮是否显示)
* 未引入thymeleaf包,Caused by: java.lang.ClassNotFoundException: org.thymeleaf.dialect.AbstractProcessorDialect
* @return
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
内置的FilterChain(常用)
这几个是我们常用到的,在这里说明下,其它的请自行查询文档进行学习。
- anon:所有url都都可以匿名访问;
- authc: 需要认证才能进行访问;
- user:配置记住我或认证通过可以访问;
MP 配置
简单测试,不创建配置类,直接在启动类配置
@SpringBootApplication
@MapperScan("com.ray.shiro_demo_01.mapper")
public class ShiroDemo01Application {
public static void main(String[] args) {
SpringApplication.run(ShiroDemo01Application.class, args);
}
}
Controller
LoginController
/**
* Created by 李新宇
* 2019-07-06 11:30
*/
@Controller
public class LoginController {
/**
* 访问项目根路径
*/
@RequestMapping(value = "/", method = RequestMethod.GET)
public String root(Model model) {
Subject subject = SecurityUtils.getSubject();
UserInfo user = (UserInfo) subject.getPrincipal();
if (user == null) {
return "redirect:/login";
} else {
return "redirect:/index";
}
}
/**
* 跳转到login页面
*/
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(Model model) {
Subject subject = SecurityUtils.getSubject();
UserInfo user = (UserInfo) subject.getPrincipal();
if (user == null) {
return "login";
} else {
return "redirect:index";
}
}
/**
* 用户登录
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String loginUser(HttpServletRequest request, String username, String password, Model model, HttpSession session) {
//对密码进行加密
//password=new SimpleHash("md5", password, ByteSource.Util.bytes(username.toLowerCase() + "shiro"),2).toHex();
//如果有点击 记住我
//UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken(username,password,remeberMe);
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
// 登录操作
subject.login(usernamePasswordToken);
UserInfo user = (UserInfo) subject.getPrincipal();
// 更新用户登录时间,也可以在ShiroRealm里做
session.setAttribute("user", user);
model.addAttribute("user", user);
return "index";
} catch (Exception e) {
//登录失败从request中获取shiro处理的异常信息 shiroLoginFailure:就是shiro异常类的全类名
String exception = (String) request.getAttribute("shiroLoginFailure");
model.addAttribute("msg", e.getMessage());
//返回登录页面
return "login";
}
}
@RequestMapping("/index")
public String index(HttpSession session, Model model) {
Subject subject = SecurityUtils.getSubject();
UserInfo user = (UserInfo) subject.getPrincipal();
if (user == null) {
return "login";
} else {
model.addAttribute("user", user);
return "index";
}
}
/**
* 登出 这个方法没用到,用的是shiro默认的logout
*/
@RequestMapping("/logout")
public String logout(HttpSession session, Model model) {
Subject subject = SecurityUtils.getSubject();
subject.logout();
model.addAttribute("msg", "安全退出");
return "login";
}
/**
* 跳转到无权限页面
*/
@RequestMapping("/unauthorized")
public String unauthorized(HttpSession session, Model model) {
return "unauthorized";
}
}
UserController
/**
* Created by 李新宇
* 2019-07-06 11:30
*/
@RestController
@RequestMapping("userInfo")
public class UserController {
@Autowired
private UserInfoMapper userInfoMapper;
/**
* 创建固定写死的用户
*/
@RequestMapping(value = "/add", method = RequestMethod.GET)
public String login(Model model) {
UserInfo user = new UserInfo();
user.setName("波波");
user.setIdCardNum("177777777777777777");
user.setUsername("bobo");
userInfoMapper.myInsert(user);
return "创建用户成功";
}
/**
* 删除固定写死的用户
*/
// @RequiresPermissions("userInfo:del")
@RequestMapping(value = "/del", method = RequestMethod.GET)
public String del(Model model) {
userInfoMapper.myDel("bobo");
return "删除用户名 bobo 成功";
}
/**
* 用户列表
*/
@RequestMapping(value = "/view", method = RequestMethod.GET)
public String view(Model model) {
return "用户列表";
}
}
View
login.html
<!DOCTYPE html>
<html lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>login.html</title>
</head>
<body>
<h1>欢迎登录</h1>
<h1 th:if="${msg != null}" th:text="${msg}" style="color:red"></h1>
<form action="/login" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="password" name="password"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>index.html</title>
</head>
<body>
<h1 th:text="'欢迎' + ${user.username} + '光临!请选择你的操作'"></h1>
<br>
<ul>
<h1 th:if="${msg != null}" th:text="${msg}" style="color: red"></h1>
<shiro:hasPermission name="userInfo:add"><a href="/userInfo/add">点击添加固定用户信息</a> </shiro:hasPermission><br>
<shiro:hasPermission name="userInfo:del"><a href="/userInfo/del">点击删除固定用户信息</a> </shiro:hasPermission><br>
<shiro:hasPermission name="userInfo:view"><a href="/userInfo/view">显示此内容表示拥有查看用户列表的权限</a> </shiro:hasPermission><br>
<!--<!– 用户没有身份验证时显示相应信息,即游客访问信息 –>-->
<shiro:guest>游客显示的信息</shiro:guest><br>
<!--<!– 用户已经身份验证/记住我登录后显示相应的信息 –>-->
<shiro:user>用户已经登录过了</shiro:user><br>
<!--<!– 用户已经身份验证通过,即Subject.login登录成功,不是记住我登录的 –>-->
<shiro:authenticated>不是记住我登录</shiro:authenticated><br>
<!--<!– 显示用户身份信息,通常为登录帐号信息,默认调用Subject.getPrincipal()获取,即Primary Principal –>-->
<shiro:principal></shiro:principal><br>
<!--用户已经身份验证通过,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证,与guest标签的区别是,该标签包含已记住用户 -->
<shiro:notAuthenticated>已记住用户</shiro:notAuthenticated><br>
<!-- 相当于Subject.getPrincipals().oneByType(String.class) -->
<shiro:principal type="java.lang.String" /><br>
<!-- 相当于((User)Subject.getPrincipals()).getUsername() -->
<shiro:principal property="username" /><br>
<!-- 如果当前Subject有角色将显示body体内容 name="角色名" -->
<shiro:hasRole name="admin">这是admin角色</shiro:hasRole><br>
<!-- 如果当前Subject有任意一个角色(或的关系)将显示body体内容。 name="角色名1,角色名2..." -->
<shiro:hasAnyRoles name="admin, vip">用户拥有admin角色或vip角色</shiro:hasAnyRoles><br>
<!-- 如果当前Subject没有角色将显示body体内容 -->
<shiro:lacksRole name="admin">如果不是admin角色,显示内容</shiro:lacksRole><br>
<!-- 如果当前Subject有权限将显示body体内容 name="权限名" -->
<shiro:hasPermission name="userInfo:add">用户拥有添加权限</shiro:hasPermission><br>
<!-- 用户同时拥有以下两种权限,显示内容 -->
<shiro:hasAllPermissions name="userInfo:add, userInfo:view">用户同时拥有列表权限和添加权限</shiro:hasAllPermissions><br>
<!-- 用户拥有以下权限任意一种 -->
<shiro:hasAnyPermissions name="userInfo:view, userInfo:del">用户拥有列表权限或者删除权限</shiro:hasAnyPermissions><br>
<!-- 如果当前Subject没有权限将显示body体内容 name="权限名" -->
<shiro:lacksPermission name="userInfo:add">如果用户没有添加权限,显示的内容</shiro:lacksPermission><br>
</ul>
<a href="/logout">点我注销</a>
</body>
</html>
unauthorized.html
<!DOCTYPE html>
<html lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>unauthorized.html</title>
</head>
<body>
<h1>对不起,您没有权限</h1>
</body>
</html>
验证测试
第一步: 访问 http://localhost:18080/userInfo/add 发现自动跳转到登录页
第二步:使用 admin/123456 登录
第三步:注销之后使用 test/123456 登录
不同的用户登录,显示不同的功能,点击之后也可以调用后台服务,证明身份验证成功。
test用户拥有 userInfo/del
权限
第四步: test用户使用尝试访问 http://localhost:8080/userInfo/add 发现没有权限
项目结构