diff --git a/backend/pom.xml b/backend/pom.xml index 8b4d20ab82..6e37a7ef98 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -111,6 +111,14 @@ shiro-spring-boot-starter ${shiro.version} + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.session + spring-session-data-redis + org.apache.commons diff --git a/backend/src/main/java/io/metersphere/commons/utils/RsaKey.java b/backend/src/main/java/io/metersphere/commons/utils/RsaKey.java index 9da3834c02..c736da9ed1 100644 --- a/backend/src/main/java/io/metersphere/commons/utils/RsaKey.java +++ b/backend/src/main/java/io/metersphere/commons/utils/RsaKey.java @@ -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; diff --git a/backend/src/main/java/io/metersphere/commons/utils/RsaUtil.java b/backend/src/main/java/io/metersphere/commons/utils/RsaUtil.java index 93bfe5d397..539b359acb 100644 --- a/backend/src/main/java/io/metersphere/commons/utils/RsaUtil.java +++ b/backend/src/main/java/io/metersphere/commons/utils/RsaUtil.java @@ -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); } diff --git a/backend/src/main/java/io/metersphere/commons/utils/SessionUtils.java b/backend/src/main/java/io/metersphere/commons/utils/SessionUtils.java index 0a9064f341..29dea80c4e 100644 --- a/backend/src/main/java/io/metersphere/commons/utils/SessionUtils.java +++ b/backend/src/main/java/io/metersphere/commons/utils/SessionUtils.java @@ -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 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); diff --git a/backend/src/main/java/io/metersphere/config/RsaConfig.java b/backend/src/main/java/io/metersphere/config/RsaConfig.java index 945ae94a7e..0409279b2a 100644 --- a/backend/src/main/java/io/metersphere/config/RsaConfig.java +++ b/backend/src/main/java/io/metersphere/config/RsaConfig.java @@ -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); } } diff --git a/backend/src/main/java/io/metersphere/config/ShiroConfig.java b/backend/src/main/java/io/metersphere/config/ShiroConfig.java index 972908b6bc..2b357ec013 100644 --- a/backend/src/main/java/io/metersphere/config/ShiroConfig.java +++ b/backend/src/main/java/io/metersphere/config/ShiroConfig.java @@ -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 */ diff --git a/backend/src/main/java/io/metersphere/controller/LoginController.java b/backend/src/main/java/io/metersphere/controller/LoginController.java index 6481280ff2..efe7b911f7 100644 --- a/backend/src/main/java/io/metersphere/controller/LoginController.java +++ b/backend/src/main/java/io/metersphere/controller/LoginController.java @@ -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(); diff --git a/backend/src/main/java/io/metersphere/controller/UserController.java b/backend/src/main/java/io/metersphere/controller/UserController.java index a8f9ebb971..e610c47c66 100644 --- a/backend/src/main/java/io/metersphere/controller/UserController.java +++ b/backend/src/main/java/io/metersphere/controller/UserController.java @@ -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") diff --git a/backend/src/main/java/io/metersphere/controller/request/LoginRequest.java b/backend/src/main/java/io/metersphere/controller/request/LoginRequest.java index 78bd20ff87..1e2f7ae95e 100644 --- a/backend/src/main/java/io/metersphere/controller/request/LoginRequest.java +++ b/backend/src/main/java/io/metersphere/controller/request/LoginRequest.java @@ -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; diff --git a/backend/src/main/java/io/metersphere/dto/GroupResourceDTO.java b/backend/src/main/java/io/metersphere/dto/GroupResourceDTO.java index bbd2222ebd..179f1989ed 100644 --- a/backend/src/main/java/io/metersphere/dto/GroupResourceDTO.java +++ b/backend/src/main/java/io/metersphere/dto/GroupResourceDTO.java @@ -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 permissions; private String type; diff --git a/backend/src/main/java/io/metersphere/security/CsrfFilter.java b/backend/src/main/java/io/metersphere/security/CsrfFilter.java index cbd66db63b..9ced1b365d 100644 --- a/backend/src/main/java/io/metersphere/security/CsrfFilter.java +++ b/backend/src/main/java/io/metersphere/security/CsrfFilter.java @@ -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; diff --git a/backend/src/main/java/io/metersphere/service/FileService.java b/backend/src/main/java/io/metersphere/service/FileService.java index 9e01561a11..e08ce6f637 100644 --- a/backend/src/main/java/io/metersphere/service/FileService.java +++ b/backend/src/main/java/io/metersphere/service/FileService.java @@ -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 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()); + } } diff --git a/backend/src/main/java/io/metersphere/service/UserService.java b/backend/src/main/java/io/metersphere/service/UserService.java index 20056dc263..f64598f007 100644 --- a/backend/src/main/java/io/metersphere/service/UserService.java +++ b/backend/src/main/java/io/metersphere/service/UserService.java @@ -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) { diff --git a/backend/src/main/resources/base.properties b/backend/src/main/resources/base.properties index 3960b21085..c7c15384c0 100644 --- a/backend/src/main/resources/base.properties +++ b/backend/src/main/resources/base.properties @@ -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