build: shiro api key config

This commit is contained in:
CaptainB 2023-06-02 12:59:26 +08:00 committed by 刘瑞斌
parent d6c4645c06
commit ce5fcf76a3
6 changed files with 205 additions and 42 deletions

View File

@ -1,6 +1,7 @@
package io.metersphere.sdk.config;
import io.metersphere.sdk.security.ApiKeyFilter;
import io.metersphere.sdk.security.CsrfFilter;
import io.metersphere.sdk.security.MsPermissionAnnotationMethodInterceptor;
import io.metersphere.sdk.security.realm.LocalRealm;
@ -39,17 +40,15 @@ public class ShiroConfig {
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setSuccessUrl("/");
// shiroFilterFactoryBean.getFilters().put("apikey", new ApiKeyFilter());
shiroFilterFactoryBean.getFilters().put("apikey", new ApiKeyFilter());
shiroFilterFactoryBean.getFilters().put("csrf", new CsrfFilter());
Map<String, String> filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();
filterChainDefinitionMap.putAll(FilterChainUtils.loadBaseFilterChain());
// todo ignore csrf
// filterChainDefinitionMap.putAll(FilterChainUtils.ignoreCsrfFilter());
filterChainDefinitionMap.put("/**", "csrf, authc");
// todo
// filterChainDefinitionMap.put("/**", "apikey, csrf, authc");
filterChainDefinitionMap.putAll(FilterChainUtils.ignoreCsrfFilter());
filterChainDefinitionMap.put("/**", "apikey, csrf, authc");
return shiroFilterFactoryBean;
}

View File

@ -0,0 +1,5 @@
package io.metersphere.sdk.constants;
public enum ApiKeyConstants {
ACTIVE, DISABLED
}

View File

@ -0,0 +1,47 @@
package io.metersphere.sdk.security;
import io.metersphere.sdk.constants.SessionConstants;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.web.filter.authc.AnonymousFilter;
import org.apache.shiro.web.util.WebUtils;
public class ApiKeyFilter extends AnonymousFilter {
@Override
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) {
HttpServletRequest httpRequest = WebUtils.toHttp(request);
// 不是apikey的通过
if (!ApiKeyHandler.isApiKeyCall(httpRequest) && !SecurityUtils.getSubject().isAuthenticated()) {
return true;
}
// apikey 验证
if (!SecurityUtils.getSubject().isAuthenticated()) {
String userId = ApiKeyHandler.getUser(WebUtils.toHttp(request));
if (StringUtils.isNotBlank(userId)) {
SecurityUtils.getSubject().login(new UsernamePasswordToken(userId, "no_pass"));
}
}
if (!SecurityUtils.getSubject().isAuthenticated()) {
((HttpServletResponse) response).setHeader(SessionConstants.AUTHENTICATION_STATUS, "invalid");
}
return true;
}
@Override
protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {
// apikey 退出
if (ApiKeyHandler.isApiKeyCall(WebUtils.toHttp(request)) && SecurityUtils.getSubject().isAuthenticated()) {
SecurityUtils.getSubject().logout();
}
}
}

View File

@ -0,0 +1,63 @@
package io.metersphere.sdk.security;
import io.metersphere.sdk.service.UserKeyService;
import io.metersphere.sdk.util.CodingUtil;
import io.metersphere.sdk.util.CommonBeanFactory;
import io.metersphere.system.domain.UserKey;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
public class ApiKeyHandler {
public static final String API_ACCESS_KEY = "accessKey";
public static final String API_SIGNATURE = "signature";
public static String getUser(HttpServletRequest request) {
if (request == null) {
return null;
}
return getUser(request.getHeader(API_ACCESS_KEY), request.getHeader(API_SIGNATURE));
}
public static Boolean isApiKeyCall(HttpServletRequest request) {
if (request == null) {
return false;
}
return !StringUtils.isBlank(request.getHeader(API_ACCESS_KEY)) && !StringUtils.isBlank(request.getHeader(API_SIGNATURE));
}
public static String getUser(String accessKey, String signature) {
if (StringUtils.isBlank(accessKey) || StringUtils.isBlank(signature)) {
return null;
}
UserKey userKey = CommonBeanFactory.getBean(UserKeyService.class).getUserKey(accessKey);
if (userKey == null) {
throw new RuntimeException("invalid accessKey");
}
String signatureDecrypt;
try {
signatureDecrypt = CodingUtil.aesDecrypt(signature, userKey.getSecretKey(), accessKey);
} catch (Throwable t) {
throw new RuntimeException("invalid signature");
}
String[] signatureArray = StringUtils.split(StringUtils.trimToNull(signatureDecrypt), "|");
if (signatureArray.length < 2) {
throw new RuntimeException("invalid signature");
}
if (!StringUtils.equals(accessKey, signatureArray[0])) {
throw new RuntimeException("invalid signature");
}
long signatureTime;
try {
signatureTime = Long.parseLong(signatureArray[signatureArray.length - 1]);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (Math.abs(System.currentTimeMillis() - signatureTime) > 1800000) {
//签名30分钟超时
throw new RuntimeException("expired signature");
}
return userKey.getCreateUser();
}
}

View File

@ -1,36 +0,0 @@
package io.metersphere.sdk.security;
import io.metersphere.sdk.constants.SessionConstants;
import io.metersphere.sdk.dto.SessionUser;
import io.metersphere.sdk.util.CodingUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import java.util.UUID;
public class SSOSessionHandler {
public static String random = UUID.randomUUID() + UUID.randomUUID().toString();
public static String validate(HttpServletRequest request) {
try {
String v = request.getHeader(SessionConstants.CSRF_TOKEN);
if (StringUtils.isNotBlank(v)) {
return validate(v);
}
} catch (Exception e) {
// LogUtil.error("failed to validate", e);
}
return null;
}
private static String validate(String csrfToken) {
csrfToken = CodingUtil.aesDecrypt(csrfToken, SessionUser.secret, SessionUser.iv);
String[] signatureArray = StringUtils.split(StringUtils.trimToNull(csrfToken), "|");
if (signatureArray.length != 4) {
throw new RuntimeException("invalid token");
}
return signatureArray[0];
}
}

View File

@ -0,0 +1,85 @@
package io.metersphere.sdk.service;
import io.metersphere.sdk.constants.ApiKeyConstants;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.domain.UserKey;
import io.metersphere.system.domain.UserKeyExample;
import io.metersphere.system.mapper.UserKeyMapper;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.UUID;
@Service
@Transactional(rollbackFor = Exception.class)
public class UserKeyService {
@Resource
private UserKeyMapper userKeyMapper;
@Resource
private BaseUserService baseUserService;
public List<UserKey> getUserKeysInfo(String userId) {
UserKeyExample userKeysExample = new UserKeyExample();
userKeysExample.createCriteria().andCreateUserEqualTo(userId);
userKeysExample.setOrderByClause("create_time");
return userKeyMapper.selectByExample(userKeysExample);
}
public UserKey generateUserKey(String userId) {
if (baseUserService.getUserDTO(userId) == null) {
MSException.throwException(Translator.get("user_not_exist") + userId);
}
UserKeyExample userKeysExample = new UserKeyExample();
userKeysExample.createCriteria().andCreateUserEqualTo(userId);
List<UserKey> userKeysList = userKeyMapper.selectByExample(userKeysExample);
if (!CollectionUtils.isEmpty(userKeysList) && userKeysList.size() >= 5) {
MSException.throwException(Translator.get("user_apikey_limit"));
}
UserKey userKeys = new UserKey();
userKeys.setId(UUID.randomUUID().toString());
userKeys.setCreateUser(userId);
userKeys.setStatus(ApiKeyConstants.ACTIVE.name());
userKeys.setAccessKey(RandomStringUtils.randomAlphanumeric(16));
userKeys.setSecretKey(RandomStringUtils.randomAlphanumeric(16));
userKeys.setCreateTime(System.currentTimeMillis());
userKeyMapper.insert(userKeys);
return userKeyMapper.selectByPrimaryKey(userKeys.getId());
}
public void deleteUserKey(String id) {
userKeyMapper.deleteByPrimaryKey(id);
}
public void activeUserKey(String id) {
UserKey userKeys = new UserKey();
userKeys.setId(id);
userKeys.setStatus(ApiKeyConstants.ACTIVE.name());
userKeyMapper.updateByPrimaryKeySelective(userKeys);
}
public void disableUserKey(String id) {
UserKey userKeys = new UserKey();
userKeys.setId(id);
userKeys.setStatus(ApiKeyConstants.DISABLED.name());
userKeyMapper.updateByPrimaryKeySelective(userKeys);
}
public UserKey getUserKey(String accessKey) {
UserKeyExample userKeyExample = new UserKeyExample();
userKeyExample.createCriteria().andAccessKeyEqualTo(accessKey).andStatusEqualTo(ApiKeyConstants.ACTIVE.name());
List<UserKey> userKeysList = userKeyMapper.selectByExample(userKeyExample);
if (!CollectionUtils.isEmpty(userKeysList)) {
return userKeysList.get(0);
}
return null;
}
}