一套系统,除了个人网站,一般都要有多种角色,每种角色必须严格控制它的权限。
Shiro是一种轻量级的安全框架,主要是做登录验证,权限检查,相对 Spring Security 是要简单很多,源码也很清晰。
本文通过实战做一个介绍。
本文采用 SpringBoot2.0.5 + MyBatis + MyBatis Plus
下面是 Shiro 的依赖
其他依赖就不贴,如果缺少,可以在下面评论取索取,或者在 Maven 上搜
5张表
① user 表
② role 表
③ permission 表
④ user_role_ref 表
⑤ role_permission_ref 表
备注:
本来还有一张菜单表,不属于本文的范畴,就不介绍了。
跟权限有关的,表名加前缀 rbac_,项目名叫 sens,表名加前缀 sens_
User.java
Role.java
Permisison.java
UserRoleRef.java
RolePermission.java
UserMapper.java
RoleMapper.java
PermissionMapper.java
备注:
关于UserRoleRefMapper.java 和 RolePermissionRefMapper.java 本文没有涉及到,不贴了
UserMapper.xml
RoleMapper.xml
PermissionMapper.xml
备注:
关于UserRoleRefMapper.xml 和 RolePermissionRefMapper.xml 本文没有涉及到,不贴了
4. Service 接口
UserService.java
RoleService.java
PermissionService.java
UserServiceImpl.java
RoleServiceImpl.java
PermissionServiceImpl.java
1.MyShiroRealm.java
上面一个方法是自定义认证方法,查询用户是否存在,然后把密码等信息放到 authenticationInfo 里,shiro 然后进行验证。
下面这个方法是被已登录用户封装角色列表和权限列表。
2.ShiroConfig.java
下面是登录的后台代码
主要是做一个 subject.login() 的操作,然后 try catch,根据抛的异常,返回指定的信息或者跳转。
这里说明一下,登录成功后,不需要添加 session,shiro 有自己的 SessionDao 创建 session,但是那个 key,可能不知道。
后台获得用户直接从 subject.getPrincipal(); 里拿,前台可以用对应的模板引擎取,或者向后台发请求获得当前用户,建议不用自己弄 session,容易出现两个 session 过期时间不同,尤其是记住密码后。
主要是在方法上加 @RequiresPermissions 注解或者 @RequiresRoles 注解,也可以在类上加(那就是整个类里的所有方法必须要有这个权限)
下面以标签控制器为例
本文介绍至此完毕,核心部分为 ShiroRealm.java 和 ShiroConfig.java 两个类
Shiro是一种轻量级的安全框架,主要是做登录验证,权限检查,相对 Spring Security 是要简单很多,源码也很清晰。
本文通过实战做一个介绍。
一、准备
1.pom.xml
本文采用 SpringBoot2.0.5 + MyBatis + MyBatis Plus
下面是 Shiro 的依赖
- <!--Shiro-->
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-spring</artifactId>
- <version>1.4.0</version>
- </dependency>
其他依赖就不贴,如果缺少,可以在下面评论取索取,或者在 Maven 上搜
2.数据库
5张表
① user 表
- CREATE TABLE `sens_user` (
- `user_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
- `login_enable` varchar(255) DEFAULT NULL,
- `login_error` int(11) DEFAULT NULL,
- `login_last` datetime DEFAULT NULL,
- `user_avatar` varchar(255) DEFAULT NULL,
- `user_desc` varchar(255) DEFAULT NULL,
- `user_display_name` varchar(255) DEFAULT NULL,
- `user_email` varchar(255) DEFAULT NULL,
- `user_name` varchar(255) DEFAULT NULL,
- `user_pass` varchar(255) DEFAULT NULL,
- `user_site` varchar(255) DEFAULT NULL,
- `status` int(1) DEFAULT NULL,
- `create_time` datetime DEFAULT NULL,
- PRIMARY KEY (`user_id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4
② role 表
- CREATE TABLE `sens_rbac_role` (
- `id` int(11) NOT NULL,
- `role` varchar(255) DEFAULT NULL,
- `description` varchar(255) DEFAULT NULL
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
③ permission 表
- CREATE TABLE `sens_rbac_permission` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `name` varchar(255) DEFAULT NULL,
- `permission` varchar(255) DEFAULT NULL,
- PRIMARY KEY (`id`)
- ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4
④ user_role_ref 表
- CREATE TABLE `sens_rbac_user_role_ref` (
- `user_id` int(20) DEFAULT NULL,
- `role_id` int(11) DEFAULT NULL
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
⑤ role_permission_ref 表
- CREATE TABLE `sens_rbac_role_permission_ref` (
- `role_id` int(11) DEFAULT NULL,
- `permission_id` int(11) DEFAULT NULL
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
备注:
本来还有一张菜单表,不属于本文的范畴,就不介绍了。
跟权限有关的,表名加前缀 rbac_,项目名叫 sens,表名加前缀 sens_
二、基础代码(Model,Dao和Service层)
1.Model 实体
User.java
- package com.liuyanzhao.sens.entity;
- import com.baomidou.mybatisplus.annotations.TableId;
- import com.baomidou.mybatisplus.annotations.TableName;
- import com.baomidou.mybatisplus.enums.IdType;
- import com.fasterxml.jackson.annotation.JsonIgnore;
- import lombok.Data;
- import javax.validation.constraints.Email;
- import javax.validation.constraints.NotBlank;
- import java.io.Serializable;
- import java.util.Date;
- /**
- * <pre>
- * 博主信息
- * </pre>
- *
- * @author : saysky
- * @date : 2017/11/14
- */
- @Data
- @TableName("sens_user")
- public class User implements Serializable {
- private static final long serialVersionUID = -5144055068797033748L;
- /**
- * 编号
- */
- @TableId(type = IdType.AUTO)
- private Long userId;
- /**
- * 用户名
- */
- @NotBlank(message = "用户名不能为空")
- // @JsonIgnore
- private String userName;
- /**
- * 显示名称
- */
- private String userDisplayName;
- /**
- * 密码
- */
- // @JsonIgnore
- private String userPass;
- /**
- * 邮箱
- */
- @Email(message = "邮箱格式不正确")
- private String userEmail;
- /**
- * 头像
- */
- private String userAvatar;
- /**
- * 说明
- */
- private String userDesc;
- /**
- * 个人主页
- */
- private String userSite;
- /**
- * 是否禁用登录
- */
- // @JsonIgnore
- private String loginEnable = "true";
- /**
- * 最后一次登录时间
- */
- // @JsonIgnore
- private Date loginLast;
- /**
- * 登录错误次数记录
- */
- // @JsonIgnore
- private Integer loginError = 0;
- /**
- * 0 正常
- * 1 禁用
- * 2 已删除
- */
- private Integer status = 0;
- /**
- * 注册时间
- */
- private Date createTime;
- }
Role.java
- package com.liuyanzhao.sens.entity;
- import com.baomidou.mybatisplus.annotations.TableField;
- import com.baomidou.mybatisplus.annotations.TableName;
- import lombok.Data;
- import java.io.Serializable;
- /**
- * @author liuyanzhao
- */
- @Data
- @TableName("sens_rbac_role")
- public class Role implements Serializable {
- private static final long serialVersionUID = 2233979806802117985L;
- /**
- * ID
- */
- private Integer id;
- /**
- * 角色名称:admin,author,subscriber
- */
- private String role;
- /**
- * 描述:管理员,作者,订阅者
- */
- private String description;
- /**
- * 该角色对应的用户数量,非数据库字段
- */
- @TableField(exist = false)
- private Integer count;
- }
Permisison.java
- package com.liuyanzhao.sens.entity;
- import com.baomidou.mybatisplus.annotations.TableName;
- import lombok.Data;
- import java.io.Serializable;
- /**
- * @author liuyanzhao
- */
- @Data
- @TableName("sens_rbac_permission")
- public class Permission implements Serializable {
- private static final long serialVersionUID = 2233979806802117985L;
- /**
- * ID
- */
- private Integer id;
- /**
- * 权限名称
- */
- private String name;
- /**
- * 权限:user:list,user:add等
- */
- private String permission;
- }
UserRoleRef.java
- package com.liuyanzhao.sens.entity;
- import com.baomidou.mybatisplus.annotations.TableName;
- import lombok.Data;
- import java.io.Serializable;
- @Data
- @TableName("sens_rbac_user_role_ref")
- public class UserRoleRef implements Serializable {
- private static final long serialVersionUID = -1528306395712515362L;
- /**
- * 用户Id
- */
- private Long userId;
- /**
- * 角色Id
- */
- private Integer roleId;
- }
RolePermission.java
- package com.liuyanzhao.sens.entity;
- import com.baomidou.mybatisplus.annotations.TableName;
- import lombok.Data;
- import java.io.Serializable;
- @Data
- @TableName("sens_rbac_role_permission_ref")
- public class RolePermissionRef implements Serializable {
- private static final long serialVersionUID = -4015823675140925791L;
- /**
- * 角色Id
- */
- private Integer roleId;
- /**
- * 权限Id
- */
- private Integer permissionId;
- }
2.Mapper类
UserMapper.java
- package com.liuyanzhao.sens.mapper;
- import com.baomidou.mybatisplus.mapper.BaseMapper;
- import com.baomidou.mybatisplus.plugins.pagination.Pagination;
- import com.liuyanzhao.sens.entity.User;
- import org.apache.ibatis.annotations.Mapper;
- import org.apache.ibatis.annotations.Param;
- import java.util.List;
- /**
- * @author liuyanzhao
- */
- @Mapper
- public interface UserMapper extends BaseMapper<User> {
- /**
- * 查询所有
- *
- * @return 用户列表
- */
- List<User> findAll(Pagination page);
- /**
- * 根据角色Id获得用户
- *
- * @param roleId 角色Id
- * @param pagination 分页信息
- * @return 用户列表
- */
- List<User> findByRoleId(@Param("roleId") Integer roleId, Pagination pagination);
- /**
- * 根据用户名获得用户
- *
- * @param userName 用户名
- * @return 用户
- */
- User findByUserName(String userName);
- /**
- * 根据用户邮箱获得用户
- *
- * @param userEmail 邮箱
- * @return 用户
- */
- User findByUserEmail(String userEmail);
- /**
- * 根据用户Id获得用户
- * @param userId 用户Id
- * @return 用户
- */
- User findByUserId(Long userId);
- }
RoleMapper.java
- package com.liuyanzhao.sens.mapper;
- import com.baomidou.mybatisplus.mapper.BaseMapper;
- import com.liuyanzhao.sens.entity.Role;
- import org.apache.ibatis.annotations.Mapper;
- import java.util.List;
- /**
- * @author liuyanzhao
- */
- @Mapper
- public interface RoleMapper extends BaseMapper<Role> {
- /**
- * 根据用户Id获得角色列表
- *
- * @param userId 用户Id
- * @return 角色列表
- */
- List<Role> findByUserId(Long userId);
- /**
- * 根据名称获得角色
- *
- * @param roleName 角色名
- * @return 角色
- */
- Role findByRoleName(String roleName);
- /**
- * 获得角色列表
- *
- * @return 角色列表
- */
- List<Role> findAll();
- /**
- * 删除用户和角色管理
- *
- * @param userId 用户ID
- * @return 影响行数
- */
- Integer deleteByUserId(Long userId);
- /**
- * 统计某个角色的用户数
- *
- * @param roleId 角色Id
- * @return 用户数
- */
- Integer countUserByRoleId(Integer roleId);
- }
PermissionMapper.java
- package com.liuyanzhao.sens.mapper;
- import com.baomidou.mybatisplus.mapper.BaseMapper;
- import com.liuyanzhao.sens.entity.Permission;
- import org.apache.ibatis.annotations.Mapper;
- import java.util.List;
- /**
- * @author liuyanzhao
- */
- @Mapper
- public interface PermissionMapper extends BaseMapper<Permission> {
- /**
- * 根据角色Id获得权限列表
- *
- * @param roleId 角色Id
- * @return 权限列表
- */
- List<Permission> findByRoleId(Integer roleId);
- /**
- * 获得权限列表
- *
- * @return 权限列表
- */
- List<Permission> findAll();
- }
备注:
关于UserRoleRefMapper.java 和 RolePermissionRefMapper.java 本文没有涉及到,不贴了
3.Mapper xml
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.liuyanzhao.sens.mapper.UserMapper">
- <resultMap id="BaseResultMap" type="com.liuyanzhao.sens.entity.User">
- <id column="user_id" jdbcType="INTEGER" property="userId"/>
- <result column="user_name" jdbcType="VARCHAR" property="userName"/>
- <result column="user_display_name" jdbcType="VARCHAR" property="userDisplayName"/>
- <result column="user_pass" jdbcType="VARCHAR" property="userPass"/>
- <result column="user_email" jdbcType="VARCHAR" property="userEmail"/>
- <result column="user_avatar" jdbcType="VARCHAR" property="userAvatar"/>
- <result column="user_desc" jdbcType="VARCHAR" property="userDesc"/>
- <result column="user_site" jdbcType="VARCHAR" property="userSite"/>
- <result column="login_enable" jdbcType="VARCHAR" property="loginEnable"/>
- <result column="login_last" jdbcType="TIMESTAMP" property="loginLast"/>
- <result column="login_error" jdbcType="INTEGER" property="loginError"/>
- <result column="status" jdbcType="INTEGER" property="status"/>
- <result column="create_time" jdbcType="TIMESTAMP" property="createTime"/>
- </resultMap>
- <sql id="all_columns">
- user_id, user_name, user_display_name, user_pass, user_email,
- user_avatar, user_desc, user_site, login_enable, login_last,
- login_error, status, create_time
- </sql>
- <sql id="normal">0</sql>
- <sql id="all_columns_with_table_name">
- sens_user.user_id, sens_user.user_name, sens_user.user_display_name,
- sens_user.user_pass, sens_user.user_email, sens_user.user_avatar,
- sens_user.user_desc, sens_user.user_site, sens_user.login_enable,
- sens_user.login_last, sens_user.login_error, sens_user.status, sens_user.create_time
- </sql>
- <sql id="tb">`sens_user`</sql>
- <sql id="all_values">
- #{userId}, #{userName}, #{userDisplayName}, #{userEmail}, #{userPass},
- #{userAvatar}, #{userDesc}, #{userSite}, #{loginEnable}, #{loginLast},
- #{loginError}, #{status}, #{createTime}
- </sql>
- <select id="findAll" resultMap="BaseResultMap">
- SELECT
- <include refid="all_columns"/>
- FROM
- <include refid="tb"/>
- </select>
- <select id="findByRoleId" resultType="com.liuyanzhao.sens.entity.User">
- SELECT
- <include refid="all_columns_with_table_name"/>
- FROM sens_rbac_user_role_ref,
- sens_user WHERE sens_rbac_user_role_ref.role_id = #{roleId} AND
- sens_rbac_user_role_ref.user_id = sens_user.user_id
- </select>
- <select id="findByUserName" resultType="com.liuyanzhao.sens.entity.User">
- SELECT
- <include refid="all_columns"/>
- FROM
- <include refid="tb"/>
- WHERE user_name = #{value} AND status = <include refid="normal"/> LIMIT 1
- </select>
- <select id="findByUserEmail" resultType="com.liuyanzhao.sens.entity.User">
- SELECT
- <include refid="all_columns"/>
- FROM
- <include refid="tb"/>
- WHERE user_email = #{value} AND status = <include refid="normal"/> LIMIT 1
- </select>
- <select id="findByUserId" resultType="com.liuyanzhao.sens.entity.User">
- SELECT
- <include refid="all_columns"/>
- FROM
- <include refid="tb"/>
- WHERE user_id = #{value}
- </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.liuyanzhao.sens.mapper.RoleMapper">
- <resultMap id="BaseResultMap" type="com.liuyanzhao.sens.entity.Role">
- <id column="id" jdbcType="INTEGER" property="id"/>
- <result column="role" jdbcType="VARCHAR" property="role"/>
- <result column="description" jdbcType="VARCHAR" property="description"/>
- </resultMap>
- <sql id="all_columns">
- id, role, description
- </sql>
- <sql id="tb">`sens_rbac_role`</sql>
- <sql id="all_values">
- #{id}, #{role}, #{description}
- </sql>
- <sql id="all_values_with_table_name">
- sens_rbac_role.id, sens_rbac_role.role, sens_rbac_role.description
- </sql>
- <select id="findByUserId" resultType="com.liuyanzhao.sens.entity.Role">
- select
- <include refid="all_values_with_table_name"/>
- FROM<include refid="tb"/>, sens_rbac_user_role_ref
- where sens_rbac_user_role_ref.user_id = #{value} AND
- sens_rbac_role.id = sens_rbac_user_role_ref.role_id
- </select>
- <select id="findByRoleName" resultType="com.liuyanzhao.sens.entity.Role">
- SELECT
- <include refid="all_columns"/>
- FROM
- <include refid="tb"/>
- WHERE role = #{value} LIMIT 1
- </select>
- <select id="findAll" resultType="com.liuyanzhao.sens.entity.Role">
- SELECT
- <include refid="all_columns"/>
- FROM
- <include refid="tb"/>
- </select>
- <delete id="deleteByUserId">
- DELETE FROM sens_rbac_user_role_ref
- WHERE user_id = #{value}
- </delete>
- <select id="countUserByRoleId" resultType="java.lang.Integer">
- SELECT count(*) FROM sens_rbac_user_role_ref,sens_user
- WHERE sens_rbac_user_role_ref.role_id = #{value} AND
- sens_rbac_user_role_ref.user_id = sens_user.user_id
- </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.liuyanzhao.sens.mapper.PermissionMapper">
- <resultMap id="BaseResultMap" type="com.liuyanzhao.sens.entity.Permission">
- <id column="id" jdbcType="INTEGER" property="id"/>
- <result column="name" jdbcType="VARCHAR" property="name"/>
- <result column="permission" jdbcType="VARCHAR" property="permission"/>
- </resultMap>
- <sql id="all_columns">
- id, `name`, permission
- </sql>
- <sql id="all_values_with_table_name">
- sens_rbac_permission.id, sens_rbac_permission.`name`, sens_rbac_permission.permission
- </sql>
- <sql id="tb">`sens_rbac_permission`</sql>
- <sql id="all_values">
- #{id}, #{name}, #{permission}
- </sql>
- <select id="findByRoleId" resultType="com.liuyanzhao.sens.entity.Permission">
- select
- <include refid="all_values_with_table_name"/>
- FROM<include refid="tb"/>, sens_rbac_role_permission_ref
- where sens_rbac_role_permission_ref.role_id = #{value} AND
- sens_rbac_permission.id = sens_rbac_role_permission_ref.permission_id
- </select>
- <select id="findAll" resultType="com.liuyanzhao.sens.entity.Permission">
- SELECT <include refid="all_columns"/>
- FROM <include refid="tb"/>
- </select>
- </mapper>
备注:
关于UserRoleRefMapper.xml 和 RolePermissionRefMapper.xml 本文没有涉及到,不贴了
4. Service 接口
UserService.java
- package com.liuyanzhao.sens.service;
- import com.baomidou.mybatisplus.plugins.Page;
- import com.liuyanzhao.sens.entity.User;
- import java.util.Date;
- /**
- * <pre>
- * 用户业务逻辑接口
- * </pre>
- *
- * @author : saysky
- * @date : 2017/11/14
- */
- public interface UserService {
- /**
- * 新增/修改用户
- *
- * @param user user
- * @return Role
- */
- User saveByUser(User user);
- /**
- * 根据用户名获得用户
- *
- * @param userName 用户名
- * @return 用户
- */
- User findByUserName(String userName);
- /**
- * 根据邮箱查找用户
- *
- * @param userEmail 邮箱
- * @return User
- */
- User findByEmail(String userEmail);
- /**
- * 根据用户Id获得用户
- *
- * @param userId 用户名
- * @return 用户
- */
- User findByUserId(Long userId);
- /**
- * 修改禁用状态
- *
- * @param enable enable
- */
- void updateUserLoginEnable(User user, String enable);
- /**
- * 修改最后登录时间
- *
- * @param lastDate 最后登录时间
- * @return User
- */
- User updateUserLoginLast(User user, Date lastDate);
- /**
- * 增加登录错误次数
- *
- * @return 登录错误次数
- */
- Integer updateUserLoginError(User user);
- /**
- * 修改用户的登录状态为正常
- *
- * @return User
- */
- User updateUserLoginNormal(User user);
- }
RoleService.java
- package com.liuyanzhao.sens.service;
- import com.liuyanzhao.sens.entity.Role;
- import com.liuyanzhao.sens.entity.User;
- import java.util.List;
- /**
- * <pre>
- * 角色逻辑接口
- * </pre>
- *
- * @author : saysky
- * @date : 2017/11/14
- */
- public interface RoleService {
- /**
- * 新增/修改角色
- *
- * @param role role
- * @return Role
- */
- Role saveByRole(Role role);
- /**
- * 根据编号删除
- *
- * @param roleId roleId
- * @return Role
- */
- void removeByRoleId(Integer roleId);
- /**
- * 删除某个用户的所有关联
- *
- * @param userId 用户Id
- */
- void removeByUserId(Long userId);
- /**
- * 根据编号查询单个权限
- *
- * @param roleId roleId
- * @return Role
- */
- Role findByRoleId(Integer roleId);
- /**
- * 根据编号查询单个权限
- *
- * @param roleName roleName
- * @return Role
- */
- Role findByRoleName(String roleName);
- /**
- * 根据用户Id获得角色列表
- *
- * @param userId 用户Id
- * @return 角色列表
- */
- List<Role> listRolesByUserId(Long userId);
- /**
- * 获得所有的角色
- *
- * @return 角色列表
- */
- List<Role> findAll();
- }
PermissionService.java
- package com.liuyanzhao.sens.service;
- import com.liuyanzhao.sens.entity.Permission;
- import java.util.List;
- /**
- * <pre>
- * 权限逻辑接口
- * </pre>
- *
- * @author : saysky
- * @date : 2017/11/14
- */
- public interface PermissionService {
- /**
- * 新增/修改权限
- *
- * @param permission permission
- * @return Permission
- */
- Permission saveByPermission(Permission permission);
- /**
- * 根据编号删除
- *
- * @param permissionId permissionId
- * @return Permission
- */
- void removeByPermissionId(Integer permissionId);
- /**
- * 根据编号查询单个权限
- *
- * @param permissionId permissionId
- * @return Permission
- */
- Permission findByPermissionId(Integer permissionId);
- /**
- * 根据角色Id获得权限列表
- *
- * @param roleId 角色Id
- * @return 权限列表
- */
- List<Permission> listPermissionsByRoleId(Integer roleId);
- }
5.Service 实现
UserServiceImpl.java
- package com.liuyanzhao.sens.service.impl;
- import com.baomidou.mybatisplus.plugins.Page;
- import com.liuyanzhao.sens.entity.Role;
- import com.liuyanzhao.sens.mapper.UserMapper;
- import com.liuyanzhao.sens.entity.User;
- import com.liuyanzhao.sens.model.enums.CommentStatusEnum;
- import com.liuyanzhao.sens.model.enums.TrueFalseEnum;
- import com.liuyanzhao.sens.model.enums.UserStatusEnum;
- import com.liuyanzhao.sens.service.RoleService;
- import com.liuyanzhao.sens.service.UserService;
- import com.liuyanzhao.sens.utils.RedisUtil;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.cache.annotation.CacheEvict;
- import org.springframework.cache.annotation.Cacheable;
- import org.springframework.stereotype.Service;
- import org.springframework.transaction.annotation.Transactional;
- import java.util.Collections;
- import java.util.Date;
- import java.util.List;
- import java.util.Objects;
- /**
- * <pre>
- * 用户业务逻辑实现类
- * </pre>
- *
- * @author : saysky
- * @date : 2017/11/14
- */
- @Service
- public class UserServiceImpl implements UserService {
- private static final String USERS_CACHE_NAME = "users";
- @Autowired(required = false)
- private UserMapper userMapper;
- @Autowired(required = false)
- private RoleService roleService;
- @Override
- @CacheEvict(value = USERS_CACHE_NAME, allEntries = true, beforeInvocation = true)
- public User saveByUser(User user) {
- if (user != null && user.getUserId() != null) {
- userMapper.updateById(user);
- } else {
- userMapper.insert(user);
- }
- return user;
- }
- @Override
- @Cacheable(value = USERS_CACHE_NAME, key = "'users_name_'+#userName", unless = "#result == null")
- public User findByUserName(String userName) {
- return userMapper.findByUserName(userName);
- }
- @Override
- @Cacheable(value = USERS_CACHE_NAME, key = "'users_email_'+#userEmail", unless = "#result == null")
- public User findByEmail(String userEmail) {
- return userMapper.findByUserEmail(userEmail);
- }
- @Override
- @Cacheable(value = USERS_CACHE_NAME, key = "'users_id_'+#userName", unless = "#result == null")
- public User findByUserId(Long userId) {
- return userMapper.findByUserId(userId);
- }
- @Override
- @CacheEvict(value = USERS_CACHE_NAME, allEntries = true, beforeInvocation = true)
- public void updateUser(User user) {
- userMapper.updateById(user);
- }
- /**
- * 修改禁用状态
- *
- * @param enable enable
- */
- @Override
- @CacheEvict(value = USERS_CACHE_NAME, allEntries = true, beforeInvocation = true)
- public void updateUserLoginEnable(User user, String enable) {
- //如果是修改为正常, 重置错误次数
- if (Objects.equals(TrueFalseEnum.TRUE.getDesc(), enable)) {
- user.setLoginError(0);
- }
- user.setLoginEnable(enable);
- this.updateUser(user);
- }
- /**
- * 修改最后登录时间
- *
- * @param lastDate 最后登录时间
- * @return User
- */
- @Override
- @CacheEvict(value = USERS_CACHE_NAME, allEntries = true, beforeInvocation = true)
- public User updateUserLoginLast(User user, Date lastDate) {
- user.setLoginLast(lastDate);
- this.updateUser(user);
- return user;
- }
- /**
- * 增加登录错误次数
- *
- * @return 登录错误次数
- */
- @Override
- @CacheEvict(value = USERS_CACHE_NAME, allEntries = true, beforeInvocation = true)
- public Integer updateUserLoginError(User user) {
- user.setLoginError((user.getLoginError() == null ? 0 : user.getLoginError()) + 1);
- this.updateUser(user);
- return user.getLoginError();
- }
- /**
- * 修改用户的状态为正常
- *
- * @return User
- */
- @Override
- @CacheEvict(value = USERS_CACHE_NAME, allEntries = true, beforeInvocation = true)
- public User updateUserLoginNormal(User user) {
- user.setLoginEnable(TrueFalseEnum.TRUE.getDesc());
- user.setLoginError(0);
- user.setLoginLast(new Date());
- this.updateUser(user);
- return user;
- }
- @Override
- @CacheEvict(value = USERS_CACHE_NAME, allEntries = true, beforeInvocation = true)
- public void updateUserStatus(Long userId, Integer status) {
- User user = findByUserId(userId);
- if (user != null) {
- user.setStatus(status);
- userMapper.updateById(user);
- }
- }
- }
RoleServiceImpl.java
- package com.liuyanzhao.sens.service.impl;
- import com.liuyanzhao.sens.entity.Role;
- import com.liuyanzhao.sens.mapper.RoleMapper;
- import com.liuyanzhao.sens.service.RoleService;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.cache.annotation.CacheEvict;
- import org.springframework.cache.annotation.Cacheable;
- import org.springframework.stereotype.Service;
- import java.util.List;
- /**
- * <pre>
- * 角色业务逻辑实现类
- * </pre>
- *
- * @author : saysky
- * @date : 2017/11/14
- */
- @Service
- public class RoleServiceImpl implements RoleService {
- private static final String ROLES_CACHE_NAME = "roles";
- @Autowired(required = false)
- private RoleMapper roleMapper;
- @Override
- @CacheEvict(value = ROLES_CACHE_NAME, allEntries = true, beforeInvocation = true)
- public Role saveByRole(Role role) {
- if (role != null && role.getId() != null) {
- roleMapper.updateById(role);
- } else {
- roleMapper.insert(role);
- }
- return role;
- }
- @Override
- @CacheEvict(value = ROLES_CACHE_NAME, allEntries = true, beforeInvocation = true)
- public void removeByRoleId(Integer roleId) {
- roleMapper.deleteById(roleId);
- }
- @Override
- @CacheEvict(value = ROLES_CACHE_NAME, allEntries = true, beforeInvocation = true)
- public void removeByUserId(Long userId) {
- roleMapper.deleteByUserId(userId);
- }
- @Override
- @Cacheable(value = ROLES_CACHE_NAME, key = "'roles_id_'+#roleId", unless = "#result == null")
- public Role findByRoleId(Integer roleId) {
- return roleMapper.selectById(roleId);
- }
- @Override
- @Cacheable(value = ROLES_CACHE_NAME, key = "'roles_name_'+#roleName", unless = "#result == null")
- public Role findByRoleName(String roleName) {
- return roleMapper.findByRoleName(roleName);
- }
- @Override
- @Cacheable(value = ROLES_CACHE_NAME, key = "'roles_uid_'+#userId", unless = "#result == null")
- public List<Role> listRolesByUserId(Long userId) {
- return roleMapper.findByUserId(userId);
- }
- @Override
- @Cacheable(value = ROLES_CACHE_NAME, key = "'roles_all'")
- public List<Role> findAll() {
- //获得角色
- List<Role> roles = roleMapper.findAll();
- //封装count
- roles.forEach(role -> role.setCount(roleMapper.countUserByRoleId(role.getId())));
- return roles;
- }
- }
PermissionServiceImpl.java
- package com.liuyanzhao.sens.service;
- import com.liuyanzhao.sens.entity.Permission;
- import java.util.List;
- /**
- * <pre>
- * 权限逻辑接口
- * </pre>
- *
- * @author : saysky
- * @date : 2017/11/14
- */
- public interface PermissionService {
- /**
- * 新增/修改权限
- *
- * @param permission permission
- * @return Permission
- */
- Permission saveByPermission(Permission permission);
- /**
- * 根据编号删除
- *
- * @param permissionId permissionId
- * @return Permission
- */
- void removeByPermissionId(Integer permissionId);
- /**
- * 根据编号查询单个权限
- *
- * @param permissionId permissionId
- * @return Permission
- */
- Permission findByPermissionId(Integer permissionId);
- /**
- * 根据角色Id获得权限列表
- *
- * @param roleId 角色Id
- * @return 权限列表
- */
- List<Permission> listPermissionsByRoleId(Integer roleId);
- }
三、整合 Shiro
1.MyShiroRealm.java
- package com.liuyanzhao.sens.config;
- import cn.hutool.core.date.DateUnit;
- import cn.hutool.core.date.DateUtil;
- import cn.hutool.core.lang.Validator;
- import cn.hutool.core.util.ObjectUtil;
- import cn.hutool.extra.servlet.ServletUtil;
- import cn.hutool.http.HtmlUtil;
- import com.liuyanzhao.sens.entity.Permission;
- import com.liuyanzhao.sens.entity.Role;
- import com.liuyanzhao.sens.entity.User;
- import com.liuyanzhao.sens.model.dto.JsonResult;
- import com.liuyanzhao.sens.model.dto.LogsRecord;
- import com.liuyanzhao.sens.model.enums.CommonParamsEnum;
- import com.liuyanzhao.sens.model.enums.ResultCodeEnum;
- import com.liuyanzhao.sens.model.enums.TrueFalseEnum;
- import com.liuyanzhao.sens.service.PermissionService;
- import com.liuyanzhao.sens.service.RoleService;
- import com.liuyanzhao.sens.service.UserService;
- import com.liuyanzhao.sens.utils.LocaleMessageUtil;
- import com.liuyanzhao.sens.utils.Md5Util;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.commons.lang3.StringUtils;
- import org.apache.shiro.authc.*;
- import org.apache.shiro.authz.AuthorizationInfo;
- import org.apache.shiro.authz.SimpleAuthorizationInfo;
- import org.apache.shiro.realm.AuthorizingRealm;
- import org.apache.shiro.subject.PrincipalCollection;
- import org.apache.shiro.util.ByteSource;
- import org.springframework.beans.factory.annotation.Autowired;
- import java.util.Date;
- import java.util.List;
- /**
- * 默认的realm
- *
- * @author 言曌
- * @date 2018/9/1 上午10:47
- */
- @Slf4j
- public class MyShiroRealm extends AuthorizingRealm {
- @Autowired
- private UserService userService;
- @Autowired
- private RoleService roleService;
- @Autowired
- private PermissionService permissionService;
- @Autowired
- private LocaleMessageUtil localeMessageUtil;
- /**
- * 认证信息(身份验证) Authentication 是用来验证用户身份
- */
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
- log.info("认证-->MyShiroRealm.doGetAuthenticationInfo()");
- //1.验证用户名
- User user = null;
- String loginName = (String) token.getPrincipal();
- if (Validator.isEmail(loginName)) {
- user = userService.findByEmail(loginName);
- } else {
- user = userService.findByUserName(loginName);
- }
- if (user == null) {
- //用户不存在
- log.info("用户不存在! 登录名:{}, 密码:{}", loginName, token.getCredentials());
- return null;
- }
- //2.首先判断是否已经被禁用已经是否已经过了10分钟
- Date loginLast = DateUtil.date();
- if (null != user.getLoginLast()) {
- loginLast = user.getLoginLast();
- }
- Long between = DateUtil.between(loginLast, DateUtil.date(), DateUnit.MINUTE);
- if (StringUtils.equals(user.getLoginEnable(), TrueFalseEnum.FALSE.getDesc()) && (between < CommonParamsEnum.TEN.getValue())) {
- log.info("账号已锁定! 登录名:{}, 密码:{}", loginName, token.getCredentials());
- throw new LockedAccountException("账号被锁定");
- }
- userService.updateUserLoginLast(user, DateUtil.date());
- //3.封装authenticationInfo,准备验证密码
- SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
- user, // 用户名
- user.getUserPass(), // 密码
- ByteSource.Util.bytes("sens"), // 盐
- getName() // realm name
- );
- System.out.println("realName:" + getName());
- return authenticationInfo;
- }
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
- log.info("授权-->MyShiroRealm.doGetAuthorizationInfo()");
- SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
- User user = (User) principals.getPrimaryPrincipal();
- List<Role> roles = roleService.listRolesByUserId(user.getUserId());
- for (Role role : roles) {
- authorizationInfo.addRole(role.getRole());
- List<Permission> permissions = permissionService.listPermissionsByRoleId(role.getId());
- for (Permission p : permissions) {
- authorizationInfo.addStringPermission(p.getPermission());
- }
- }
- return authorizationInfo;
- }
- }
上面一个方法是自定义认证方法,查询用户是否存在,然后把密码等信息放到 authenticationInfo 里,shiro 然后进行验证。
下面这个方法是被已登录用户封装角色列表和权限列表。
2.ShiroConfig.java
- package com.liuyanzhao.sens.config;
- import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
- import org.apache.shiro.mgt.SecurityManager;
- import org.apache.shiro.realm.Realm;
- import org.apache.shiro.spring.LifecycleBeanPostProcessor;
- import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
- import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
- import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
- import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.context.annotation.DependsOn;
- import java.util.ArrayList;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Map;
- /**
- * @author 言曌
- * @date 2018/8/20 上午6:19
- */
- @Configuration
- public class ShiroConfig {
- @Bean
- public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
- System.out.println("ShiroConfiguration.shirFilter()");
- ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
- shiroFilterFactoryBean.setSecurityManager(securityManager);
- //拦截器.
- Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
- // 配置不会被拦截的链接 顺序判断
- filterChainDefinitionMap.put("/static/**", "anon");
- filterChainDefinitionMap.put("/upload/**", "anon");
- filterChainDefinitionMap.put("/favicon.ico", "anon");
- filterChainDefinitionMap.put("/favicon.ico", "anon");
- filterChainDefinitionMap.put("/admin/login", "anon");
- filterChainDefinitionMap.put("/admin/getLogin", "anon");
- //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
- filterChainDefinitionMap.put("/logout", "logout");
- //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
- //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
- filterChainDefinitionMap.put("/admin/**", "authc");
- filterChainDefinitionMap.put("/backup/**", "authc");
- filterChainDefinitionMap.put("/**", "anon");
- shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
- // 如果不设置默认会自动寻找Web工程根目录下的"/login"页面
- shiroFilterFactoryBean.setLoginUrl("/admin/login");
- // 登录成功后要跳转的链接
- shiroFilterFactoryBean.setSuccessUrl("/");
- //未授权界面;
- shiroFilterFactoryBean.setUnauthorizedUrl("/403");
- return shiroFilterFactoryBean;
- }
- @Bean
- public SecurityManager securityManager() {
- DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
- securityManager.setRealm(myShiroRealm());
- return securityManager;
- }
- /**
- * 身份认证realm; (这个需要自己写,账号密码校验;权限等)
- *
- * @return MyShiroRealm
- */
- @Bean
- public MyShiroRealm myShiroRealm() {
- MyShiroRealm myShiroRealm = new MyShiroRealm();
- myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
- return myShiroRealm;
- }
- /**
- * 凭证匹配器
- * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
- * 所以我们需要修改下doGetAuthenticationInfo中的代码;
- * )
- * @return
- */
- @Bean
- public HashedCredentialsMatcher hashedCredentialsMatcher(){
- HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
- hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
- hashedCredentialsMatcher.setHashIterations(2);//散列的次数,md5(md5("xxx"))
- return hashedCredentialsMatcher;
- }
- /**
- * 开启shiro aop注解支持,不开启无法使用 shiro 的注解.
- * 使用代理方式;所以需要开启代码支持;
- * @param securityManager
- * @return
- */
- /** * Shiro生命周期处理器 * @return */
- @Bean
- public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
- return new LifecycleBeanPostProcessor();
- }
- @Bean
- @DependsOn({"lifecycleBeanPostProcessor"})
- public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
- DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
- advisorAutoProxyCreator.setProxyTargetClass(true);
- return advisorAutoProxyCreator;
- }
- @Bean
- public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
- AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
- authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
- return authorizationAttributeSourceAdvisor;
- }
- }
四、登录和退出
下面是登录的后台代码
主要是做一个 subject.login() 的操作,然后 try catch,根据抛的异常,返回指定的信息或者跳转。
这里说明一下,登录成功后,不需要添加 session,shiro 有自己的 SessionDao 创建 session,但是那个 key,可能不知道。
后台获得用户直接从 subject.getPrincipal(); 里拿,前台可以用对应的模板引擎取,或者向后台发请求获得当前用户,建议不用自己弄 session,容易出现两个 session 过期时间不同,尤其是记住密码后。
1.登录代码
- /**
- * 验证登录信息
- *
- * @param loginName 登录名:邮箱/用户名
- * @param loginPwd loginPwd 密码
- * @return JsonResult JsonResult
- */
- @PostMapping(value = "/doLogin")
- @ResponseBody
- public JsonResult getLogin(@ModelAttribute("loginName") String loginName,
- @ModelAttribute("loginPwd") String loginPwd) {
- Subject subject = SecurityUtils.getSubject();
- UsernamePasswordToken token = new UsernamePasswordToken(loginName, loginPwd);
- try {
- subject.login(token);
- if (subject.isAuthenticated()) {
- //登录成功,修改登录错误次数为0
- User user = (User) subject.getPrincipal();
- userService.updateUserLoginNormal(user);
- return new JsonResult(ResultCodeEnum.SUCCESS.getCode(), "登录成功!");
- }
- } catch (UnknownAccountException e) {
- log.info("UnknownAccountException -- > 账号不存在:");
- return new JsonResult(ResultCodeEnum.FAIL.getCode(), "用户不存在!"));
- } catch (IncorrectCredentialsException e) {
- //更新失败次数
- User user;
- if (Validator.isEmail(loginName)) {
- user = userService.findByEmail(loginName);
- } else {
- user = userService.findByUserName(loginName);
- }
- if (user != null) {
- Integer errorCount = userService.updateUserLoginError(user);
- //超过五次禁用账户
- if (errorCount >= CommonParamsEnum.FIVE.getValue()) {
- userService.updateUserLoginEnable(user, TrueFalseEnum.FALSE.getDesc());
- }
- int rest = 5 - errorCount;
- return new JsonResult(ResultCodeEnum.FAIL.getCode(), "密码错误,你还可以尝试 [" + rest + "] 次!"));
- }
- } catch (LockedAccountException e) {
- log.info("LockedAccountException -- > 账号被锁定");
- return new JsonResult(ResultCodeEnum.FAIL.getCode(), "账号被锁定,十分钟后再来!");
- } catch (Exception e) {
- log.info(e.getMessage());
- }
- return new JsonResult(ResultCodeEnum.FAIL.getCode(), "发送错误!");
- }
2.退出代码
- /**
- * 退出登录
- *
- * @return 重定向到/admin/login
- */
- @GetMapping(value = "/logOut")
- public String logOut() {
- Subject subject = SecurityUtils.getSubject();
- User user = (User) subject.getPrincipal();
- subject.logout();
- log.info("用户[{}]退出登录", user.getUserName());
- return "redirect:/admin/login";
- }
七、权限控制
主要是在方法上加 @RequiresPermissions 注解或者 @RequiresRoles 注解,也可以在类上加(那就是整个类里的所有方法必须要有这个权限)
下面以标签控制器为例
- package com.liuyanzhao.sens.web.controller.admin;
- import com.liuyanzhao.sens.entity.Tag;
- import com.liuyanzhao.sens.model.dto.JsonResult;
- import com.liuyanzhao.sens.model.enums.ResultCodeEnum;
- import com.liuyanzhao.sens.service.TagService;
- import com.liuyanzhao.sens.utils.LocaleMessageUtil;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.shiro.authz.annotation.RequiresPermissions;
- import org.apache.shiro.authz.annotation.RequiresRoles;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Controller;
- import org.springframework.ui.Model;
- import org.springframework.web.bind.annotation.*;
- import java.util.List;
- /**
- * <pre>
- * 后台标签管理控制器
- * </pre>
- *
- * @author : saysky
- * @date : 2017/12/10
- */
- @Slf4j
- @Controller
- @RequestMapping(value = "/admin/tag")
- public class TagController {
- @Autowired
- private TagService tagService;
- @Autowired
- private LocaleMessageUtil localeMessageUtil;
- /**
- * 渲染标签管理页面
- *
- * @return 模板路径admin/admin_tag
- */
- @GetMapping
- @RequiresPermissions("tag:list")
- public String tags(Model model) {
- List<Tag> tags = tagService.findAllTags();
- model.addAttribute("tags", tags);
- return "admin/admin_tag";
- }
- /**
- * 新增/修改标签
- *
- * @param tag tag
- * @return 重定向到/admin/tag
- */
- @PostMapping(value = "/save")
- @RequiresPermissions("tag:save")
- public String saveTag(@ModelAttribute Tag tag) {
- try {
- tagService.saveByTag(tag);
- } catch (Exception e) {
- log.error("新增/修改标签失败:{}", e.getMessage());
- }
- return "redirect:/admin/tag";
- }
- /**
- * 验证是否存在该路径
- *
- * @param tagUrl 标签路径名
- * @return true:不存在,false:已存在
- */
- @GetMapping(value = "/checkUrl")
- @ResponseBody
- @RequiresPermissions("tag:url:check")
- public JsonResult checkTagUrlExists(@RequestParam("tagUrl") String tagUrl) {
- Tag tag = tagService.findByTagUrl(tagUrl);
- if (null != tag) {
- return new JsonResult(ResultCodeEnum.FAIL.getCode(), localeMessageUtil.getMessage("code.admin.common.url-is-exists"));
- }
- return new JsonResult(ResultCodeEnum.SUCCESS.getCode(), "");
- }
- /**
- * 删除标签
- *
- * @param tagId 标签Id
- * @return JsonResult
- */
- @GetMapping(value = "/remove")
- @ResponseBody
- @RequiresPermissions("tag:delete")
- public JsonResult checkDelete(@RequestParam("tagId") Long tagId) {
- tagService.removeByTagId(tagId);
- return new JsonResult(ResultCodeEnum.FAIL.getCode(), localeMessageUtil.getMessage("code.admin.common.delete-success"));
- }
- /**
- * 跳转到修改标签页面
- *
- * @param model model
- * @param tagId 标签编号
- * @return 模板路径admin/admin_tag
- */
- @GetMapping(value = "/edit")
- @RequiresPermissions("tag:edit")
- public String toEditTag(Model model, @RequestParam("tagId") Long tagId) {
- //当前修改的标签
- Tag tag = tagService.findByTagId(tagId);
- model.addAttribute("updateTag", tag);
- //所有标签
- List<Tag> tags = tagService.findAllTags();
- model.addAttribute("tags", tags);
- return "admin/admin_tag";
- }
- }
本文介绍至此完毕,核心部分为 ShiroRealm.java 和 ShiroConfig.java 两个类
2019年04月07日 23:27:19
文章不错支持一下吧
2019年02月25日 09:27:38
springboot 整合shiro有Git链接吗,想学习下