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