需求描述:
最近需要实现一个功能,token
验证参数可以动态配置化,即在不关闭应用的前提下实现token
验证功能的关闭和开启功能,于是想到了使用分布式配置中心Nacos,如何在Filter
中获取Nacos配置中心的参数?
在功能修改过程中出现了各种问题,如果不想关注中间过程可以采用最终版本
,可以实现Filter中动态获取参数的功能。
原配置参数如下
token:
enable: true
urlPatterns: /*
excluded:
paths: /list1,/test1,/test2
原代码
Token配置类
@Configuration
public class TokenConfig {
public static final String TOKEN_ID = "localUUID";
public static final String PLAN_DETAIL_UUID = "planDetailUuid";
public static final String TOKEN_EXCLUDED_PATHS = "TOKEN_EXCLUDED_PATHS";
public static final String TOKEN_ENABLE = "TOKEN_ENABLE";
public static final String TOKEN_NAME = "MOBILE_TOKEN";
@Value("${token.urlPatterns}")
private String urlPatterns;
@Value("${token.excluded.paths}")
private String excludedPaths;
@Value("${token.enable}")
private String tokenEnable;
@Autowired
private TokenLoginHelper tokenLoginHelper;
@Bean
public FilterRegistrationBean tokenFilterRegistration() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setDispatcherTypes(DispatcherType.REQUEST);
registrationBean.setFilter(new TokenFilter(tokenLoginHelper));
registrationBean.addUrlPatterns(StringUtils.split(urlPatterns, ",") == null ? new String[]{urlPatterns} : StringUtils.split(urlPatterns, ","));
registrationBean.setName("TokenFilter");
registrationBean.setOrder(9999);
Map<String, Object> initParameters = new HashMap<>();
initParameters.put(TokenConfig.TOKEN_EXCLUDED_PATHS, excludedPaths);
initParameters.put(TokenConfig.TOKEN_ENABLE, tokenEnable);
registrationBean.setInitParameters(initParameters);
return registrationBean;
}
}
TokenFilter过滤器如下
/**
* h5二维码token验证
*/
public class TokenFilter extends HttpServlet implements Filter {
private TokenLoginHelper tokenLoginHelper;
private static Logger logger = LoggerFactory.getLogger(TokenFilter.class);
private static final AntPathMatcher antPathMatcher = new AntPathMatcher();
private String excludedPaths;
private String tokenEnable;
public TokenFilter(TokenLoginHelper tokenLoginHelper) {
this.tokenLoginHelper = tokenLoginHelper;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
tokenEnable = filterConfig.getInitParameter(TokenConfig.TOKEN_ENABLE);
excludedPaths = filterConfig.getInitParameter(TokenConfig.TOKEN_EXCLUDED_PATHS);
logger.info("TokenFilter init.");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
// make url
String servletPath = req.getServletPath();
if (!"true".equals(tokenEnable)) {
chain.doFilter(request, response);
return;
}
// excluded path check
if (excludedPaths != null && excludedPaths.trim().length() > 0) {
for (String excludedPath : excludedPaths.split(",")) {
String uriPattern = excludedPath.trim();
// 支持ANT表达式
if (antPathMatcher.match(uriPattern, servletPath)) {
// excluded path, allow
chain.doFilter(request, response);
return;
}
}
}
// mobile token filter
boolean flag = tokenLoginHelper.tokenCheck(req);
if (!flag) {
// response
res.setStatus(HttpServletResponse.SC_OK);
res.setContentType("application/json;charset=UTF-8");
res.getWriter().println("{\"code\":501, \"msg\":\"未知错误\"}");
return;
}
// already login, allow
chain.doFilter(request, response);
return;
}
}
第一版修改
按照官网Nacos的用法
1.对 TokenConfig
进行改造,增加@RefreshScope
注解,可以对@Value(${...})
参数实时刷新
@Configuration
@RefreshScope
public class TokenConfig {
...
2.引入相关的依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
3.修改bootstrap.yml
文件
spring:
application:
name: eval-zjiaomobile
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
group: zjiao_group
但是发现TokenConfig
上增加@RefreshScope
注解之后,FilterRegistrationBean
注入失效,过滤器不生效
@Bean
public FilterRegistrationBean tokenFilterRegistration() {
...
如果不使用@RefreshScope
注解,Filter过滤器正常工作,但是没有实时刷新效果。
第二版修改
分为两个类来处理,TokenConfig
类独立实时刷新,而FilterRegistrationBean
在其他类中注入,保证两个过程不冲突
@Configuration
@RefreshScope
public class TokenConfig {
public static final String TOKEN_ID = "localUUID";
public static final String PLAN_DETAIL_UUID = "planDetailUuid";
public static final String TOKEN_EXCLUDED_PATHS = "TOKEN_EXCLUDED_PATHS";
public static final String TOKEN_ENABLE = "TOKEN_ENABLE";
public static final String TOKEN_NAME = "MOBILE_TOKEN";
@Value("${token.urlPatterns}")
private String urlPatterns;
@Value("${token.excluded.paths}")
private String excludedPaths;
@Value("${token.enable}")
private String tokenEnable;
public String getUrlPatterns() {
return urlPatterns;
}
public String getExcludedPaths() {
return excludedPaths;
}
public String getTokenEnable() {
return tokenEnable;
}
}
TokenFilterBeanConfig 分离出来,只做FilterBean的配置
@Configuration
public class TokenFilterBeanConfig {
@Autowired
private TokenLoginHelper tokenLoginHelper;
@Autowired
private TokenConfig tokenConfig;
@Bean
public FilterRegistrationBean tokenFilterRegistration() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setDispatcherTypes(DispatcherType.REQUEST);
registrationBean.setFilter(new TokenFilter(tokenLoginHelper));
registrationBean.addUrlPatterns(StringUtils.split(tokenConfig.getUrlPatterns(), ",") == null ?
new String[]{tokenConfig.getUrlPatterns()} : StringUtils.split(tokenConfig.getUrlPatterns(), ","));
registrationBean.setName("TokenFilter");
registrationBean.setOrder(9999);
Map<String, Object> initParameters = new HashMap<>();
initParameters.put(TokenFilterBeanConfig.TOKEN_EXCLUDED_PATHS, tokenConfig.getExcludedPaths());
initParameters.put(TokenFilterBeanConfig.TOKEN_ENABLE, tokenConfig.getTokenEnable());
registrationBean.setInitParameters(initParameters);
return registrationBean;
}
}
发现Filter
中永远只会使用第一次初始化的FilterRegistrationBean
,参数不会实时更新,因为Bean只会初始化一次。
第三版修改
在Filter
过滤器中进行实时刷新,取到的值都是null
,部分代码如下
@Component
@RefreshScope
public class TokenFilter extends HttpServlet implements Filter {
@Value("${token.excluded.paths}")
private String excludedPaths;
@Value("${token.enable}")
private String tokenEnable;
...
最终版本(问题解决)
1.必须在每次访问接口的时候,动态的去获取
${token.enable}
的值,所以必须是在Filter
的doFilter
方法中去获取。
2.通过Environment
对象获取token.enable
的值
3.实现EnvironmentAware
接口,可以在Spring
初始化过程中获取到Environment
对象
TokenConfig
实时刷新参数不变
@Configuration
@RefreshScope
public class TokenConfig {
...
@Value("${token.excluded.paths}")
private String excludedPaths;
@Value("${token.enable}")
private String tokenEnable;
...
在Filter
使用Environment
对象获取token.enable
的值,这一点是受官网测试代码的启发String userName = applicationContext.getEnvironment().getProperty("user.name");
最终代码如下:
@Component
public class TokenFilter extends HttpServlet implements Filter, EnvironmentAware {
private static Environment environment;
private TokenLoginHelper tokenLoginHelper;
private static Logger logger = LoggerFactory.getLogger(TokenFilter.class);
private static final AntPathMatcher antPathMatcher = new AntPathMatcher();
public TokenFilter(TokenLoginHelper tokenLoginHelper) {
this.tokenLoginHelper = tokenLoginHelper;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
logger.info("TokenFilter init.");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
// make url
String servletPath = req.getServletPath();
String tokenEnable = environment.getProperty("token.enable");
String excludedPaths = environment.getProperty("token.excluded.paths");
System.out.println("tokenEnable>>>>>>>>>>>>>>>>>>>>>"+tokenEnable);
System.out.println("excludedPaths>>>>>>>>>>>>>>>>>>>>>"+excludedPaths);
if (!"true".equals(tokenEnable)) {
chain.doFilter(request, response);
return;
}
// excluded path check
if (excludedPaths != null && excludedPaths.trim().length() > 0) {
for (String excludedPath : excludedPaths.split(",")) {
String uriPattern = excludedPath.trim();
// 支持ANT表达式
if (antPathMatcher.match(uriPattern, servletPath)) {
// excluded path, allow
chain.doFilter(request, response);
return;
}
}
}
// mobile token filter
boolean flag = tokenLoginHelper.tokenCheck(req);
if (!flag) {
// response
res.setStatus(HttpServletResponse.SC_OK);
res.setContentType("application/json;charset=UTF-8");
res.getWriter().println("{\"code\":501, \"msg\":\"无权限访问\"}");
return;
}
// already login, allow
chain.doFilter(request, response);
return;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}
测试截图
filter
中打印信息
前端效果
修改nacos
中的配置信息,false
写错了不好意思
修改之后
控制台实时打印,因为nacos
采用的是一种特殊的push
机制,所以只需要修改配置信息,就会实时推送到客户端。
再访问该接口