权限认证下的Nacos SpringBoot
在此我们假设你已经成功启动了nacos(无论是standalone亦或是cluster模式),并且开启了权限校验 nacos.core.auth.enabled=true,如果您对权限校验还不明了,可以翻看 从nacos的权限控制看nacos的配置热加载 。
这里我们开始使用客户端来开启nacos使用之旅。好吧,先提前告知,一切并不顺利,因为我们盲目的去除了客户端依赖的fastjson…
基于nacos-1.3.3
Nacos SpringBoot Demo
先上代码,再逐步解析吧
假设我们的配置是这样的:
- appliaction.yml
spring:
application:
name: helloNacos
server:
port: 9001
nacos:
config:
# nacos服务器的地址及端口
server-addr: xxxx:xx
# 配置的命名空间
namespace: xxxxxx
# 因为服务端开启了权限认证了,所以需要提供用户名和密码
username: nacos
password: nacos
# 分组
group: staging
data-id: helloNacos.properties
# 配置的类型(ConfigType: properties, xml, json, text, html, yaml)
type: properties
auto-refresh: true
- HelloController.java
import com.alibaba.nacos.api.config.annotation.NacosValue;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
public class HelloController {
@NacosValue(value = "${name:xxx}", autoRefreshed = true)
private String name;
@RequestMapping(path = "/test", method = RequestMethod.GET)
@ResponseBody
public String hello(@RequestParam(value = "greets") String greets) {
return "hello, " + name;
}
}
- HelloNacosApplication.java
import com.alibaba.nacos.spring.context.annotation.config.NacosPropertySource;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
// 这个注解即为开启nacos的钥匙
@NacosPropertySource(dataId = "helloNacos.properties", groupId = "staging", autoRefreshed = true)
public class HelloNacosApplication {
public static void main(String[] args) {
SpringApplication.run(HellomiceApplication.class, args);
}
}
- pom.xml
<properties>
<spring-cloud.version>Greenwich.SR6</spring-cloud.version>
<nacos-config-spring-boot.version>0.2.7</nacos-config-spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>nacos-config-spring-boot-starter</artifactId>
<version>${nacos-config-spring-boot.version}</version>
<exclusions>
<!-- 注意这里排除了fastjson,因为由于安全漏洞我们已经不再支持fastjson -->
<exclusion>
<artifactId>fastjson</artifactId>
<groupId>com.alibaba</groupId>
</exclusion>
<exclusion>
<artifactId>nacos-spring-context</artifactId>
<groupId>com.alibaba.nacos</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 先别惊讶这里的多此一举操作 -->
<!-- 排除nacos-spring-context,并重新引入新包 -->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-spring-context</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<artifactId>nacos-client</artifactId>
<groupId>com.alibaba.nacos</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 排除nacos-client,并重新引入新包 -->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.3.3</version>
</dependency>
</dependencies>
ps:
如果你不介意使用fastjson,那么参考官方示例,只需依赖合适版本的 nacos-config-spring-boot-starter ,当然也就不会出现下文所说的哪些问题了…
nacos客户端鉴权及getConfig流程
nacos通过NacosConfigService类来对配置进行查询修改等,nacos client获取配置是通过Http向server端发送请求,于是NacosConfigService将这个工作交给了ClientWorker, ClientWorker又将这份工作委托给了HttpAgent:
private HttpAgent agent;
public NacosConfigService(Properties properties) throws NacosException {
...
agent = new MetricsHttpAgent(new ServerHttpAgent(properties));
agent.start();
worker = new ClientWorker(agent, configFilterChainManager, properties);
}
ServerHttpAgent在创建时,就会调用登录接口,登录成功后会更新SecurityProxy#accessToken,同时还会有个定时任务调用login方法来更新accessToken;
这个accessToken将包含用户的登录信息,用来在服务端进行权限验证;
public ServerHttpAgent(Properties properties) throws NacosException {
...
// 先登录
securityProxy.login(serverListMgr.getServerUrls());
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(....);
// 定时更新token
executorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
securityProxy.login(serverListMgr.getServerUrls());
}
}, 0, securityInfoRefreshIntervalMills, TimeUnit.MILLISECONDS);
}
当查询当前namespace及group的配置时,会通过http调用"/nacos/v1/cs/configs"接口,从服务端的代码可知这个接口需要进行权限校验;
/nacos/v1/cs/configs接口的实现, 需要有用户具有READ权限
@GetMapping
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public void getConfig(HttpServletRequest request, HttpServletResponse ...)
那么校验就需要accessToken,这个token就需要在客户端调用时注入到header中,具体注入的地方是在:
ServerHttpAgent#injectSecurityInfo
private void injectSecurityInfo(List<String> params) {
if (StringUtils.isNotBlank(securityProxy.getAccessToken())) {
params.add(Constants.ACCESS_TOKEN);
params.add(securityProxy.getAccessToken());
}
...
}
那么在原生的0.2.7版本的 nacos-config-spring-boot-starter中登录的实现是:
SecurityProxy#login(String server)
HttpClient.HttpResult result = HttpClient.request(url, new ArrayList<String>(2),
params, body, Charsets.UTF_8.name(), HttpMethod.POST);
if (result.code != HttpURLConnection.HTTP_OK) {
SECURITY_LOGGER.error("login failed: {}", JSON.toJSONString(result));
return false;
}
// !!! com.alibaba.fastjson.JSONObject;
JSONObject obj = JSON.parseObject(result.content);
if (obj.containsKey(Constants.ACCESS_TOKEN)) {
accessToken = obj.getString(Constants.ACCESS_TOKEN);
tokenTtl = obj.getIntValue(Constants.TOKEN_TTL);
tokenRefreshWindow = tokenTtl / 10;
}
getConfig调用返回403
开启debug模式启动HelloNacosApplication会发现登录,获取accessToken的登录接口调用成功,但是调用 /nacos/v1/cs/configs 接口时一直抛出异常,实际的返回码是403,拒绝访问,在nacos中导致403通常是权限用户没登录导致的。
增加断点发现:在登陆接口中 http的返回结果中 确实是返回了token,但是在 ServerHttpAgent#injectSecurityInfo中securityProxy.getAccessToken() 返回空…
这是由于fastjson反序列化失败导致的,这是由于事先在我们在依赖中排除fastjson(因为我的私有仓库已经找不到fastjson了)
JSONObject obj = JSON.parseObject(result.content);
而且的调用login方法的地方catch了这个异常,并且没给任何提示。所以我们什么也感觉不到,明明是调用login接口成功了,但是在后续的使用中就是拿不到accessToken
怎样替换fastjson
事实上,fastjson是由nacos-client引入的,而nacos-client是在nacos-spring-context中引入的,那就尝试升级nacos-spring-context吧,最新版本是1.0.0,其中对应的nacos-client版本为1.3.2,启动后会发现这个版本虽然将fastjosn替换为Jackson了,但是返回结果乱码,依然会反序列化失败;于是把nacos-client单独升级到1.3.3这个版本,对请求的编码做了处理,可以正常返回。
ps:为什么会想到升级nacos-client了,怎么就知道新版nacos-client替换了fastjson了?
因为实现尝试过nacos spring cloud,那个版本完全没有问题,而其中的nacos-client 版本恰好是1.3.3,并且使用Jackson反序列化结果。