在开发中遇到了一个需求:创建了ldap用户后需要自动同步到hue中.查看官方文档后可以使用http的方式请求hue的接口,同步ldap用户.
记录一下本次接口调用的坑,及解决方法
一,hue的登陆
hue的相关接口请求需要在同一个session中完成,即需要先进行登陆操作后,才能进行后续的接口访问,不然就会报403或者login require
hue的登陆以及接口的访问又时使用csrf认证的方式这点就很坑
登陆操作的步骤:(1)请求 hue/accounts/login/,获取csrfToken和sessionId
(2)第二次使用账号,密码,及上次保存的csrfToken和sessionId请求 hue/accounts/login/成功登陆,并保存csrfToken和sessionId
二,请求同步的接口:useradmin/users/add_ldap_users及hue/useradmin/users/add_ldap_users完成同步操作
使用保存的csrfToken和sessionId及其余参数:X-requested-with,Referer,username_pattern等完成请求
下面附上java版代码
public class HueRestUtil {
private static final Logger logger = LoggerFactory.getLogger(HueUiConf.class);
private String hueHost;
private String hueUserName;
private String huePassword;
private String csrfToken;
private String sessionId;
private RestTemplate restTemplate = new RestTemplate();
private HueRestUtil() {
}
public HueRestUtil(String hueHost, String hueUserName, String huePassword) {
this.hueHost = hueHost;
this.hueUserName = hueUserName;
this.huePassword = huePassword;
logInHue();
}
public String getHueHost() {
return hueHost;
}
public String getHueUserName() {
return hueUserName;
}
public String getHuePassword() {
return huePassword;
}
public String getCsrfToken() {
return csrfToken;
}
public String getSessionId() {
return sessionId;
}
private void logInHue() {
String url = String.join("", hueHost, "hue/accounts/login/");
MultiValueMap<String, String> urlSuffix = new LinkedMultiValueMap<>();
urlSuffix.add("fromModal", "true");
URI uri = UriComponentsBuilder.fromHttpUrl(url).queryParams(urlSuffix).build().toUri();
HttpHeaders headers = new HttpHeaders();
try {
// 第一次登录获取cookie,csrf验证信息(get)
ResponseEntity<String> exchange = restTemplate.exchange(uri, HttpMethod.GET, new HttpEntity<>(headers),
String.class);
if (!exchange.getStatusCode().isError()) {
for (Map.Entry<String, List<String>> entry : exchange.getHeaders().entrySet()) {
if (HttpHeaders.SET_COOKIE.equals(entry.getKey())) {
List<String> value = entry.getValue();
if (0 < value.size()) {
this.csrfToken = value.get(0).split(";")[0];
this.sessionId = value.get(1).split(";")[0];
}
}
}
// 设置第二次登陆相关参数信息
// 设置body相关信息
MultiValueMap<String, String> body = new LinkedMultiValueMap<>(2);
body.add("username", hueUserName);
body.add("password", huePassword);
body.add("server", "LDAP");
body.add("next", "/");
body.add("csrfmiddlewaretoken", this.csrfToken.split("=")[1]);
// 设置header相关信息
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
httpHeaders.add(HttpHeaders.COOKIE, join(";", this.csrfToken, this.sessionId));
httpHeaders.set("X-CSRFToken", this.csrfToken.split("=")[1]);
httpHeaders.set("Referer", hueHost);
URI loginUri = UriComponentsBuilder.fromHttpUrl(url).queryParams(urlSuffix).build().toUri();
// 第二次登陆
ResponseEntity<String> loginResult = restTemplate.exchange(loginUri, HttpMethod.POST,
new HttpEntity<>(body, httpHeaders), String.class);
if (loginResult.getStatusCode().isError()) {
logger.error("hue server error,please check config!code:{}", loginResult.getStatusCode().value());
} else {
for (Map.Entry<String, List<String>> entry : loginResult.getHeaders().entrySet()) {
List<String> value = entry.getValue();
if (HttpHeaders.SET_COOKIE.equals(entry.getKey())) {
if (0 < value.size()) {
// 保存第二次登陆的csrfToken,sessionId
this.csrfToken = value.get(0).split(";")[0];
this.sessionId = value.get(1).split(";")[0];
} else {
throw new RuntimeException("hue login,second check failed...");
}
}
}
logger.info("log in hue suss");
}
} else {
logger.error("hue server error,please check config!code:{}", exchange.getStatusCode().value());
}
} catch (Exception e) {
logger.error("Hue lonIn failed!", e);
}
}
public Boolean syncLdapUser(String userName) throws UnsupportedEncodingException {
String url = String.join("", hueHost, "useradmin/users/add_ldap_users");
String refererUrl = String.join("", hueHost, "hue/useradmin/users/add_ldap_users");
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
httpHeaders.add(HttpHeaders.COOKIE, join(";", this.csrfToken, this.sessionId));
if (Strings.isNotBlank(this.csrfToken) && 1 < this.csrfToken.split("=").length) {
httpHeaders.set("X-CSRFToken", this.csrfToken.split("=")[1]);
}
httpHeaders.set("X-requested-with", "XMLHttpRequest");
httpHeaders.set("Referer", refererUrl);
httpHeaders.set("Accept", "application/json");
httpHeaders.set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("csrfmiddlewaretoken", URLEncoder.encode(this.csrfToken.split("=")[1], StandardCharsets.UTF_8.name()));
params.add("is_embeddable", URLEncoder.encode("true", StandardCharsets.UTF_8.name()));
params.add("username_pattern", URLEncoder.encode(userName, StandardCharsets.UTF_8.name()));
params.add("ensure_home_directory", URLEncoder.encode("on", StandardCharsets.UTF_8.name()));
ResponseEntity<String> stringResponseEntity = restTemplate.postForEntity(url,
new HttpEntity<MultiValueMap>(params, httpHeaders), String.class);
String responseStr = stringResponseEntity.getBody();
JSONObject jsonObject = JSON.parseObject(responseStr);
Object status = jsonObject.get("status");
if (!"0".equals(status)) {
logger.error("sync hue ldapUser failed response:{}", responseStr);
return false;
}
return true;
}
}
此次踩坑着实有点多:1,首先使用postman使用浏览器的调试模式的参数请求一直不过,后来才发现需要在同一个session中完成请求,首先的登陆
2,还有登陆hue也一直不过,后面才知道需要在登陆时在body中加上:"server", "LDAP"
3,在浏览器中csrfmiddlewaretoken和X-CSRFToken的值不同,一直在纠结csrfmiddlewaretoken的生成方式,后面才发现这两个其实就一个东西,可以都填CSRFToken的值,至于浏览器为什么不一样就不得而知了
4,调试时也很坑,很多问题接口直接返回403或者login require,根本不知道何处处理报错问题
最后
本次仅仅是同步ldap用户到hue的接口调用,其余的调用方式基本一致,调用参数使用浏览器调试模式查看即可