feat: 支持共享 session

This commit is contained in:
CaptainB 2022-01-18 16:56:01 +08:00 committed by xiaomeinvG
parent f3dbe66b63
commit fd76c1d8d4
14 changed files with 112 additions and 68 deletions

View File

@ -111,6 +111,14 @@
<artifactId>shiro-spring-boot-starter</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>

View File

@ -3,9 +3,11 @@ package io.metersphere.commons.utils;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
@Setter
@Getter
public class RsaKey {
public class RsaKey implements Serializable {
//公钥
private String publicKey;

View File

@ -16,10 +16,23 @@ public class RsaUtil {
public static final String CHARSET = "UTF-8";
public static final String RSA_ALGORITHM = "RSA";
private static RsaKey rsaKey;
/**
* 创建RSA 公钥-私钥
*/
public static RsaKey getRsaKey() throws NoSuchAlgorithmException {
if (rsaKey == null) {
rsaKey = createKeys();
}
return rsaKey;
}
public static void setRsaKey(RsaKey rsaKey) throws NoSuchAlgorithmException {
// 放到缓存里
RsaUtil.rsaKey = rsaKey;
}
public static RsaKey createKeys() throws NoSuchAlgorithmException {
return createKeys(1024);
}

View File

@ -1,17 +1,13 @@
package io.metersphere.commons.utils;
import io.metersphere.commons.user.SessionUser;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Collection;
import static io.metersphere.commons.constants.SessionConstants.ATTR_USER;
@ -39,30 +35,6 @@ public class SessionUtils {
return (String) SecurityUtils.getSubject().getSession().getId();
}
private static Session getSessionByUsername(String username) {
DefaultSessionManager sessionManager = CommonBeanFactory.getBean(DefaultSessionManager.class);
Collection<Session> sessions = sessionManager.getSessionDAO().getActiveSessions();
for (Session session : sessions) {
if (null != session && StringUtils.equals(String.valueOf(session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY)), username)) {
return session;
}
}
return null;
}
/**
* 踢除用户
*
* @param username
*/
public static void kickOutUser(String username) {
Session session = getSessionByUsername(username);
if (session != null) {
DefaultSessionManager sessionManager = CommonBeanFactory.getBean(DefaultSessionManager.class);
sessionManager.getSessionDAO().delete(session);
}
}
//
public static void putUser(SessionUser sessionUser) {
SecurityUtils.getSubject().getSession().setAttribute(ATTR_USER, sessionUser);

View File

@ -2,15 +2,22 @@ package io.metersphere.config;
import io.metersphere.commons.utils.RsaKey;
import io.metersphere.commons.utils.RsaUtil;
import org.springframework.context.annotation.Bean;
import io.metersphere.service.FileService;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;
import java.security.NoSuchAlgorithmException;
import javax.annotation.Resource;
@Configuration
public class RsaConfig {
@Bean
public RsaKey rsaKey() throws NoSuchAlgorithmException {
return RsaUtil.createKeys();
@Resource
private FileService fileService;
@EventListener
public void rsaKey(ContextRefreshedEvent event) throws Exception {
RsaKey value = fileService.checkRsaKey();
// 保存到缓存中
RsaUtil.setRsaKey(value);
}
}

View File

@ -1,13 +1,16 @@
package io.metersphere.config;
import io.metersphere.commons.utils.ShiroUtils;
import io.metersphere.security.ApiKeyFilter;
import io.metersphere.security.CsrfFilter;
import io.metersphere.security.UserModularRealmAuthenticator;
import io.metersphere.security.realm.LdapRealm;
import io.metersphere.security.realm.LocalRealm;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
@ -15,8 +18,8 @@ import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.ServletContainerSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.EnvironmentAware;
@ -32,8 +35,8 @@ import javax.servlet.Filter;
import java.util.*;
@Configuration
@ConditionalOnProperty(prefix = "sso", name = "mode", havingValue = "local", matchIfMissing = true)
public class ShiroConfig implements EnvironmentAware {
private Environment env;
@Bean
@ -69,16 +72,26 @@ public class ShiroConfig implements EnvironmentAware {
return new MemoryConstrainedCacheManager();
}
@Bean
public SessionManager sessionManager() {
Long timeout = env.getProperty("spring.session.timeout", Long.class);
String storeType = env.getProperty("spring.session.store-type");
if (StringUtils.equals(storeType, "none")) {
return ShiroUtils.getSessionManager(timeout, memoryConstrainedCacheManager());
}
return new ServletContainerSessionManager();
}
/**
* securityManager 不用直接注入 Realm可能会导致事务失效
* 解决方法见 handleContextRefresh
* http://www.debugrun.com/a/NKS9EJQ.html
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager securityManager(SessionManager sessionManager, MemoryConstrainedCacheManager memoryConstrainedCacheManager) {
public DefaultWebSecurityManager securityManager(SessionManager sessionManager, CacheManager cacheManager) {
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
dwsm.setSessionManager(sessionManager);
dwsm.setCacheManager(memoryConstrainedCacheManager);
dwsm.setCacheManager(cacheManager);
dwsm.setAuthenticator(modularRealmAuthenticator());
return dwsm;
}
@ -123,12 +136,6 @@ public class ShiroConfig implements EnvironmentAware {
return aasa;
}
@Bean
public SessionManager sessionManager(MemoryConstrainedCacheManager memoryConstrainedCacheManager) {
Long sessionTimeout = env.getProperty("session.timeout", Long.class, 43200L); // 默认43200s, 12个小时
return ShiroUtils.getSessionManager(sessionTimeout, memoryConstrainedCacheManager);
}
/**
* 等到ApplicationContext 加载完成之后 装配shiroRealm
*/

View File

@ -4,6 +4,7 @@ import io.metersphere.commons.constants.OperLogConstants;
import io.metersphere.commons.constants.UserSource;
import io.metersphere.commons.user.SessionUser;
import io.metersphere.commons.utils.RsaKey;
import io.metersphere.commons.utils.RsaUtil;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.LoginRequest;
import io.metersphere.dto.UserDTO;
@ -19,6 +20,7 @@ import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
@RestController
@RequestMapping
@ -28,13 +30,15 @@ public class LoginController {
private UserService userService;
@Resource
private BaseDisplayService baseDisplayService;
@Resource
private RsaKey rsaKey;
@GetMapping(value = "/isLogin")
public ResultHolder isLogin() {
public ResultHolder isLogin() throws NoSuchAlgorithmException {
RsaKey rsaKey = RsaUtil.getRsaKey();
if (SecurityUtils.getSubject().isAuthenticated()) {
UserDTO user = userService.getUserDTO(SessionUtils.getUserId());
if (user == null) {
return ResultHolder.error(rsaKey.getPublicKey());
}
if (StringUtils.isBlank(user.getLanguage())) {
user.setLanguage(LocaleContextHolder.getLocale().toString());
}
@ -64,7 +68,7 @@ public class LoginController {
}
@GetMapping(value = "/signout")
@MsAuditLog(module = "auth_title", beforeEvent = "#msClass.getUserId(id)",type = OperLogConstants.LOGIN, title = "登出",msClass = SessionUtils.class)
@MsAuditLog(module = "auth_title", beforeEvent = "#msClass.getUserId(id)", type = OperLogConstants.LOGIN, title = "登出", msClass = SessionUtils.class)
public ResultHolder logout() throws Exception {
userService.logout();
SecurityUtils.getSubject().logout();

View File

@ -13,7 +13,8 @@ import io.metersphere.controller.request.member.EditPassWordRequest;
import io.metersphere.controller.request.member.QueryMemberRequest;
import io.metersphere.controller.request.member.UserRequest;
import io.metersphere.controller.request.resourcepool.UserBatchProcessRequest;
import io.metersphere.dto.*;
import io.metersphere.dto.UserDTO;
import io.metersphere.dto.UserGroupPermissionDTO;
import io.metersphere.excel.domain.ExcelResponse;
import io.metersphere.i18n.Translator;
import io.metersphere.log.annotation.MsAuditLog;
@ -21,6 +22,7 @@ import io.metersphere.service.UserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -55,8 +57,7 @@ public class UserController {
@MsAuditLog(module = "system_user", type = OperLogConstants.DELETE, beforeEvent = "#msClass.getLogDetails(#userId)", msClass = UserService.class)
public void deleteUser(@PathVariable(value = "userId") String userId) {
userService.deleteUser(userId);
// 踢掉在线用户
SessionUtils.kickOutUser(userId);
// todo 剔除在线用户
}
@PostMapping("/special/update")

View File

@ -1,13 +1,10 @@
package io.metersphere.controller.request;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.RsaKey;
import io.metersphere.commons.utils.RsaUtil;
import lombok.Getter;
import lombok.Setter;
import java.security.NoSuchAlgorithmException;
@Getter
@Setter
public class LoginRequest {
@ -18,7 +15,7 @@ public class LoginRequest {
public String getUsername() {
try {
RsaKey rsaKey = CommonBeanFactory.getBean(RsaKey.class);
RsaKey rsaKey = RsaUtil.getRsaKey();
return RsaUtil.privateDecrypt(username, rsaKey.getPrivateKey());
} catch (Exception e) {
return username;
@ -27,7 +24,7 @@ public class LoginRequest {
public String getPassword() {
try {
RsaKey rsaKey = CommonBeanFactory.getBean(RsaKey.class);
RsaKey rsaKey = RsaUtil.getRsaKey();
return RsaUtil.privateDecrypt(password, rsaKey.getPrivateKey());
} catch (Exception e) {
return password;

View File

@ -4,10 +4,11 @@ import io.metersphere.base.domain.Group;
import io.metersphere.base.domain.UserGroupPermission;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
@Data
public class GroupResourceDTO {
public class GroupResourceDTO implements Serializable {
private GroupResource resource;
private List<GroupPermission> permissions;
private String type;

View File

@ -29,6 +29,10 @@ public class CsrfFilter extends AnonymousFilter {
((HttpServletResponse) response).setHeader(SessionConstants.AUTHENTICATION_STATUS, SessionConstants.AUTHENTICATION_INVALID);
return true;
}
// 错误页面不需要 csrf
if (WebUtils.toHttp(request).getRequestURI().equals("/error")) {
return true;
}
// api 过来的请求
if (ApiKeyHandler.isApiKeyCall(WebUtils.toHttp(request))) {
return true;

View File

@ -6,10 +6,14 @@ import io.metersphere.base.mapper.FileMetadataMapper;
import io.metersphere.base.mapper.TestCaseFileMapper;
import io.metersphere.commons.constants.FileType;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.RsaKey;
import io.metersphere.commons.utils.RsaUtil;
import io.metersphere.performance.request.QueryProjectFileRequest;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
@ -124,11 +128,11 @@ public class FileService {
public FileMetadata insertFileByFileName(File file, byte[] fileByte, String projectId) {
if (StringUtils.isEmpty(file.getName())) {
return null;
}else {
} else {
FileMetadataExample example = new FileMetadataExample();
example.createCriteria().andProjectIdEqualTo(projectId).andNameEqualTo(file.getName());
List<FileMetadata> fileMetadatasInDataBase = fileMetadataMapper.selectByExample(example);
if(CollectionUtils.isEmpty(fileMetadatasInDataBase)){
if (CollectionUtils.isEmpty(fileMetadatasInDataBase)) {
final FileMetadata fileMetadata = new FileMetadata();
fileMetadata.setId(UUID.randomUUID().toString());
fileMetadata.setName(file.getName());
@ -145,7 +149,7 @@ public class FileService {
fileContent.setFile(fileByte);
fileContentMapper.insert(fileContent);
return fileMetadata;
}else {
} else {
FileMetadata fileMetadata = fileMetadatasInDataBase.get(0);
fileMetadata.setName(file.getName());
fileMetadata.setSize(file.length());
@ -282,4 +286,32 @@ public class FileService {
example.createCriteria().andIdIn(fileIds);
return fileMetadataMapper.selectByExample(example);
}
public RsaKey checkRsaKey() {
String key = "ms.login.rsa.key";
FileContent value = getFileContent(key);
if (value == null) {
try {
RsaKey rsaKey = RsaUtil.getRsaKey();
byte[] bytes = SerializationUtils.serialize(rsaKey);
final FileMetadata fileMetadata = new FileMetadata();
fileMetadata.setId(key);
fileMetadata.setName(key);
fileMetadata.setSize((long) bytes.length);
fileMetadata.setCreateTime(System.currentTimeMillis());
fileMetadata.setUpdateTime(System.currentTimeMillis());
fileMetadata.setType("RSA_KEY");
fileMetadataMapper.insert(fileMetadata);
FileContent fileContent = new FileContent();
fileContent.setFileId(fileMetadata.getId());
fileContent.setFile(bytes);
fileContentMapper.insert(fileContent);
return rsaKey;
} catch (Exception e) {
LogUtil.error(e);
}
}
return SerializationUtils.deserialize(value.getFile());
}
}

View File

@ -378,10 +378,7 @@ public class UserService {
user.setPassword(null);
user.setUpdateTime(System.currentTimeMillis());
userMapper.updateByPrimaryKeySelective(user);
// 禁用用户之后剔除在线用户
if (StringUtils.equals(user.getStatus(), UserStatus.DISABLED)) {
SessionUtils.kickOutUser(user.getId());
}
// todo 禁用用户之后剔除在线用户
}
public void switchUserResource(String sign, String sourceId) {

View File

@ -101,6 +101,5 @@ spring.servlet.multipart.max-request-size=500MB
management.server.port=8083
management.endpoints.web.exposure.include=*
#spring.freemarker.checkTemplateLocation=false
spring.session.timeout=${session.timeout:43200}
spring.session.store-type=none