Skip to content

Commit

Permalink
feature: 支持蓝鲸应用级别的权限控制跳转 TencentBlueKing#2238
Browse files Browse the repository at this point in the history
支持国际化
  • Loading branch information
jsonwan committed Jul 20, 2023
1 parent 8ef1788 commit cf95c8b
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ public class LocaleUtils {
public static final String LANG_ZH = "zh";
public static final String LANG_EN = "en";
public static final String LANG_EN_US = "en_US";
/**
* 蓝鲸通用的LANG HEADER
*/
public static final String BLUEKING_LANG_HEADER = "blueking_language";
/**
* 定义项目通用的LANG HEADER
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@

## Business error - User/Login
1247001=User does not exist or is not logged in
1247403=Do not have access permission for the current application
1247403=No access permission for this application

## Business error - Backup
1249001=Fail to get node info from artifactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@

## Business error - User/Login
1247001=User does not exist or is not logged in
1247403=Do not have access permission for the current application
1247403=No access permission for this application

## Business error - Backup
1249001=Fail to get node info from artifactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

import java.util.List;

import static com.tencent.bk.job.common.i18n.locale.LocaleUtils.COMMON_LANG_HEADER;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;

@Slf4j
Expand Down Expand Up @@ -95,6 +96,7 @@ Mono<ServerResponse> getUserByBkToken(ServerRequest request) {

String tokenCookieName = loginService.getCookieNameForToken();
List<String> cookieList = request.headers().header("cookie");
String lang = request.headers().firstHeader(COMMON_LANG_HEADER);

List<String> bkTokenList = RequestUtil.getCookieValuesFromCookies(cookieList, tokenCookieName);
if (CollectionUtils.isEmpty(bkTokenList)) {
Expand All @@ -107,7 +109,7 @@ Mono<ServerResponse> getUserByBkToken(ServerRequest request) {
BkUserDTO user = null;
// 遍历所有传入token找出当前环境的
for (String bkToken : bkTokenList) {
user = loginService.getUser(bkToken);
user = loginService.getUser(bkToken, lang);
if (user != null) {
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

package com.tencent.bk.job.gateway.config;

import com.tencent.bk.job.common.i18n.locale.LocaleUtils;
import com.tencent.bk.job.common.paas.login.CustomLoginClient;
import com.tencent.bk.job.common.paas.login.EELoginClient;
import com.tencent.bk.job.common.paas.login.ILoginClient;
Expand All @@ -44,12 +45,31 @@ public ILoginClient innerLoginClient(@Autowired BkConfig bkConfig) {
return new CustomLoginClient(bkConfig.getCustomLoginApiUrl());
}

@Bean
@Bean(name = "enLoginClient")
@ConditionalOnProperty(value = "paas.login.custom.enabled", havingValue = "false", matchIfMissing = true)
@Primary
public ILoginClient enLoginClient(@Autowired BkConfig bkConfig) {
log.info("Init standard en login client");
return new EELoginClient(
bkConfig.getEsbUrl(),
bkConfig.getAppCode(),
bkConfig.getAppSecret(),
LocaleUtils.LANG_EN,
bkConfig.isUseEsbTestEnv()
);
}

@Bean(name = "cnLoginClient")
@ConditionalOnProperty(value = "paas.login.custom.enabled", havingValue = "false", matchIfMissing = true)
@Primary
public ILoginClient standardLoginClient(@Autowired BkConfig bkConfig) {
log.info("Init standard login client");
return new EELoginClient(bkConfig.getEsbUrl(), bkConfig.getAppCode(), bkConfig.getAppSecret(),
bkConfig.isUseEsbTestEnv());
public ILoginClient cnLoginClient(@Autowired BkConfig bkConfig) {
log.info("Init standard cn login client");
return new EELoginClient(
bkConfig.getEsbUrl(),
bkConfig.getAppCode(),
bkConfig.getAppSecret(),
LocaleUtils.LANG_ZH_CN,
bkConfig.isUseEsbTestEnv()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;

import static com.tencent.bk.job.common.i18n.locale.LocaleUtils.BLUEKING_LANG_HEADER;
import static com.tencent.bk.job.common.i18n.locale.LocaleUtils.COMMON_LANG_HEADER;

/**
Expand All @@ -54,7 +55,7 @@ public AddWebLangHeaderGatewayFilterFactory() {
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String webLangCookieValue = RequestUtil.getCookieValue(request, "blueking_language");
String webLangCookieValue = RequestUtil.getCookieValue(request, BLUEKING_LANG_HEADER);
String commonLang = LocaleUtils.LANG_ZH_CN;
if (!StringUtils.isEmpty(webLangCookieValue)) {
if (webLangCookieValue.equalsIgnoreCase(WebLangCookie.EN)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
package com.tencent.bk.job.gateway.filter.web;

import com.tencent.bk.job.common.constant.ErrorCode;
import com.tencent.bk.job.common.i18n.locale.LocaleUtils;
import com.tencent.bk.job.common.model.Response;
import com.tencent.bk.job.common.model.dto.BkUserDTO;
import com.tencent.bk.job.common.paas.exception.AppPermissionDeniedException;
Expand All @@ -37,8 +38,10 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
Expand All @@ -48,6 +51,8 @@
import java.nio.charset.StandardCharsets;
import java.util.List;

import static com.tencent.bk.job.common.i18n.locale.LocaleUtils.BLUEKING_LANG_HEADER;

/**
* 用户token校验
*/
Expand Down Expand Up @@ -81,6 +86,12 @@ private GatewayFilter getLoginFilter() {
ServerHttpResponse response = exchange.getResponse();
String tokenCookieName = loginService.getCookieNameForToken();
List<String> bkTokenList = RequestUtil.getCookieValuesFromHeader(request, tokenCookieName);
String lang = RequestUtil.getCookieValue(request, BLUEKING_LANG_HEADER);
if (StringUtils.isBlank(lang)) {
lang = LocaleUtils.LANG_EN;
log.warn("Cannot find blueking_language in cookie, use en");
}
LocaleContextHolder.setLocale(LocaleUtils.getLocale(lang), true);
if (CollectionUtils.isEmpty(bkTokenList)) {
log.warn("Fail to parse token from headers, please check");
String bkToken = RequestUtil.getCookieValue(request, tokenCookieName);
Expand All @@ -96,7 +107,7 @@ private GatewayFilter getLoginFilter() {
}
BkUserDTO user;
try {
user = getUserByTokenList(bkTokenList);
user = getUserByTokenList(bkTokenList, lang);
} catch (AppPermissionDeniedException e) {
return getUserAccessAppForbiddenResp(response, e.getMessage());
}
Expand All @@ -117,16 +128,18 @@ private Mono<Void> getUserAccessAppForbiddenResp(ServerHttpResponse response, St
Response<?> resp = new Response<>(ErrorCode.USER_ACCESS_APP_FORBIDDEN, data);
response.setStatusCode(HttpStatus.FORBIDDEN);
String body = JsonUtils.toJson(resp);
DataBuffer dataBuffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
response.getHeaders().setContentLength(body.length());
byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8);
DataBuffer dataBuffer = response.bufferFactory().wrap(bodyBytes);
response.getHeaders().setContentLength(bodyBytes.length);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
response.writeWith(Mono.just(dataBuffer)).subscribe();
return response.setComplete();
}

private BkUserDTO getUserByTokenList(List<String> bkTokenList) {
private BkUserDTO getUserByTokenList(List<String> bkTokenList, String lang) {
// 遍历所有传入token找出当前环境的
for (String bkToken : bkTokenList) {
BkUserDTO user = loginService.getUser(bkToken);
BkUserDTO user = loginService.getUser(bkToken, lang);
if (user != null) {
return user;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ public interface LoginService {
* 根据token获取用户信息
*
* @param bkToken 用户token
* @param lang 语言
* @return 用户信息
*/
BkUserDTO getUser(String bkToken);
BkUserDTO getUser(String bkToken, String lang);

/**
* 获取登录跳转url
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,19 @@
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.tencent.bk.job.common.constant.ErrorCode;
import com.tencent.bk.job.common.exception.InternalException;
import com.tencent.bk.job.common.i18n.locale.LocaleUtils;
import com.tencent.bk.job.common.model.dto.BkUserDTO;
import com.tencent.bk.job.common.paas.login.ILoginClient;
import com.tencent.bk.job.gateway.config.BkConfig;
import com.tencent.bk.job.gateway.web.service.LoginService;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
Expand All @@ -49,14 +53,22 @@ public class LoginServiceImpl implements LoginService {
private final BkConfig bkConfig;
private final String tokenName;
private final String loginUrl;
private final ILoginClient loginClient;
private LoadingCache<String, Optional<BkUserDTO>> onlineUserCache = CacheBuilder.newBuilder()
private final ILoginClient enLoginClient;
private final ILoginClient cnLoginClient;
private LoadingCache<BkTokenWithLang, Optional<BkUserDTO>> onlineUserCache = CacheBuilder.newBuilder()
.maximumSize(200).expireAfterWrite(10, TimeUnit.SECONDS).build(
new CacheLoader<String, Optional<BkUserDTO>>() {
new CacheLoader<BkTokenWithLang, Optional<BkUserDTO>>() {
@Override
public Optional<BkUserDTO> load(String bkToken) throws Exception {
public Optional<BkUserDTO> load(BkTokenWithLang bkTokenWithLang) throws Exception {
try {
BkUserDTO userDto = loginClient.getUserInfoByToken(bkToken);
String lang = bkTokenWithLang.getLang();
String bkToken = bkTokenWithLang.getBkToken();
BkUserDTO userDto;
if (LocaleUtils.LANG_ZH_CN.equals(lang)) {
userDto = cnLoginClient.getUserInfoByToken(bkToken);
} else {
userDto = enLoginClient.getUserInfoByToken(bkToken);
}
return Optional.ofNullable(userDto);
} catch (Exception e) {
return Optional.empty();
Expand All @@ -66,13 +78,16 @@ public Optional<BkUserDTO> load(String bkToken) throws Exception {
);

@Autowired
public LoginServiceImpl(BkConfig bkConfig, ILoginClient loginClient) {
public LoginServiceImpl(BkConfig bkConfig,
@Qualifier("enLoginClient") ILoginClient enLoginClient,
@Qualifier("cnLoginClient") ILoginClient cnLoginClient) {
this.bkConfig = bkConfig;
this.loginClient = loginClient;
this.enLoginClient = enLoginClient;
this.cnLoginClient = cnLoginClient;
this.loginUrl = getLoginUrlProp();
this.tokenName = bkConfig.isCustomPaasLoginEnabled() ? bkConfig.getCustomLoginToken() : "bk_token";
log.info("Init login service, customLoginEnabled:{}, loginClient:{}, loginUrl:{}, tokenName:{}",
bkConfig.isCustomPaasLoginEnabled(), loginClient.getClass(), loginUrl, tokenName);
bkConfig.isCustomPaasLoginEnabled(), enLoginClient.getClass(), loginUrl, tokenName);
}

private String getLoginUrlProp() {
Expand All @@ -96,14 +111,43 @@ public void deleteUser(String bkToken) {
onlineUserCache.invalidate(bkToken);
}

@Getter
class BkTokenWithLang {
private String bkToken;
private String lang;

BkTokenWithLang(String bkToken, String lang) {
this.bkToken = bkToken;
this.lang = lang;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BkTokenWithLang)) return false;
BkTokenWithLang that = (BkTokenWithLang) o;
return Objects.equals(bkToken, that.bkToken) &&
Objects.equals(lang, that.lang);
}

@Override
public int hashCode() {
return Objects.hash(bkToken, lang);
}
}

@Override
public BkUserDTO getUser(String bkToken) {
public BkUserDTO getUser(String bkToken, String lang) {
if (StringUtils.isBlank(bkToken)) {
return null;
}
if (StringUtils.isBlank(lang)) {
log.warn("getUser: lang is null or blank, use default en");
lang = LocaleUtils.LANG_EN;
}
try {
Optional<BkUserDTO> userDto = onlineUserCache.get(bkToken);
BkTokenWithLang bkTokenWithLang = new BkTokenWithLang(bkToken, lang);
Optional<BkUserDTO> userDto = onlineUserCache.get(bkTokenWithLang);
return userDto.orElse(null);
} catch (ExecutionException | UncheckedExecutionException e) {
log.warn("Error occur when get user from paas!");
Expand Down

0 comments on commit cf95c8b

Please sign in to comment.