build: shiro api key config
This commit is contained in:
parent
d6c4645c06
commit
ce5fcf76a3
|
@ -1,6 +1,7 @@
|
||||||
package io.metersphere.sdk.config;
|
package io.metersphere.sdk.config;
|
||||||
|
|
||||||
|
|
||||||
|
import io.metersphere.sdk.security.ApiKeyFilter;
|
||||||
import io.metersphere.sdk.security.CsrfFilter;
|
import io.metersphere.sdk.security.CsrfFilter;
|
||||||
import io.metersphere.sdk.security.MsPermissionAnnotationMethodInterceptor;
|
import io.metersphere.sdk.security.MsPermissionAnnotationMethodInterceptor;
|
||||||
import io.metersphere.sdk.security.realm.LocalRealm;
|
import io.metersphere.sdk.security.realm.LocalRealm;
|
||||||
|
@ -39,17 +40,15 @@ public class ShiroConfig {
|
||||||
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
|
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
|
||||||
shiroFilterFactoryBean.setSuccessUrl("/");
|
shiroFilterFactoryBean.setSuccessUrl("/");
|
||||||
|
|
||||||
// shiroFilterFactoryBean.getFilters().put("apikey", new ApiKeyFilter());
|
shiroFilterFactoryBean.getFilters().put("apikey", new ApiKeyFilter());
|
||||||
shiroFilterFactoryBean.getFilters().put("csrf", new CsrfFilter());
|
shiroFilterFactoryBean.getFilters().put("csrf", new CsrfFilter());
|
||||||
Map<String, String> filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();
|
Map<String, String> filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();
|
||||||
|
|
||||||
filterChainDefinitionMap.putAll(FilterChainUtils.loadBaseFilterChain());
|
filterChainDefinitionMap.putAll(FilterChainUtils.loadBaseFilterChain());
|
||||||
// todo ignore csrf
|
|
||||||
// filterChainDefinitionMap.putAll(FilterChainUtils.ignoreCsrfFilter());
|
|
||||||
|
|
||||||
filterChainDefinitionMap.put("/**", "csrf, authc");
|
filterChainDefinitionMap.putAll(FilterChainUtils.ignoreCsrfFilter());
|
||||||
// todo
|
|
||||||
// filterChainDefinitionMap.put("/**", "apikey, csrf, authc");
|
filterChainDefinitionMap.put("/**", "apikey, csrf, authc");
|
||||||
return shiroFilterFactoryBean;
|
return shiroFilterFactoryBean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package io.metersphere.sdk.constants;
|
||||||
|
|
||||||
|
public enum ApiKeyConstants {
|
||||||
|
ACTIVE, DISABLED
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue