1. 前言
通过配置文件实现动态登录拦截的好处有:
-
可以实现灵活的权限控制:通过在配置文件中配置需要拦截的URL,可以灵活地控制哪些URL需要进行登录拦截。不需要修改代码,只需要修改配置文件即可实现权限控制。
-
可以快速响应变化:由于配置文件可以实现动态加载,当需要修改登录拦截的URL时,只需要修改配置文件,然后重新加载即可,不需要重启应用,可以快速响应变化。
-
可以集中管理权限配置:通过在配置文件中集中管理登录拦截的URL,可以方便地管理各个微服务的权限配置。不需要在每个微服务中分别配置登录拦截的URL,只需要在配置中心统一管理即可。
-
可以实现微服务的解耦:通过使用配置文件,可以将登录拦截的配置和具体的微服务解耦。不需要在每个微服务中编写相同的登录拦截逻辑,只需要在配置中心进行配置即可。这样可以减少代码的重复性,提高开发效率。
-
可以方便地进行测试和部署:由于登录拦截的配置是通过配置文件进行管理的,可以方便地进行测试和部署。可以通过不同的配置文件设置不同的拦截规则,方便地进行测试和部署不同的环境。
2. 代码实现
2.1 导入依赖
<!--configuration-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--hutool工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.17</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
2.2 创建读取拦截规则的配置类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
@Data
@ConfigurationProperties(prefix = "demo.auth.resource")
public class ResourceAuthProperties {
/**
* 是否开启登录拦截功能,如果开启则需要指定拦截路径,默认拦截所有
*/
private Boolean enable = false;
/**
* 要拦截的路径,例如:/user/**
*/
private List<String> includeLoginPaths;
/**
* 不拦截的路径,例如:/user/**
*/
private List<String> excludeLoginPaths;
}
2.3 创建存放 ThreadLocal 实体类
public class UserContext {
private static final ThreadLocal<Long> TL = new ThreadLocal<>();
/**
* 保存用户信息
* @param userId 用户id
*/
public static void setUser(Long userId){
TL.set(userId);
}
/**
* 获取用户
* @return 用户id
*/
public static Long getUser(){
return TL.get();
}
/**
* 移除用户信息
*/
public static void removeUser(){
TL.remove();
}
}
2.4 创建拦截器拦截请求头中的用户信息并存放到 ThreadLocal 中
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class UserInfoInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.尝试获取头信息中的用户信息
String authorization = request.getHeader("user-info");
// 2.判断是否为空
if (authorization == null) {
return true;
}
// 3.转为用户id并保存
try {
Long userId = Long.valueOf(authorization);
UserContext.setUser(userId);
return true;
} catch (NumberFormatException e) {
log.error("用户身份信息格式不正确,{}, 原因:{}", authorization, e.getMessage());
return true;
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清理用户信息
UserContext.removeUser();
}
}
2.5 创建拦截器用于登录校验
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class LoginAuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.尝试获取用户信息
Long userId = UserContext.getUser();
// 2.判断是否登录
if (userId == null) {
response.setStatus(401);
response.sendError(401, "未登录用户无法访问!");
// 2.3.未登录,直接拦截
return false;
}
// 3.登录则放行
return true;
}
}
2.6 根据配置类的规则注册两个拦截器
import cn.hutool.core.collection.CollUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableConfigurationProperties(ResourceAuthProperties.class)
public class ResourceInterceptorConfiguration implements WebMvcConfigurer {
private final ResourceAuthProperties authProperties;
@Autowired
public ResourceInterceptorConfiguration(ResourceAuthProperties resourceAuthProperties) {
this.authProperties = resourceAuthProperties;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 1.添加用户信息拦截器
registry.addInterceptor(new UserInfoInterceptor()).order(0);
// 2.是否需要做登录拦截
if(!authProperties.getEnable()){
// 无需登录拦截
return;
}
// 2.添加登录拦截器
InterceptorRegistration registration = registry.addInterceptor(new LoginAuthInterceptor()).order(1);
// 2.1.添加拦截器路径
if(CollUtil.isNotEmpty(authProperties.getIncludeLoginPaths())){
registration.addPathPatterns(authProperties.getIncludeLoginPaths());
}
// 2.2.添加排除路径
if(CollUtil.isNotEmpty(authProperties.getExcludeLoginPaths())){
registration.excludePathPatterns(authProperties.getExcludeLoginPaths());
}
// 2.3.排除swagger路径
registration.excludePathPatterns(
"/v2/**",
"/v3/**",
"/swagger-resources/**",
"/webjars/**",
"/doc.html"
);
}
}
2.7 将 ResourceInterceptorConfiguration 类实现自动配置
方式一:在 resources 目录下创建 META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.demo.authsdk.resource.config.ResourceInterceptorConfiguration
方式二: 在 resources 目录下创建 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.demo.authsdk.resource.config.ResourceInterceptorConfiguration
2.8 8. 在 application.yml 添加规则
例如:
- 除了 /user/login 其他都拦截
demo:
auth:
resource:
# 是否拦截
enable: true
# 不拦截路径
exclude-login-paths: /user/login
- 除了 /user/login 其他都不拦截
demo:
auth:
resource:
# 是否拦截
enable: false
# 拦截的路径
include-login-paths: /user/login