基于拦截器Interceptor实现简易权限控制及行为记录功能

一、业务需求

        使用拦截器(Interceptor),实现Controller中方法的权限控制,并记录访问行为。要求仅在Controller方法上加注解,就可以实现权限控制。具体为:

        1、拦截未登录用户的访问;

        2、拦截不具有权限用户的访问;

        3、用户访问成功,记录访问时间、设备等信息。

对拦截器还不了解的可以看我这一篇文章《Java拦截器(Interceptor)和过滤器(Filter)实例详解》

二、数据库设计 

        简单设计两个数据库,一个是用户表,一个是日志表。

用户表建表语句:

CREATE TABLE `user` (`user_id` varchar(255) NOT NULL,`user_role` varchar(255) DEFAULT NULL,PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

存入两个模拟数据:

日志表建表语句:

CREATE TABLE `record` (`id` bigint NOT NULL AUTO_INCREMENT,`ip` varchar(255) DEFAULT NULL,`method` varchar(255) DEFAULT NULL,`browser` varchar(255) DEFAULT NULL,`time` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

里面数据差不多这个样子:

        接着是实体类,及其对应的Mapper,这里使用了lombok和Mybatis plus。

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** @author Hao* @program: DockerTest* @description: 用户* @date 2023-10-23 15:22:36*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User {// 模拟用户IDprivate String userId;// 模拟用户角色private String userRole;
}
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** @author Hao* @program: DockerTest* @description: 访问记录* @date 2023-10-20 12:09:27*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("record")
public class Record {// 自增ID@TableId(type = IdType.AUTO)private Long id;// 访问IPprivate String ip;// 请求方式private String method;// 浏览器标识private String browser;// 访问时间private String time;
}
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hao.dockertest.po.User;
import org.apache.ibatis.annotations.Mapper;/*** @author Hao* @program: DockerTest* @description: User Mapper* @date 2023-10-23 15:24:49*/
@Mapper
public interface UserDAO extends BaseMapper<User> {
}
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hao.dockertest.po.Record;
import org.apache.ibatis.annotations.Mapper;/*** @author Hao* @program: DockerTest* @description: Record Mapper* @date 2023-10-20 12:12:03*/
@Mapper
public interface RecordDAO extends BaseMapper<Record> {
}

三、Service代码

        由于使用了Mybatis Plus,这里开发就简单很多

首先是UserService

import com.baomidou.mybatisplus.extension.service.IService;
import com.hao.dockertest.po.User;/*** @author Hao* @program: DockerTest* @description: User接口* @date 2023-10-23 15:24:34*/
public interface UserService extends IService<User> {// 检查用户对应的角色boolean checkUserRole(String userId, String userRole);
}

然后是其实现类,只有一个简单的校验用户角色和传入的角色是否相等。

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hao.dockertest.mapper.UserDAO;
import com.hao.dockertest.po.User;
import com.hao.dockertest.service.UserService;
import org.springframework.stereotype.Service;/*** @author Hao* @program: DockerTest* @description: UserService实现类* @date 2023-10-23 15:25:48*/
@Service
public class UserServiceImpl extends ServiceImpl<UserDAO, User> implements UserService {/*** 检查用户对应的角色* @param userId 用户ID* @param userRole 要检查的角色* @return yes or no*/@Overridepublic boolean checkUserRole(String userId, String userRole) {User user = this.lambdaQuery().eq(User::getUserId, userId).one();if(user != null)    return userRole.equals(user.getUserRole()); // 返回要检查的用户角色是否和数据库中存储的角色对于return false;}
}

然后是日志接口和实现类,里面都没有东西,因为Mybatis plus帮我们做了。

import com.baomidou.mybatisplus.extension.service.IService;
import com.hao.dockertest.po.Record;/*** @author Hao* @program: DockerTest* @description: Record接口层* @date 2023-10-20 12:12:42*/
public interface RecordService extends IService<Record> {}
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hao.dockertest.mapper.RecordDAO;
import com.hao.dockertest.po.Record;
import com.hao.dockertest.service.RecordService;
import org.springframework.stereotype.Service;/*** @author Hao* @program: DockerTest* @description: RecordService实现类* @date 2023-10-20 12:25:56*/
@Service
public class RecordServiceImpl extends ServiceImpl<RecordDAO, Record> implements RecordService {}

接下来再弄两个工具类,一个是JWT工具类,用于生成token、验证token和解析token;还有一个是时间日期格式的(简单写下)。

import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;import java.util.Date;
import java.util.UUID;/*** @author Hao* @program: DockerTest* @description: JWT工具* @date 2023-10-23 10:44:36*/
@Slf4j
public class JWTUtil {private static long time = 1000*60*60*10;// 签名private static final String signature = "test";// 生产Tokenpublic static String createToken(String userName, String userID){JwtBuilder jwtBuilder = Jwts.builder();//构建JWT对象return jwtBuilder// Header.setHeaderParam("typ","JWT").setHeaderParam("alg","HS256")// payload.claim("userName",userName).claim("userId", userID)// 设置有效期(毫秒单位).setExpiration(new Date(System.currentTimeMillis()+time)).setId(UUID.randomUUID().toString())// signature.signWith(SignatureAlgorithm.HS256, signature)// compact拼接三部分header、payload、signature.compact();}// 验证Tokenpublic static Boolean checkToken(String token){if(token == null){return false;}try {JwtParser jwtParser = Jwts.parser();jwtParser.setSigningKey(signature).parseClaimsJws(token);return true;}catch (Exception e){// log.error("token失效");return false;}}// 解析Tokenpublic static String getTokenInfo(String token, String key){if(token == null || key == null)   return null;JwtParser parser = Jwts.parser();Jws<Claims> claimsJws = parser.setSigningKey(signature).parseClaimsJws(token);Claims payload = claimsJws.getBody();// 获取key对于的内容return payload.get(key).toString();}}
import java.text.SimpleDateFormat;/*** @author Hao* @program: DockerTest* @description: 时间格式工具* @date 2023-10-22 17:33:19*/
public class MyTimeUtil {public static SimpleDateFormat sdf= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}

 四、Controller代码

        在写Controller之前,我们先要定义一个注解,这个注解可以加在方法上,指定某个方法需要什么角色,如果不会注解的可以看廖雪峰的官方网站--定义注解。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** @author Hao* @program: DockerTest* @description: 需要某种角色注解* @date 2023-10-23 15:17:18*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireRole {// 这个value就存需要什么角色String value() default "";
}

        然后就可以定义Controller了,这里我们模拟三个简单方法,分别是:登录,普通用户常规操作,管理员用户查询日志操作。

        1、登录操作,任何人都可以访问,根据用户ID生成token返回给前端,后续前端访问可以携带token访问;

        2、模拟普通用户常规功能:普通用户可以访问的功能;

        3、模拟管理员获取日志表单条记录功能:管理员用户根据日志ID查询某条记录,此操作仅管理员可访问。

import com.hao.dockertest.AOP.RequireRole;
import com.hao.dockertest.po.Record;
import com.hao.dockertest.service.RecordService;
import com.hao.dockertest.util.JWTUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;/*** @author Hao* @program: DockerTest* @description: Record管理层* @date 2023-10-20 12:13:28*/
@RestController
@RequestMapping
@Slf4j
public class RecordController {@Autowiredprivate RecordService recordService;/*** 模拟用户登录功能(直接返回token,只是模拟)* @return token*/@PostMapping("/login")public String login(){// return JWTUtil.createToken("张三", "20210919"); // 模拟返回登录成功tokenreturn JWTUtil.createToken("李四", "20210920"); // 模拟返回登录成功token}/*** 模拟普通用户常规功能* @return 返回Welcome*/@GetMapping@RequireRole("common")public String getInfo(){return "Welcome!";}/*** 模拟管理员获取日志表单条记录功能* @param id 日志ID* @return 日志内容*/@GetMapping("/getInfo/{id}")@RequireRole("admin")public String getRecord(@PathVariable Long id){if(id == null)  return "Id must not null!";Record record = recordService.getById(id);if(record == null)  return "Do not have this record!";return record.toString();}
}

        至此,我们的基本业务以及模拟完成,下面就需要定义我们的拦截器,实现拦截需求。

五、自定义拦截器

import com.hao.dockertest.AOP.RequireRole;
import com.hao.dockertest.po.Record;
import com.hao.dockertest.service.RecordService;
import com.hao.dockertest.service.UserService;
import com.hao.dockertest.util.JWTUtil;
import com.hao.dockertest.util.MyTimeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;/*** @author Hao* @program: DockerTest* @description: 拦截器* @date 2023-10-21 21:13:27*/
@Slf4j
@Configuration
public class MyInterceptor implements HandlerInterceptor {@Autowiredprivate RecordService recordService;@Autowiredprivate UserService userService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {log.info("拦截器前置处理 preHandle");// 验证tokenString token = request.getHeader("token");if(token == null || !JWTUtil.checkToken(token)){log.error("未登录或身份信息验证失败");response.setCharacterEncoding("UTF-8");response.setContentType("application/json;charset=UTF-8");PrintWriter printWriter = response.getWriter();printWriter.write("您还未登录或身份验证失败,请重新登录!");return false;}// 验证角色HandlerMethod method = (HandlerMethod) handler; // 此处仅是模拟,理论上应该先使用instanceof检验RequireRole requireRole = method.getMethodAnnotation(RequireRole.class); // 通过反射获取方法注解if(requireRole != null){String userId = JWTUtil.getTokenInfo(token, "userId");// 判断此用户是否有访问权限if(!userService.checkUserRole(userId, requireRole.value())) {response.setCharacterEncoding("UTF-8");response.setContentType("application/json;charset=UTF-8");PrintWriter printWriter = response.getWriter();printWriter.write("您无权访问此功能!");log.error("用户{}非法访问,已成功拦截!", userId);return false;}}return HandlerInterceptor.super.preHandle(request, response, handler);}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {log.info("拦截器后置处理 postHandle");// 从HttpServletRequest获取相关信息String ip = request.getRemoteAddr();if (ip.equals("0:0:0:0:0:0:0:1"))   ip = "127.0.0.1";String browser = request.getHeader("Sec-Ch-Ua-Platform");String httpMethod = request.getMethod();String time = MyTimeUtil.sdf.format(System.currentTimeMillis());// 日志存档Record record = new Record(null, ip, httpMethod, browser, time);recordService.save(record); // 访问日志记录// 从token中获取相关信息String token = request.getHeader("token");String userName = JWTUtil.getTokenInfo(token, "userName");String userId = JWTUtil.getTokenInfo(token, "userId");log.info("访问用户:{}-{},访问IP:{},访问时间:{},请求方式:{},访问设备:{}",userName, userId, ip, time, httpMethod, browser);HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {log.info("拦截器完成后 afterCompletion");HandlerInterceptor.super.afterCompletion(request, response, handler, ex);}
}

        在preHandle()方法中我们首先验证用户是否登录和用户token是否有效,如果无效,直接拦截,让用户登录;验证身份之后,我们还需要验证他的角色是否符合访问方法的要求,我们通过反射获取方法上的注解,并获取注解中的value,与我们数据库中存储的角色进行对比,如果符合要求则放行,如果不符合要求,则拦截访问,并提示用户权限不足。

        在postHandle()方法中,我们从HttpServletRequest中,获取到了用户的IP、浏览器类型、请求方式等信息,持久化到我们的数据库中。

        定义完我们自己的拦截器之后,还要将其配置到Spring MVC中才会生效。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** @author Hao* @program: DockerTest* @description: Interceptor配置* @date 2023-10-21 21:26:18*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {@Autowiredprivate MyInterceptor myInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(myInterceptor).addPathPatterns("/**").excludePathPatterns("/login"); // 拦截所有请求,排除login}
}

        在上面的配置中,首先将我们自定义的拦截器addInterceptor,然后addPathPatterns指定了要拦截哪些路径,这里我们设置全部拦截,但是还要通过excludePathPatterns放行/login,不然用户无法登录。

六、功能测试

        这里我们使用postman进行功能测试,分为以下几种情况:

1、未登录用户访问页面

可以看到,我们自定义的拦截器,成功拦截了未登录用户的访问。

2、用户执行登录,返回token

可以看到,用户成功登录,且后端控制台并未打印信息,说明excludePathPatterns中我们放行了/login起到了作用。

3、普通用户20210919访问只需普通角色就可以访问的功能

可以看到,普通用户成功访问了getInfo()方法,并在访问后,我们的日志记录功能,成功记录了此用户的访问(注意我们的数据库并没有记录访问用户的ID,这个可以自行加,不难)。

4、普通用户20210919访问需admin管理员角色才能访问的方法,例如getRecord()

可以看到,此用户是无法访问需要admin角色的功能的,非法访问已经被成功拦截。

5、管理员用户20210920访问需 admin管理员角色才能访问的方法

首先我执行管理员用户的登录方法,获取管理员token(把login()方法中的return换成20210920即可)。

可以看到拥有admin身份的 20210920用户,成功访问到了指定的日志内容。

6、token失效或被篡改之后的拦截效果

我们随便删除token中的几个字符,模拟token失效或者被恶意篡改

可以看到失效的token是无法正常访问业务的。

以上,我们就完成了使用拦截器实现身份校验、权限控制和日志记录的全部功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/168506.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Git教程

文章目录 Git 介绍GIt历史Git 安装环境配置工作区、缓存区和仓库区&#xff08;版本库&#xff09;工作区(工作目录)暂存区仓库区git工作目录下文件的装填 Git 生成公钥及添加到gitlab或Gerrit上Git常用命令git stautsgit statu -sgit addgit commitgit reset1. git reset --ha…

Post-Process1-水下

一、新建第三人称游戏项目&#xff0c;我这里选择C&#xff0c;你也可以选择Blueprint。 新建一个Level&#xff0c;命名为DemoUnderWater 保存一下&#xff0c;命名为DownUnderWater 添加水插件 选择Yes 勾选Show Engine Content和Show Plugin Content&#xff0c;在左侧可以看…

图(graph)的遍历----深度优先(DFS)遍历

目录 前言 深度优先遍历&#xff08;DFS&#xff09; 1.基本概念 2.算法思想 3.二叉树的深度优先遍历&#xff08;例子&#xff09; 图的深度优先遍历 1.图(graph)邻接矩阵的深度优先遍历 思路分析 代码实现 2.图(graph)邻接表的深度优先遍历 思路分析 代码实现 递…

【Web】| CSS Float (浮动)的使用方法

Float&#xff08;浮动&#xff09;概念 CSS的Float&#xff08;浮动&#xff09;&#xff0c;会使得元素向左或者向右移动&#xff0c;其它周围元素也会重新排列。 Float浮动&#xff0c;往往是用于图像&#xff0c;但它的布局一样非常有效。 元素如何浮动 元素的水平方向…

【MySQL架构篇】MySQL字符集、大小写规范及默认数据库

文章目录 1. 字符集与字符集比较规则2. 大小写规范3. 默认数据库4. 与文件系统相关 1. 字符集与字符集比较规则 MySQL有4个级别的字符集和比较规则&#xff0c;分别是 服务器级别数据库级别表级别列级别 当创建对应表或列未指定字符集时&#xff0c;默认会取其上一级别的字符…

MySQL中的表操作,配置文件,储存引擎,数据类型

MySQL中的表操作 1 查库&#xff08;已密码登陆mysql&#xff09; show databases; 2 添加库 create database t1; 3 表操作 1选定操作库 use t1 2在库里添加表格式 create table t1(id int, name varchar(32), gender varchar(32),age int); 3往表里添加具体元素 insert…

OpenCV 笔记(2):图像的属性以及像素相关的操作

Part11. 图像的属性 11.1 Mat 的主要属性 在前文中&#xff0c;我们大致了解了 Mat 的基本结构以及它的创建与赋值。接下来我们通过一个例子&#xff0c;来看看 Mat 所包含的常用属性。 先创建一个 3*4 的四通道的矩阵&#xff0c;并打印出其相关的属性&#xff0c;稍后会详细…

django建站过程(2)创建第一个应用程序页面

创建第一个应用程序页面 设置第一个页面【settings.py,urls.py,views.py】settings.pyurls.pyviews.py django是由一系列应用程序组成&#xff0c;协同工作&#xff0c;让项目成为一个整体。前面已创建了一个应用程序baseapp,使用的命令 python manage.py startapp baseapps这…

2023全新小程序广告流量主奖励发放系统源码 流量变现系统

2023全新小程序广告流量主奖励发放系统源码 流量变现系统 分享软件&#xff0c;吃瓜视频&#xff0c;或其他资源内容&#xff0c;通过用户付费买会员来变现&#xff0c;用户需要付费&#xff0c;有些人喜欢白嫖&#xff0c;所以会流失一部分用户&#xff0c;所以就写了这个系统…

RustCC分享会|非凸科技与开发者共同探讨Rust安全进化

10月15日&#xff0c;非凸科技受邀参加RustCC联合多家开发者社区组织的Global Tour of Rust技术分享活动&#xff0c;旨在为Rust开发者提供交流互动的平台&#xff0c;分享Rust语言的知识、经验和最佳实践。 活动上&#xff0c;非凸科技成都分公司研发总监赵海峰以“Rust安全进…

系统架构师备考倒计时13天(每日知识点)

1. 数据仓库四大特点 面向主题的。操作型数据库的数据组织面向事务处理任务&#xff0c;各个业务系统之间各自分离&#xff0c;而数据仓库中的数据是按照一定的主题域进行组织的。集成的。数据仓库中的数据是在对原有分散的数据库数据抽取、清理的基础上经过系统加工、汇总和整…

TechSmith Camtasia 2023 for Mac 屏幕录像视频录制编辑软件

​ TechSmith Camtasia for Mac 2023中文破解版 是一款专业的屏幕录像视频录制编辑软件&#xff0c;非常容易就可以获得精彩的截屏视频。创建引人注目的培训&#xff0c;演示和演示视频。Camtasia 屏幕录制软件简化&#xff0c;直观&#xff0c;让您看起来像专业人士。利用Camt…

安卓使用android studio跨进程通信之AIDL

我写这篇文章不想从最基础的介绍开始,我直接上步骤吧. 1.创建服务端 1.1:创建服务端项目:我的as版本比较高,页面就是这样的 1.2:创建AIDL文件,右键项目,选中aidl aidl名字可以自定义也可以默认 basicTypes是自带的,可以删掉,也可以不删,然后把你自己所需的接口写上去 1.3:创建…

让uniGUI支持https

今天在专家的帮助下&#xff0c;成功的让uniGUI支持https了。 首先&#xff0c;去申请个**的证书。我同事去阿里申请的&#xff0c;申请回是一个zip文件&#xff0c;里面有两个文件&#xff0c;一个扩展是per&#xff0c;一个key 然后&#xff0c;把这两个证书文件放到uniGUI…

06、Python 序列 与 列表 与 元组 的关系和创建 和 简单使用

目录 序列元组与列表关系总结 创建元组与列表方式一创建元组注意点 创建元组与列表方式二简单使用通过索引访问元素子序列序列加法序列乘法in运算 了解Python序列 创建列表和元组 通过索引访问元素 子序列 序列运算 序列 所谓序列&#xff0c;指的是一种包含多项数据的数据结…

【蓝桥每日一题]-动态规划 (保姆级教程 篇11)#方格取数2.0 #传纸条

目录 题目&#xff1a;方格取数 思路&#xff1a; 题目&#xff1a;传纸条 思路&#xff1a; 题目&#xff1a;方格取数 &#xff08;跑两次&#xff09; 思路&#xff1a; 如果记录一种方案后再去跑另一个方案&#xff0c;影响因素太多了&#xff0c;所以两个方案要同时开…

FL Studio 21 for Mac中文破解版百度网盘免费下载安装激活

FL Studio 21 for Mac中文破解版是Mac系统中的一款水果音乐编辑软件&#xff0c;提供多种插件&#xff0c;包括采样器、合成器和效果器&#xff0c;可编辑不同风格的音乐作品&#xff0c;Pattern/Song双模式&#xff0c;可兼容第三方插件和音效包&#xff0c;为您的创意插上翅膀…

Unity3D 基础——鼠标悬停更改物体颜色,移走恢复

方法介绍 【unity学习笔记】OnMouseEnter、OnMouseOver、OnMouseExit_unity onmouseover_一白梦人的博客-CSDN博客https://blog.csdn.net/a1208498468/article/details/117856445 GetComponent()详解_getcomponet<> 动态名称-CSDN博客https://blog.csdn.net/kaixindrag…

uniapp 测试 app 到安卓模拟器部署方法以及常见错误解决 无废话

uniapp 测试 app 到安卓模拟器 1.1 安装安卓模拟器 https://www.yeshen.com/ 1.2 查看安装模拟器端口 右击夜神模拟器属性打开文件位置 在打开的文件夹找到 debugReport 双击运行查看运行出来的端口号 一般都是&#xff1a;62001 1.3 HBuilder 配置 选中项目运行运行到手机…

自然语言处理---Transformer机制详解之ELMo模型介绍

1 ELMo简介 ELMo是2018年3月由华盛顿大学提出的一种预训练模型. ELMo的全称是Embeddings from Language Models.ELMo模型的提出源于论文<< Deep Contextualized Word Representations >>.ELMo模型提出的动机源于研究人员认为一个好的预训练语言模型应该能够包含丰…