权限校验机制
温馨提示
在这里,你将系统学习了解 MemorySearch 忆搜阁
中权限校验机制
的实际运用场景
# 🍚 为什么要实现权限校验
权限校验在接口设计中是非常重要的,主要有以下几个原因:
数据安全性:权限校验可以确保只有授权的用户或系统可以访问特定的接口和数据。这可以防止未经授权的访问和潜在的数据泄露。
业务逻辑保护:某些接口可能涉及到敏感的业务逻辑或数据操作,如修改用户信息、删除订单等。权限校验可以确保只有具备相应权限的用户或系统才能执行这些操作,从而保护业务逻辑的正常运行。
系统稳定性:恶意用户或系统可能会通过频繁调用接口来攻击系统,导致系统资源耗尽或性能下降。通过权限校验,可以限制非法用户的访问,提高系统的稳定性和可用性。
合规性要求:在很多行业和地区,都有相应的数据保护和隐私法规。权限校验可以帮助企业满足这些法规要求,避免因不合规操作而面临的法律风险。
在实现权限校验时,通常会采用鉴权机制,如 OAuth、JWT 等。这些机制可以通过令牌、角色、权限点等方式来实现对用户的身份认证和权限控制。同时,结合 IP 白名单、数据加密等安全措施,可以进一步提高接口的安全性。
# 🍜 权限校验运用
# 🍣 Vuex 实现状态管理
🍚 推荐阅读:
# 🍛 Spring AOP 实现权限校验
在业务实践中,由于用户权限的限制,我们需要对普通用户和管理员执行的操作进行精细化管理。这种权限校验不仅有助于提升系统的安全性,还能确保数据的完整性和隐私性。在 Spring Boot 框架中,我们可以利用 AOP(面向切面编程)来实现这一需求。
AOP 允许程序员定义“切面”,这些切面可以在方法的调用之前、之后或者在异常抛出时执行特定的代码逻辑。通过在切面中加入权限校验逻辑,我们可以判断执行者是否具有管理员权限。如果执行者是管理员,则方法正常执行;否则,方法将不会执行。
这种方法不仅技术上先进,而且能有效地管理不同用户的操作权限,进一步提升了系统的安全性、稳定性和可维护性。
# 权限校验
- 自定义
AuthCheck
注解:
/**
* 权限校验
*
* @author <a href="https://gitee.com/deng-2022">回忆如初</a>
* @from <a href="https://deng-2022.gitee.io/blog/">Memory's Blog</a>
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthCheck {
/**
* 必须有某个角色
*
* @return 用户权限
*/
String mustRole() default "";
}
- 导入相关依赖坐标:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 基于注解的方法匹配器,实现权限校验,这个切面仅应用于带有
AuthCheck
注解的方法:
@Around("@annotation(authCheck)")
public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
String mustRole = authCheck.mustRole();
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
// 当前登录用户
User loginUser = userService.getLoginUser(request);
// 必须有该权限才通过
if (StringUtils.isNotBlank(mustRole)) {
UserRoleEnum mustUserRoleEnum = UserRoleEnum.getEnumByValue(mustRole);
if (mustUserRoleEnum == null) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
String userRole = loginUser.getUserRole();
// 如果被封号,直接拒绝
if (UserRoleEnum.BAN.equals(mustUserRoleEnum)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
// 必须有管理员权限
if (UserRoleEnum.ADMIN.equals(mustUserRoleEnum)) {
if (!mustRole.equals(userRole)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
}
}
// 通过权限校验,放行
return joinPoint.proceed();
}
在构建一个健壮的系统时,全局请求处理和日志记录是不可或缺的。通过记录每一个请求和响应的详细信息,我们可以更好地理解系统的运行状况,及时发现并解决潜在的问题。
在专业编程实践中,我们可以利用中间件或者拦截器来实现全局请求处理。这种设计模式允许我们在请求进入系统之前或者响应离开系统之后,插入自定义的处理逻辑。对于日志记录,我们通常会选择一个成熟的日志框架,如Log4j
或SLF4J
,来记录详细的请求和响应信息。
这样的设计不仅可以提高系统的可维护性,还能在出现问题时提供宝贵的调试信息。同时,全局的日志记录也有助于我们分析和优化系统的性能。
# 全局请求拦截
- 导入相关依赖坐标:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
- 基于包的方法匹配器,表示这个切面应用于
com.memory.oj.controller
包下所有类的所有方法:
@Around("execution(* com.memory.oj.controller.*.*(..))")
public Object doInterceptor(ProceedingJoinPoint point) throws Throwable {
// 计时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 获取请求路径
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
// 生成请求唯一 id
String requestId = UUID.randomUUID().toString();
// 获取请求路径
String url = httpServletRequest.getRequestURI();
// 获取请求来源地址
String host = httpServletRequest.getRemoteHost();
// 获取请求参数
Object[] args = point.getArgs();
String reqParam = "[" + StringUtils.join(args, ", ") + "]";
// 输出请求日志
log.info("request start,id: {}, path: {}, ip: {}, params: {}", requestId, url,
host, reqParam);
// 执行原方法
Object result = point.proceed();
// 输出响应日志
stopWatch.stop();
long totalTimeMillis = stopWatch.getTotalTimeMillis();
// 输出耗时
log.info("request end, id: {}, cost: {}ms", requestId, totalTimeMillis);
return result;
}
使用 Spring AOP 和自定义注解,可以实现权限校验和全局请求拦截:
通过在方法上添加自定义的 AuthCheck 注解,可以指定该方法所需的用户权限。然后,通过定义一个切面(Aspect),并在其中实现权限校验的逻辑,可以在方法执行前进行权限检查。如果当前登录用户具有所需的权限,则允许方法继续执行;否则,抛出相应的异常或进行其他处理。
此外,还可以利用 Spring AOP 实现全局请求拦截。通过定义一个切面,并使用@Around 注解来匹配所有控制器类中的方法,可以在请求进入系统之前或响应离开系统之后插入自定义的处理逻辑。例如,可以记录每个请求的详细信息,包括请求路径、参数、来源地址等,以及请求的耗时。这些信息对于调试和优化系统性能非常有帮助。
综上所述,通过结合 Spring AOP 和自定义注解,可以实现权限校验
和全局请求拦截
的功能。这种设计模式可以提高系统的可维护性和可靠性,同时为开发人员提供了更多的灵活性和控制能力。
# 🥘 Filter 实现全局请求拦截
Spring Filter 是 Spring 框架中用于拦截 HTTP 请求和响应的组件。它可以用于实现全局请求拦截,如身份验证、授权、日志记录、请求/响应修改和性能优化等。通过实现 Filter 接口或继承 OncePerRequestFilter 类,可以轻松地将自定义逻辑应用于所有请求,从而增强应用程序的功能和性能。
@Component: 这是一个 Spring 框架的注解,用于自动检测并注册这个类作为 Spring 容器中的一个 bean。这意味着 Spring 会管理这个类的生命周期,并在需要的时候自动注入依赖。
@Slf4j: 这是 Lombok 库提供的一个注解,用于在类中自动生成一个 SLF4J 的 Logger 实例。这使得在类中记录日志变得非常简单,无需手动创建 Logger 对象。
LoginCheckFilter 类实现了 Filter 接口,这意味着它必须提供 doFilter 方法的实现。Filter 接口是 Java Servlet API 的一部分,用于处理进入和离开 Servlet 容器的请求和响应。
在 doFilter 方法中,首先通过类型转换获取了 HttpServletRequest 和 HttpServletResponse 对象。这两个对象分别代表 HTTP 请求和响应,提供了访问请求参数、头信息、会话信息等的方法,以及设置响应状态、头信息、输出内容等的功能。
@Component
@Slf4j
public class LoginCheckFilter implements Filter {// 验证是否登录
// 路径匹配器,支持通配符
public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
...........................
}
}
使用 AntPathMatcher 类来检查请求的 URI 是否需要登录检查。AntPathMatcher 支持 Ant 风格的路径模式,如使用 * 作为通配符。这允许开发者定义复杂的路径匹配规则。
定义了一个 urls 数组,列出了不需要进行登录检查的路径。这些通常是公开可访问的接口,如登录接口本身、API 文档等。 如果请求的 URI 需要登录检查,则从 HTTP 会话(session)中获取用户登录状态。通常,用户登录后,其身份信息(如用户 ID)会被存储在会话中。
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 1.获取本次请求的URI
String requestURI = request.getRequestURI();
// 定义不需要处理的请求路径
String[] urls = new String[]{
"/api/user/login", // 登录时
"/api/user/get/login",
"/api/v2/api-docs",
"/api/search/all"
};
如果用户已登录(即会话中存在用户信息),则获取用户 ID,并将其存储在 ThreadLocal 变量中,以便在后续的请求处理中能够方便地获取当前用户的 ID。
如果用户未登录(即会话中没有用户信息),则通过输出流向客户端响应一个 JSON 格式的错误消息,表明用户未授权。这通常会导致前端页面显示一个登录提示或重定向到登录页面。
在过滤器的不同部分,通过 log 对象记录了相应的日志信息。这有助于调试和监控过滤器的行为,例如记录哪些请求被放行、哪些请求因为用户未登录而被拒绝等。
// 2.判断本次请求是否需要处理
boolean check = check(urls, requestURI);
// 3.如果不需要处理,则直接放行
if (check) {
log.info("本次请求不需要处理...");
filterChain.doFilter(request, response);// 放行请求
return;
}
// 4.需要处理的用户的请求,则判断登录状态,如果已经登录,则直接放行
Long userId;
User currentUser = (User) request.getSession().getAttribute(USER_LOGIN_STATE);
if (currentUser != null) {// Session中存储着用户id(登录成功)
userId = currentUser.getId();
log.info("该用户已登录, id为{}", userId);
BaseContext.setCurrentId(userId);// ThreadLocal
filterChain.doFilter(request, response);// 放行请求
return;
}
// 5.如果未登录,则返回登录结果,通过输出流方式向客户端页面响应数据
log.info("该用户未登录...");
response.getWriter().write(JSON.toJSONString(ResultUtils.error(ErrorCode.NO_AUTH_ERROR)));
- 使用 AntPathMatcher 进行路径匹配相对于简单的字符串比较来说可能稍微慢一些,但在需要支持通配符匹配的场景下,这是一个合理的选择。
/**
* 路径匹配,检查本次请求是否需要放行
*
* @param urls :已定义的不需要处理的请求
* @param requestURI :接收检查的请求
* @return
*/
public boolean check(String[] urls, String requestURI) {
for (String url : urls) {
boolean match = PATH_MATCHER.match(url, requestURI);
if (match) {// 该请求不需要处理
return true;
}
}
return false;// 该请求得处理
}
每次请求都需要从会话中检索用户状态,这确实会增加一些处理时间。然而,由于登录检查是安全机制的一部分,这种开销是必要的。
这个登录检查过滤器是一个典型的 Web 应用安全特性,用于确保只有已登录的用户才能访问受保护的资源。它利用了 Spring 框架、Lombok 库和 Java Servlet API 等技术,实现了简洁而有效的功能。通过合理的日志记录和性能考虑,这个过滤器既易于维护,又能满足实际应用的需求。