在学习springsecurity和shiro等安全框架之前可以使用原生的方式,基于cookie和session实现原生的认证,有利于加强对框架学习的理解,也有助于清晰的理解认证和授权的流程,也有助于对cookie和session作用的理解。
对cookie和session的概念不清楚的可以参考:cookie和session
一般的管理系统都是基于RABC授权,也即基于角色授权,RABC授权模式最简单的就是利用5张表:用户表,角色表,权限表,用户角色中间表,角色权限中间表,这里就基于这5张表进行讲解。
案例代码和SQL放在gitte中:案例源码地址
/*
Navicat Premium Data Transfer
Source Server : 本地
Source Server Type : MySQL
Source Server Version : 80025
Source Host : localhost:3306
Source Schema : sys_cookie
Target Server Type : MySQL
Target Server Version : 80025
File Encoding : 65001
Date: 23/03/2022 16:30:57
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键id,自增',
`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '访问的url',
`permission_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '权限许可码',
`permission_note` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '权限描述',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES (1, '/selectUser', 'select_user', '查看用户信息');
INSERT INTO `sys_permission` VALUES (2, '/addUser', 'add_user', '添加用户信息');
INSERT INTO `sys_permission` VALUES (3, '/updateUser', 'update_user', '更新用户信息');
INSERT INTO `sys_permission` VALUES (4, '/deleteUser', 'delete_user', '删除用户信息');
INSERT INTO `sys_permission` VALUES (5, '/selectLogs', 'select_logs', '查看系统日志');
INSERT INTO `sys_permission` VALUES (6, '/selectFinance', 'select_finance', '查看财务信息');
-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '角色id,主键自增',
`role_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色名称',
`role_note` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色拥有的权限',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, '系统超级管理员', '拥有系统所有权限');
INSERT INTO `sys_role` VALUES (2, '人事经理', '拥有用户所有管理权限');
INSERT INTO `sys_role` VALUES (3, '总经理', '拥有系统部分权限');
INSERT INTO `sys_role` VALUES (4, '财务总监', '拥有财务管理权限');
-- ----------------------------
-- Table structure for sys_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission` (
`id` int NOT NULL AUTO_INCREMENT,
`role_id` int NULL DEFAULT NULL,
`permission_id` int NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_role_permission
-- ----------------------------
INSERT INTO `sys_role_permission` VALUES (1, 1, 1);
INSERT INTO `sys_role_permission` VALUES (2, 1, 2);
INSERT INTO `sys_role_permission` VALUES (3, 1, 3);
INSERT INTO `sys_role_permission` VALUES (4, 1, 4);
INSERT INTO `sys_role_permission` VALUES (5, 1, 5);
INSERT INTO `sys_role_permission` VALUES (6, 1, 6);
INSERT INTO `sys_role_permission` VALUES (7, 2, 1);
INSERT INTO `sys_role_permission` VALUES (8, 2, 2);
INSERT INTO `sys_role_permission` VALUES (9, 2, 3);
INSERT INTO `sys_role_permission` VALUES (10, 2, 4);
INSERT INTO `sys_role_permission` VALUES (11, 3, 1);
INSERT INTO `sys_role_permission` VALUES (12, 3, 5);
INSERT INTO `sys_role_permission` VALUES (13, 3, 6);
INSERT INTO `sys_role_permission` VALUES (14, 4, 6);
-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '用户id,主键自增',
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户名',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密码',
`fullname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '真实姓名',
`phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '电话',
`created_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`updated_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'zhangsan', '123456', '张三', '15070443883', '2021-09-18 14:27:56', '2021-09-18 14:28:05');
INSERT INTO `sys_user` VALUES (2, 'lisi', '321123', '李四', '13496858978', '2021-09-18 14:29:11', '2021-09-20 14:29:14');
INSERT INTO `sys_user` VALUES (3, 'wangwu', '654321', '王五', '18965458769', '2021-09-18 16:06:35', '2021-09-18 16:06:41');
-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键id,自增',
`user_id` int NULL DEFAULT NULL COMMENT '用户id',
`role_id` int NULL DEFAULT NULL COMMENT '角色id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES (1, 1, 1);
INSERT INTO `sys_user_role` VALUES (2, 2, 2);
INSERT INTO `sys_user_role` VALUES (3, 2, 3);
INSERT INTO `sys_user_role` VALUES (4, 3, 4);
SET FOREIGN_KEY_CHECKS = 1;
一.认证
认证实体:首先创建一个认证实体,用户实体,角色实体,权限实体
认证实体
@Data
public class AuthenticationRequest {
private String username;
private String password;
}
用户实体
@Data
public class SysUser {
public static final String USER_SESSION_KEY = "user_session_key";
private Integer id;
private String username;
private String password;
private String fullname;
private String phone;
private Date createdTime;
private Date updatedTime;
private List<SysRole> roles;
private List<SysPermission> sysPermissions;
}
角色实体
@Data
public class SysRole {
private Integer id;
private String roleName;
private String roleNote;
}
权限实体
@Data
public class SysPermission {
private Integer id;
private String url;
private String permissionCode;
private String permissionNote;
}
认证控制器:在controller中有一个登入控制器,传入两个参数:认证实体和HttpServletRequest,这里首先调认证业务进行认证处理,认证成功后则通过HttpServletRequest获取session对象,然后将用户的基本信息(用户信息,用户拥有的角色,用户拥有的权限)存入session中,并且设置好session的过期时间。
@RestController
public class UserController {
@Autowired
private AuthenticationService authenticationService;
@PostMapping("/login")
public JsonResult login(AuthenticationRequest authenticationRequest, HttpServletRequest httpServletRequest){
SysUser sysUser = authenticationService.authentication(authenticationRequest);
//获取session对象
HttpSession session = httpServletRequest.getSession();
//将用户的信息放入到session中
session.setAttribute(SysUser.USER_SESSION_KEY, sysUser);
//设置session失效时间,单位为秒
session.setMaxInactiveInterval(60);
return new JsonResult("登入成功", sysUser);
}
}
认证业务类:认证业务类中首先对用户名做非空判断,不为空后再根据用户输入的用户名去查询数据库,查询出用户的基本信息,取出密码和用户输入的密码比对,密码相匹配后再根据用户的id去关联5张表,查询出用户的信息,用户的角色以及用户的权限。
@Service
public class AuthenticationServiceImpl implements AuthenticationService {
@Autowired
private SysUserMapper sysUserMapper;
@Override
public SysUser authentication(AuthenticationRequest authenticationRequest) {
String username = authenticationRequest.getUsername();
//先对用户名做非空判断
if (StringUtils.isBlank(username)){
throw new RuntimeException("用户名不能为空");
}
//不为空则根据用户名到数据库中查找用户信息
QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();
queryWrapper
.select("id", "username", "password", "password", "fullname", "phone")
.eq("username", username);
SysUser sysUser = sysUserMapper.selectOne(queryWrapper);
//判断数据库是否存在该用户
if (sysUser == null){
throw new RuntimeException("系统不存在该用户名,请先注册");
}
//存在则根据用户输入的密码和数据库取出的用户密码进行比对
if (!sysUser.getPassword().equals(authenticationRequest.getPassword())){
throw new RuntimeException("密码错误,请重试");
}
//密码匹配后根据用户id到数据库中查出用户的基本信息(用户信息,用户的角色,用户的权限)
sysUser = sysUserMapper.selectSysUserInfo(sysUser.getId());
return sysUser;
}
}
认证数据层
@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {
public SysUser selectSysUserInfo(Integer id);
}
用户数据层的映射文件
<?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.bingqin.sys.dao.SysUserMapper">
<select id="selectSysUserInfo" resultType="sysUser" resultMap="userInfoMap">
SELECT
u.username,
u.fullname,
u.phone,
r.role_name,
r.role_note,
p.url,
p.permission_code,
p.permission_note
FROM
sys_user u
INNER JOIN sys_user_role ur ON u.id = ur.user_id
INNER JOIN sys_role r ON r.id = ur.role_id
INNER JOIN sys_role_permission rp ON r.id = rp.role_id
INNER JOIN sys_permission p ON p.id = rp.permission_id
WHERE
u.id = #{id}
</select>
<resultMap id="userInfoMap" type="sysUser">
<result property="username" column="username"/>
<result property="fullname" column="fullname"/>
<result property="phone" column="phone"/>
<collection property="roles" ofType="sysRole">
<result property="roleName" column="role_name"/>
<result property="roleNote" column="role_note"/>
</collection>
<collection property="sysPermissions" ofType="sysPermission">
<result property="url" column="url"/>
<result property="permissionCode" column="permission_code"/>
<result property="permissionNote" column="permission_note"/>
</collection>
</resultMap>
</mapper>
登出:认证做完后下面介绍登出功能,用户退出系统后台只需将用户的session信息移除即可。
@PostMapping("/logout")
public JsonResult logout(HttpServletRequest httpServletRequest){
HttpSession session = httpServletRequest.getSession();
session.removeAttribute(SysUser.USER_SESSION_KEY);
return new JsonResult("登出成功");
}
二.鉴权
用户登入后需要访问其他接口,但是如何判断用户是否有该权限,这里需在后台进行判断,判断的方式就是每当用户访问一个接口时都会先执行拦截器,在拦截器中对用户的权限进行鉴别,如果该用户有该url的权限则对其放行,没有权限的则告知用户无权限访问。
首先定义一个拦截器,实现preHandle方法
@Component
public class AuthorizeInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession();
//根据session的键取出用户的信息
SysUser sysUserInfo = (SysUser) session.getAttribute(SysUser.USER_SESSION_KEY);
if (sysUserInfo == null){
throw new RuntimeException("请先登入");
}
//拿到用户访问的uri
String uri = request.getRequestURI();
//取出用户权限
List<SysPermission> userPermissions = sysUserInfo.getSysPermissions();
for (SysPermission userPermission : userPermissions) {
if (userPermission.getUrl().contains(uri)){
//权限匹配则放行
return true;
}
}
//无权则抛异常
throw new RuntimeException("无权限访问该接口,请联系管理员");
}
}
然后定义一个配置类,实现HandlerInterceptor,并将自己定义的拦截器添加进去,这里开放登入和登出接口,/login,/logout,其他接口都做拦截处理。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private AuthorizeInterceptor authorizeInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authorizeInterceptor)
.addPathPatterns("/**").excludePathPatterns("/login", "/logout");
WebMvcConfigurer.super.addInterceptors(registry);
}
}
三.测试
这里以wangwu这个用户做测试,wangwu的角色是财务总监,只拥有财务管理权限,其他的接口都不允许访问。
3.1登入
3.2接口访问测试
首先访问查看财务信息接口
再访问wangwu不拥有的权限,如:/selectUser
3.3登出
登出后不能访问其他接口,需要再次登入。