diff --git a/backend/pom.xml b/backend/pom.xml index 0b94144982..360ed3f072 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -18,6 +18,9 @@ 1.8 20.1.0 3.12.1 + + 4.0.0 + 3.3.0 @@ -36,12 +39,7 @@ org.springframework.boot spring-boot-starter-web - + @@ -89,11 +87,7 @@ lombok - + org.springframework.boot @@ -122,7 +116,7 @@ org.apache.shiro - shiro-spring-boot-starter + shiro-spring ${shiro.version} @@ -236,8 +230,7 @@ - + io.dataease dataease-plugin-interface @@ -336,6 +329,7 @@ org.apache.commons commons-pool2 + @@ -350,9 +344,7 @@ src/main/resources - + true static/**/*.woff diff --git a/backend/src/main/java/io/dataease/auth/api/AuthApi.java b/backend/src/main/java/io/dataease/auth/api/AuthApi.java index c7219799a7..51080b57b0 100644 --- a/backend/src/main/java/io/dataease/auth/api/AuthApi.java +++ b/backend/src/main/java/io/dataease/auth/api/AuthApi.java @@ -38,6 +38,10 @@ public interface AuthApi { @PostMapping("/logout") String logout(); + @ApiIgnore + @PostMapping("/deLogout") + String deLogout(); + @ApiOperation("验证账号") @PostMapping("/validateName") Boolean validateName(Map nameDto); @@ -50,6 +54,10 @@ public interface AuthApi { @PostMapping("/isOpenOidc") boolean isOpenOidc(); + @ApiOperation("是否开启cas") + @PostMapping("/isOpenCas") + boolean isOpenCas(); + @ApiIgnore @PostMapping("/isPluginLoaded") boolean isPluginLoaded(); diff --git a/backend/src/main/java/io/dataease/auth/config/cas/CasStrategy.java b/backend/src/main/java/io/dataease/auth/config/cas/CasStrategy.java new file mode 100644 index 0000000000..7a8693b428 --- /dev/null +++ b/backend/src/main/java/io/dataease/auth/config/cas/CasStrategy.java @@ -0,0 +1,60 @@ +package io.dataease.auth.config.cas; + +import io.dataease.auth.service.impl.ShiroServiceImpl; +import io.dataease.commons.utils.CommonBeanFactory; +import io.dataease.service.system.SystemParameterService; +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.util.AntPathMatcher; +import org.jasig.cas.client.authentication.UrlPatternMatcherStrategy; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +@Component +public class CasStrategy implements UrlPatternMatcherStrategy { + + + private static Set releaseTypes = new HashSet<>(); + + @PostConstruct + public void init() { + releaseTypes.add("anon"); + releaseTypes.add("link"); + releaseTypes.add("doc"); + } + @Override + public boolean matches(String s) { + SystemParameterService service = CommonBeanFactory.getBean(SystemParameterService.class); + String serviceValue = service.getValue("cas.callBack"); + if (StringUtils.isBlank(serviceValue)) return false; + String serverName = serviceValue.substring(0, serviceValue.indexOf("/cas/callBack")); + int beginIndex = -1; + if ((beginIndex = s.indexOf(serverName)) != -1) { + s = s.substring(beginIndex + serverName.length()); + } + if (StringUtils.equals("/", s)) return false; + if (StringUtils.equals("/login", s)) return false; + if (StringUtils.startsWith(s, "/cas/callBack")) return false; + if (StringUtils.equals("/api/auth/deLogout", s)) return true; + AntPathMatcher antPathMatcher = new AntPathMatcher(); + ShiroServiceImpl shiroService = CommonBeanFactory.getBean(ShiroServiceImpl.class); + Map stringStringMap = shiroService.loadFilterChainDefinitionMap(); + for (Map.Entry entry : stringStringMap.entrySet()) { + if (releaseTypes.contains(entry.getValue())) { + boolean matches = antPathMatcher.matches(entry.getKey(), s); + if (matches) { + return true; + } + } + } + return false; + } + + @Override + public void setPattern(String s) { + + } +} diff --git a/backend/src/main/java/io/dataease/auth/server/AuthServer.java b/backend/src/main/java/io/dataease/auth/server/AuthServer.java index 42a685d42a..eee98e7ca0 100644 --- a/backend/src/main/java/io/dataease/auth/server/AuthServer.java +++ b/backend/src/main/java/io/dataease/auth/server/AuthServer.java @@ -17,12 +17,14 @@ import io.dataease.i18n.Translator; import io.dataease.plugins.common.entity.XpackLdapUserEntity; import io.dataease.plugins.config.SpringContextUtil; import io.dataease.plugins.util.PluginUtils; +import io.dataease.plugins.xpack.cas.service.CasXpackService; import io.dataease.plugins.xpack.ldap.dto.request.LdapValidateRequest; import io.dataease.plugins.xpack.ldap.dto.response.ValidateResult; import io.dataease.plugins.xpack.ldap.service.LdapXpackService; import io.dataease.plugins.xpack.oidc.service.OidcXpackService; import io.dataease.service.sys.SysUserService; +import io.dataease.service.system.SystemParameterService; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.SecurityUtils; @@ -36,7 +38,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; @RestController public class AuthServer implements AuthApi { @@ -50,6 +54,9 @@ public class AuthServer implements AuthApi { @Autowired private SysUserService sysUserService; + @Resource + private SystemParameterService systemParameterService; + @Override public Object login(@RequestBody LoginDto loginDto) throws Exception { String username = RsaUtil.decryptByPrivateKey(RsaProperties.privateKey, loginDto.getUsername()); @@ -159,6 +166,37 @@ public class AuthServer implements AuthApi { return DEFAULT_PWD; } + @Override + public String deLogout() { + String token = ServletUtils.getToken(); + if (StringUtils.isEmpty(token) || StringUtils.equals("null", token) || StringUtils.equals("undefined", token)) { + return "success"; + } + SecurityUtils.getSubject().logout(); + String result = null; + Integer defaultLoginType = systemParameterService.defaultLoginType(); + if (defaultLoginType == 3 && isOpenCas()) { + HttpServletRequest request = ServletUtils.request(); + HttpSession session = request.getSession(); + session.invalidate(); + CasXpackService casXpackService = SpringContextUtil.getBean(CasXpackService.class); + result = casXpackService.logout(); + } + try { + Long userId = JWTUtils.tokenInfoByToken(token).getUserId(); + authUserService.clearCache(userId); + if (StringUtils.isBlank(result)) { + result = "success"; + } + } catch (Exception e) { + LogUtil.error(e); + if (StringUtils.isBlank(result)) { + result = "fail"; + } + } + return result; + } + @Override public String logout() { String token = ServletUtils.getToken(); @@ -170,20 +208,36 @@ public class AuthServer implements AuthApi { OidcXpackService oidcXpackService = SpringContextUtil.getBean(OidcXpackService.class); oidcXpackService.logout(idToken); } - } + if (StringUtils.isEmpty(token) || StringUtils.equals("null", token) || StringUtils.equals("undefined", token)) { return "success"; } + + SecurityUtils.getSubject().logout(); + String result = null; + Integer defaultLoginType = systemParameterService.defaultLoginType(); + if (defaultLoginType == 3 && isOpenCas()) { + HttpServletRequest request = ServletUtils.request(); + HttpSession session = request.getSession(); + session.invalidate(); + CasXpackService casXpackService = SpringContextUtil.getBean(CasXpackService.class); + result = casXpackService.logout(); + } try { Long userId = JWTUtils.tokenInfoByToken(token).getUserId(); + authUserService.clearCache(userId); + if (StringUtils.isBlank(result)) { + result = "success"; + } } catch (Exception e) { LogUtil.error(e); - return "fail"; + if (StringUtils.isBlank(result)) { + result = "fail"; + } } - - return "success"; + return result; } @Override @@ -211,6 +265,17 @@ public class AuthServer implements AuthApi { return authUserService.supportOidc(); } + + @Override + public boolean isOpenCas() { + Boolean licValid = PluginUtils.licValid(); + if (!licValid) + return false; + Boolean supportCas = authUserService.supportCas(); + + return authUserService.supportCas(); + } + @Override public boolean isPluginLoaded() { Boolean licValid = PluginUtils.licValid(); diff --git a/backend/src/main/java/io/dataease/auth/service/AuthUserService.java b/backend/src/main/java/io/dataease/auth/service/AuthUserService.java index c9e4bc60bb..41fdd96632 100644 --- a/backend/src/main/java/io/dataease/auth/service/AuthUserService.java +++ b/backend/src/main/java/io/dataease/auth/service/AuthUserService.java @@ -14,6 +14,8 @@ public interface AuthUserService { SysUserEntity getLdapUserByName(String username); + SysUserEntity getCasUserByName(String username); + SysUserEntity getUserBySub(String sub); List roles(Long userId); @@ -28,6 +30,8 @@ public interface AuthUserService { Boolean supportOidc(); + Boolean supportCas(); + Boolean pluginLoaded(); diff --git a/backend/src/main/java/io/dataease/auth/service/impl/AuthUserServiceImpl.java b/backend/src/main/java/io/dataease/auth/service/impl/AuthUserServiceImpl.java index e01fd35ba4..e83030deab 100644 --- a/backend/src/main/java/io/dataease/auth/service/impl/AuthUserServiceImpl.java +++ b/backend/src/main/java/io/dataease/auth/service/impl/AuthUserServiceImpl.java @@ -10,6 +10,7 @@ import io.dataease.plugins.common.base.domain.SysUser; import io.dataease.plugins.common.base.mapper.SysUserMapper; import io.dataease.plugins.common.service.PluginCommonService; import io.dataease.plugins.config.SpringContextUtil; +import io.dataease.plugins.xpack.cas.service.CasXpackService; import io.dataease.plugins.xpack.ldap.service.LdapXpackService; import io.dataease.plugins.xpack.oidc.service.OidcXpackService; @@ -65,6 +66,11 @@ public class AuthUserServiceImpl implements AuthUserService { return authMapper.findLdapUserByName(username); } + @Override + public SysUserEntity getCasUserByName(String username) { + return authMapper.findCasUserByName(username); + } + @Override public SysUserEntity getUserBySub(String sub) { return authMapper.findUserBySub(sub); @@ -147,6 +153,15 @@ public class AuthUserServiceImpl implements AuthUserService { return oidcXpackService.isSuuportOIDC(); } + @Override + public Boolean supportCas() { + Map beansOfType = SpringContextUtil.getApplicationContext().getBeansOfType((CasXpackService.class)); + if (beansOfType.keySet().size() == 0) return false; + CasXpackService casXpackService = SpringContextUtil.getBean(CasXpackService.class); + if (ObjectUtils.isEmpty(casXpackService)) return false; + return casXpackService.suuportCas(); + } + @Override public Boolean pluginLoaded() { Map beansOfType = SpringContextUtil.getApplicationContext().getBeansOfType((PluginCommonService.class)); diff --git a/backend/src/main/java/io/dataease/auth/service/impl/ShiroServiceImpl.java b/backend/src/main/java/io/dataease/auth/service/impl/ShiroServiceImpl.java index 6d226ef715..168ed90719 100644 --- a/backend/src/main/java/io/dataease/auth/service/impl/ShiroServiceImpl.java +++ b/backend/src/main/java/io/dataease/auth/service/impl/ShiroServiceImpl.java @@ -84,6 +84,8 @@ public class ShiroServiceImpl implements ShiroService { filterChainDefinitionMap.put("/api/pluginCommon/staticInfo/**", ANON); filterChainDefinitionMap.put("/plugin/oidc/authInfo", ANON); filterChainDefinitionMap.put("/sso/callBack*", ANON); + filterChainDefinitionMap.put("/cas/callBack*", ANON); + filterChainDefinitionMap.put("/cas/reset", ANON); filterChainDefinitionMap.put("/unauth", ANON); filterChainDefinitionMap.put("/display/**", ANON); @@ -91,7 +93,6 @@ public class ShiroServiceImpl implements ShiroService { filterChainDefinitionMap.put("/downline", ANON); filterChainDefinitionMap.put("/common-files/**", ANON); filterChainDefinitionMap.put("/linkage/getPanelAllLinkageInfo/**", ANON); - filterChainDefinitionMap.put("/api/auth/logout", "logout"); filterChainDefinitionMap.put("/api/link/resourceDetail/**", "link"); filterChainDefinitionMap.put("/api/link/viewDetail/**", "link"); diff --git a/backend/src/main/java/io/dataease/commons/filter/SqlFilter.java b/backend/src/main/java/io/dataease/commons/filter/SqlFilter.java index 1c29391c09..6439f27b8e 100644 --- a/backend/src/main/java/io/dataease/commons/filter/SqlFilter.java +++ b/backend/src/main/java/io/dataease/commons/filter/SqlFilter.java @@ -3,7 +3,10 @@ package io.dataease.commons.filter; import io.dataease.commons.exception.DEException; import io.dataease.commons.holder.ThreadLocalContextHolder; import io.dataease.commons.wrapper.XssAndSqlHttpServletRequestWrapper; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; @@ -20,7 +23,10 @@ public class SqlFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - + if (ObjectUtils.isEmpty(RequestContextHolder.getRequestAttributes())) { + ServletRequestAttributes attributes = new ServletRequestAttributes((HttpServletRequest) request); + RequestContextHolder.setRequestAttributes(attributes); + } HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; if ("TRACE".equalsIgnoreCase(httpRequest.getMethod()) || "TRACK".equalsIgnoreCase(httpRequest.getMethod())) { diff --git a/backend/src/main/java/io/dataease/controller/sys/SystemParameterController.java b/backend/src/main/java/io/dataease/controller/sys/SystemParameterController.java index f00673a3ce..456bab17b7 100644 --- a/backend/src/main/java/io/dataease/controller/sys/SystemParameterController.java +++ b/backend/src/main/java/io/dataease/controller/sys/SystemParameterController.java @@ -7,6 +7,7 @@ import io.dataease.controller.sys.response.MailInfo; import io.dataease.dto.SystemParameterDTO; import io.dataease.listener.DatasetCheckListener; import io.dataease.listener.util.CacheUtils; +import io.dataease.plugins.xpack.cas.dto.CasSaveResult; import io.dataease.service.FileService; import io.dataease.service.system.EmailService; import io.dataease.service.system.SystemParameterService; @@ -66,14 +67,14 @@ public class SystemParameterController { @RequiresPermissions("sysparam:read") @PostMapping("/edit/basic") - public void editBasic(@RequestBody List systemParameter) { + public CasSaveResult editBasic(@RequestBody List systemParameter) { int timeout = Integer.parseInt(systemParameter.stream().filter( parameter -> parameter.getParamKey().equals("basic.frontTimeOut") ).findFirst().get().getParamValue()); if (timeout < 0 || timeout > 300) { //增加了合法性检验 throw new NumberFormatException("Timeout Range Error!"); } - systemParameterService.editBasic(systemParameter); + return systemParameterService.editBasic(systemParameter); } @PostMapping("/testConnection") diff --git a/backend/src/main/java/io/dataease/ext/AuthMapper.java b/backend/src/main/java/io/dataease/ext/AuthMapper.java index 62557fbc57..8824240ab1 100644 --- a/backend/src/main/java/io/dataease/ext/AuthMapper.java +++ b/backend/src/main/java/io/dataease/ext/AuthMapper.java @@ -27,6 +27,8 @@ public interface AuthMapper { SysUserEntity findLdapUserByName(@Param("username") String username); + SysUserEntity findCasUserByName(@Param("username") String username); + SysUserEntity findUserBySub(@Param("sub") String sub); diff --git a/backend/src/main/java/io/dataease/ext/AuthMapper.xml b/backend/src/main/java/io/dataease/ext/AuthMapper.xml index 24b66c869c..4322d3469b 100644 --- a/backend/src/main/java/io/dataease/ext/AuthMapper.xml +++ b/backend/src/main/java/io/dataease/ext/AuthMapper.xml @@ -49,6 +49,11 @@ select user_id, username,nick_name, dept_id, password, enabled,email, phone, language ,is_admin, `from` from sys_user a where username = #{username} and a.from = 1 + + + diff --git a/backend/src/main/java/io/dataease/listener/CasStatusListener.java b/backend/src/main/java/io/dataease/listener/CasStatusListener.java new file mode 100644 index 0000000000..aa4c1abcd1 --- /dev/null +++ b/backend/src/main/java/io/dataease/listener/CasStatusListener.java @@ -0,0 +1,27 @@ +package io.dataease.listener; + +import io.dataease.plugins.config.SpringContextUtil; +import io.dataease.plugins.xpack.cas.service.CasXpackService; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletContext; +import java.util.Map; + +@Component +public class CasStatusListener implements ApplicationListener { + + + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + Map beansOfType = SpringContextUtil.getApplicationContext().getBeansOfType((CasXpackService.class)); + if (beansOfType.keySet().size() == 0) return; + CasXpackService casXpackService = SpringContextUtil.getBean(CasXpackService.class); + if (ObjectUtils.isEmpty(casXpackService)) return; + ServletContext servletContext = event.getApplicationContext().getBean(ServletContext.class); + casXpackService.checkCasStatus(servletContext); + } +} diff --git a/backend/src/main/java/io/dataease/plugins/server/CasServer.java b/backend/src/main/java/io/dataease/plugins/server/CasServer.java new file mode 100644 index 0000000000..d342cbcb55 --- /dev/null +++ b/backend/src/main/java/io/dataease/plugins/server/CasServer.java @@ -0,0 +1,103 @@ +package io.dataease.plugins.server; + +import cn.hutool.core.util.RandomUtil; +import io.dataease.auth.entity.SysUserEntity; +import io.dataease.auth.entity.TokenInfo; +import io.dataease.auth.service.AuthUserService; +import io.dataease.auth.util.JWTUtils; +import io.dataease.commons.utils.CodingUtil; +import io.dataease.commons.utils.LogUtil; +import io.dataease.commons.utils.ServletUtils; + +import io.dataease.service.sys.SysUserService; +import io.dataease.service.system.SystemParameterService; +import org.apache.commons.lang3.StringUtils; +import org.jasig.cas.client.authentication.AttributePrincipal; +import org.jasig.cas.client.util.AssertionHolder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.ModelAndView; +import springfox.documentation.annotations.ApiIgnore; + +import javax.annotation.Resource; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +@ApiIgnore +@RequestMapping("/cas") +@Controller +public class CasServer { + + @Autowired + private AuthUserService authUserService; + + @Autowired + private SysUserService sysUserService; + + @Resource + private SystemParameterService systemParameterService; + + @GetMapping("/callBack") + public ModelAndView callBack() { + ModelAndView modelAndView = new ModelAndView("redirect:/"); + HttpServletResponse response = ServletUtils.response(); + + AttributePrincipal principal = AssertionHolder.getAssertion().getPrincipal(); + String name = principal.getName(); + try { + SysUserEntity sysUserEntity = authUserService.getCasUserByName(name); + if(null == sysUserEntity){ + String s = RandomUtil.randomString(6); + String email = s + "@xxx.com"; + sysUserService.validateCasUser(name); + sysUserService.saveCASUser(name, email); + sysUserEntity = authUserService.getUserByName(name); + } + String realPwd = CodingUtil.md5(sysUserService.defaultPWD()); + TokenInfo tokenInfo = TokenInfo.builder().userId(sysUserEntity.getUserId()).username(sysUserEntity.getUsername()).build(); + String token = JWTUtils.sign(tokenInfo, realPwd); + ServletUtils.setToken(token); + Cookie cookie_token = new Cookie("Authorization", token);cookie_token.setPath("/"); + response.addCookie(cookie_token); + + }catch(Exception e) { + + String msg = e.getMessage(); + if (null != e.getCause()) { + msg = e.getCause().getMessage(); + } + try { + msg = URLEncoder.encode(msg, "UTF-8"); + LogUtil.error(e); + Cookie cookie_error = new Cookie("CasError", msg); + cookie_error.setPath("/"); + response.addCookie(cookie_error); + + return modelAndView; + } catch (UnsupportedEncodingException e1) { + e.printStackTrace(); + } + } + return modelAndView; + } + + @GetMapping("/reset") + @ResponseBody + public String reset() { + systemParameterService.resetCas(); + String token = ServletUtils.getToken(); + if (StringUtils.isNotBlank(token)) { + Long userId = JWTUtils.tokenInfoByToken(token).getUserId(); + authUserService.clearCache(userId); + } + HttpServletRequest request = ServletUtils.request(); + request.getSession().invalidate(); + return "已经切换默认登录方式"; + } +} diff --git a/backend/src/main/java/io/dataease/plugins/server/XCasServer.java b/backend/src/main/java/io/dataease/plugins/server/XCasServer.java new file mode 100644 index 0000000000..a41476e0ce --- /dev/null +++ b/backend/src/main/java/io/dataease/plugins/server/XCasServer.java @@ -0,0 +1,35 @@ +package io.dataease.plugins.server; + + +import io.dataease.plugins.config.SpringContextUtil; +import io.dataease.plugins.xpack.cas.dto.CasSaveResult; +import io.dataease.plugins.xpack.cas.service.CasXpackService; +import io.dataease.plugins.xpack.display.dto.response.SysSettingDto; +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import springfox.documentation.annotations.ApiIgnore; + +import java.util.List; + +@ApiIgnore +@RequestMapping("/plugin/cas") +@RestController +public class XCasServer { + + + @PostMapping("/info") + public List getOidcInfo() { + CasXpackService casXpackService = SpringContextUtil.getBean(CasXpackService.class); + return casXpackService.casSettings(); + } + + @RequiresPermissions("sysparam:read") + @PostMapping("/save") + public CasSaveResult save(@RequestBody List settings) { + CasXpackService casXpackService = SpringContextUtil.getBean(CasXpackService.class); + return casXpackService.save(settings); + } +} diff --git a/backend/src/main/java/io/dataease/service/sys/SysUserService.java b/backend/src/main/java/io/dataease/service/sys/SysUserService.java index 20a8ac5fd2..5fc90e9ec8 100644 --- a/backend/src/main/java/io/dataease/service/sys/SysUserService.java +++ b/backend/src/main/java/io/dataease/service/sys/SysUserService.java @@ -124,6 +124,31 @@ public class SysUserService { } } + @Transactional + public void saveCASUser(String name, String email) { + long now = System.currentTimeMillis(); + SysUser sysUser = new SysUser(); + sysUser.setUsername(name); + sysUser.setNickName(name); + sysUser.setEmail(email); + sysUser.setPassword(CodingUtil.md5(DEFAULT_PWD)); + sysUser.setCreateTime(now); + sysUser.setUpdateTime(now); + sysUser.setEnabled(1L); + sysUser.setLanguage("zh_CN"); + sysUser.setFrom(3); + sysUser.setIsAdmin(false); + // sysUser.setSub(ssoUserInfo.getSub()); + sysUserMapper.insert(sysUser); + SysUser dbUser = findOne(sysUser); + if (null != dbUser && null != dbUser.getUserId()) { + // oidc默认角色是普通员工 + List roleIds = new ArrayList(); + roleIds.add(2L); + saveUserRoles( dbUser.getUserId(), roleIds); + } + } + public String defaultPWD() { return DEFAULT_PWD; } @@ -323,6 +348,14 @@ public class SysUserService { return null; } + public void validateCasUser(String userName) { + SysUserExample example = new SysUserExample(); + example.createCriteria().andUsernameEqualTo(userName); + List users = sysUserMapper.selectByExample(example); + if(CollectionUtils.isNotEmpty(users)) { + throw new RuntimeException("用户ID【"+userName+"】已存在,请联系管理员"); + } + } public void validateExistUser(String userName, String nickName, String email) { SysUserExample example = new SysUserExample(); if (StringUtils.isNotBlank(userName)) { diff --git a/backend/src/main/java/io/dataease/service/system/SystemParameterService.java b/backend/src/main/java/io/dataease/service/system/SystemParameterService.java index 098d3b0dae..1a85f3046b 100644 --- a/backend/src/main/java/io/dataease/service/system/SystemParameterService.java +++ b/backend/src/main/java/io/dataease/service/system/SystemParameterService.java @@ -10,8 +10,12 @@ import io.dataease.plugins.common.base.domain.FileMetadata; import io.dataease.plugins.common.base.domain.SystemParameter; import io.dataease.plugins.common.base.domain.SystemParameterExample; import io.dataease.plugins.common.base.mapper.SystemParameterMapper; +import io.dataease.plugins.config.SpringContextUtil; +import io.dataease.plugins.xpack.cas.dto.CasSaveResult; +import io.dataease.plugins.xpack.cas.service.CasXpackService; import io.dataease.service.FileService; import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,6 +26,8 @@ import javax.imageio.ImageIO; import java.io.IOException; import java.io.InputStream; import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + import io.dataease.ext.*; @Service @@ -29,6 +35,7 @@ import io.dataease.ext.*; public class SystemParameterService { private final static String LOGIN_TYPE_KEY = "basic.loginType"; + private final static String CAS_LOGIN_TYPE = "3"; @Resource private SystemParameterMapper systemParameterMapper; @Resource @@ -81,8 +88,11 @@ public class SystemParameterService { return result; } - public void editBasic(List parameters) { - parameters.forEach(parameter -> { + @Transactional + public CasSaveResult editBasic(List parameters) { + CasSaveResult casSaveResult = afterSwitchDefaultLogin(parameters); + for (int i = 0; i < parameters.size(); i++) { + SystemParameter parameter = parameters.get(i); SystemParameterExample example = new SystemParameterExample(); example.createCriteria().andParamKeyEqualTo(parameter.getParamKey()); @@ -92,8 +102,65 @@ public class SystemParameterService { systemParameterMapper.insert(parameter); } example.clear(); + } + return casSaveResult; + } + @Transactional + public void resetCas() { + Map beansOfType = SpringContextUtil.getApplicationContext().getBeansOfType((CasXpackService.class)); + if (beansOfType.keySet().size() == 0) DEException.throwException("当前未启用CAS"); + CasXpackService casXpackService = SpringContextUtil.getBean(CasXpackService.class); + if (ObjectUtils.isEmpty(casXpackService)) DEException.throwException("当前未启用CAS"); + + String loginTypePk = "basic.loginType"; + SystemParameter loginTypeParameter = systemParameterMapper.selectByPrimaryKey(loginTypePk); + if (ObjectUtils.isNotEmpty(loginTypeParameter) && StringUtils.equals("3", loginTypeParameter.getParamValue())) { + loginTypeParameter.setParamValue("0"); + systemParameterMapper.updateByPrimaryKeySelective(loginTypeParameter); + } + casXpackService.setEnabled(false); + } + + public CasSaveResult afterSwitchDefaultLogin(List parameters) { + CasSaveResult casSaveResult = new CasSaveResult(); + casSaveResult.setNeedLogout(false); + Map beansOfType = SpringContextUtil.getApplicationContext().getBeansOfType((CasXpackService.class)); + if (beansOfType.keySet().size() == 0) return casSaveResult; + CasXpackService casXpackService = SpringContextUtil.getBean(CasXpackService.class); + if (ObjectUtils.isEmpty(casXpackService)) return casSaveResult; + + AtomicReference loginType = new AtomicReference(); + boolean containLoginType = parameters.stream().anyMatch(param -> { + if (StringUtils.equals(param.getParamKey(), LOGIN_TYPE_KEY)) { + loginType.set(param.getParamValue()); + return true; + } + return false; }); + if (!containLoginType) return casSaveResult; + + + SystemParameter systemParameter = systemParameterMapper.selectByPrimaryKey(LOGIN_TYPE_KEY); + String originVal = null; + if (ObjectUtils.isNotEmpty(systemParameter)) { + originVal = systemParameter.getParamValue(); + } + + if (StringUtils.equals(originVal, loginType.get())) return casSaveResult; + + if (StringUtils.equals(CAS_LOGIN_TYPE, loginType.get())) { + casSaveResult.setNeedLogout(true); + casXpackService.setEnabled(true); + casSaveResult.setCasEnable(true); + } + + if (StringUtils.equals(CAS_LOGIN_TYPE, originVal)) { + casSaveResult.setNeedLogout(true); + casXpackService.setEnabled(false); + casSaveResult.setCasEnable(false); + } + return casSaveResult; } public List getParamList(String type) { @@ -102,6 +169,8 @@ public class SystemParameterService { return systemParameterMapper.selectByExample(example); } + + public String getVersion() { return System.getenv("MS_VERSION"); } diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index a90f6d32b9..85a8ccf334 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -114,6 +114,13 @@ server.compression.min-response-size=1024 +server.servlet.context-parameters.configurationStrategy=SYSTEM_PROPERTIES +server.servlet.session.cookie.http-only=true +server.servlet.session.tracking-modes=cookie + + + + diff --git a/frontend/package.json b/frontend/package.json index eaeb6cace2..efede49d1e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -79,8 +79,10 @@ "@vue/cli-plugin-babel": "3.6.0", "@vue/cli-plugin-eslint": "^3.9.1", "@vue/cli-service": "^3.3.1", + "add-asset-html-webpack-plugin": "^3.1.3", "babel-eslint": "10.0.1", "chalk": "2.4.2", + "clean-webpack-plugin": "^1.0.1", "connect": "3.6.6", "copy-webpack-plugin": "^4.6.0", "eslint": "5.15.3", @@ -95,11 +97,9 @@ "script-ext-html-webpack-plugin": "2.1.3", "script-loader": "^0.7.2", "serve-static": "^1.13.2", - "add-asset-html-webpack-plugin": "^3.1.3", - "clean-webpack-plugin": "^1.0.1", "vue-template-compiler": "2.6.10", - "webpack-cli": "^3.2.3", - "vuetify": "^2.6.6" + "vuetify": "^2.6.6", + "webpack-cli": "^3.2.3" }, "engines": { "node": ">=8.9", diff --git a/frontend/src/api/user.js b/frontend/src/api/user.js index 36d1c05370..c57748d5d9 100644 --- a/frontend/src/api/user.js +++ b/frontend/src/api/user.js @@ -22,6 +22,13 @@ export function logout() { }) } +export function deLogout() { + return request({ + url: '/api/auth/deLogout', + method: 'post' + }) +} + export function needModifyPwd() { return request({ url: '/api/auth/useInitPwd', @@ -79,6 +86,13 @@ export function oidcStatus() { }) } +export function casStatus() { + return request({ + url: '/api/auth/isOpenCas', + method: 'post' + }) +} + export function pluginLoaded() { return request({ url: '/api/auth/isPluginLoaded', diff --git a/frontend/src/lang/en.js b/frontend/src/lang/en.js index 03385ad3e6..ffe3fbfcbb 100644 --- a/frontend/src/lang/en.js +++ b/frontend/src/lang/en.js @@ -471,7 +471,8 @@ export default { display: 'Display Setting', ldap: 'LDAP Setting', oidc: 'OIDC Setting', - theme: 'Theme Setting' + theme: 'Theme Setting', + cas: 'CAS Setting' }, license: { i18n_no_license_record: 'No License Record', @@ -657,7 +658,8 @@ export default { test_recipients: 'Test recipients', tip: 'Tip: use as test mail recipient only', engine_mode_setting: 'Engine Setting', - kettle_setting: 'Kettle Setting' + kettle_setting: 'Kettle Setting', + cas_selected_warn: 'Selecting CAS will cause you to login again' }, chart: { view_reset: 'View Reset', diff --git a/frontend/src/lang/tw.js b/frontend/src/lang/tw.js index c2af66b461..bb8c4e40f8 100644 --- a/frontend/src/lang/tw.js +++ b/frontend/src/lang/tw.js @@ -471,7 +471,8 @@ export default { display: '顯示設置', ldap: 'LDAP設置', oidc: 'OIDC設置', - theme: '主題設置' + theme: '主題設置', + cas: 'CAS設置' }, license: { i18n_no_license_record: '沒有 License 記錄', @@ -645,7 +646,7 @@ export default { login_type: '默認登錄方式', empty_front: '為空則默認取值10秒', empty_msg: '為空則默認取值30天', - front_error: '請填寫0-300正整數', //修改了提示信息 + front_error: '請填寫0-300正整數', // 修改了提示信息 msg_error: '請填寫1-365正整數', SMTP_port: 'SMTP端口', SMTP_account: 'SMTP賬戶', @@ -659,7 +660,8 @@ export default { test_recipients: '測試收件人', tip: '提示:僅用來作爲測試郵件收件人', engine_mode_setting: '引擎設置', - kettle_setting: 'Kettle 設置' + kettle_setting: 'Kettle 設置', + cas_selected_warn: '選擇CAS方式保存後會註銷當前回話,重新登錄' }, chart: { view_reset: '视图重置', diff --git a/frontend/src/lang/zh.js b/frontend/src/lang/zh.js index 66a0864a84..a96eeb4da2 100644 --- a/frontend/src/lang/zh.js +++ b/frontend/src/lang/zh.js @@ -472,7 +472,8 @@ export default { display: '显示设置', ldap: 'LDAP设置', oidc: 'OIDC设置', - theme: '主题设置' + theme: '主题设置', + cas: 'CAS设置' }, license: { i18n_no_license_record: '没有 License 记录', @@ -660,7 +661,8 @@ export default { test_recipients: '测试收件人', tip: '提示:仅用来作为测试邮件收件人', engine_mode_setting: '引擎设置', - kettle_setting: 'Kettle 设置' + kettle_setting: 'Kettle 设置', + cas_selected_warn: '选择CAS方式保存后会注销当前回话,重新登录' }, chart: { view_reset: '视图重置', diff --git a/frontend/src/layout/components/Topbar.vue b/frontend/src/layout/components/Topbar.vue index 6036f92ab2..cad4ee778c 100644 --- a/frontend/src/layout/components/Topbar.vue +++ b/frontend/src/layout/components/Topbar.vue @@ -197,6 +197,9 @@ export default { bus.$on('set-top-menu-active-info', this.setTopMenuActiveInfo) bus.$on('set-top-text-info', this.setTopTextInfo) bus.$on('set-top-text-active-info', this.setTopTextActiveInfo) + bus.$on('sys-logout', param => { + this.logout(param) + }) this.showTips && this.$nextTick(() => { const drop = this.$refs['my-drop'] drop && drop.show && drop.show() @@ -300,9 +303,13 @@ export default { } return route.children.filter(kid => !kid.hidden).length }, - async logout() { - await this.$store.dispatch('user/logout') - this.$router.push(`/login?redirect=${this.$route.fullPath}`) + async logout(param) { + const result = await this.$store.dispatch('user/logout', param) + if (result !== 'success' && result !== 'fail') { + window.location.href = result + } else { + this.$router.push(`/login?redirect=${this.$route.fullPath}`) + } }, loadUiInfo() { this.$store.dispatch('user/getUI').then(() => { diff --git a/frontend/src/settings.js b/frontend/src/settings.js index 10faf444f4..2a4f98b21d 100644 --- a/frontend/src/settings.js +++ b/frontend/src/settings.js @@ -6,6 +6,7 @@ module.exports = { /* for sso */ IdTokenKey: 'IdToken', AccessTokenKey: 'AccessToken', + CASSESSION: 'JSESSIONID', /** * @type {boolean} true | false diff --git a/frontend/src/store/modules/user.js b/frontend/src/store/modules/user.js index e9c43e7774..4ba90dae2f 100644 --- a/frontend/src/store/modules/user.js +++ b/frontend/src/store/modules/user.js @@ -1,4 +1,4 @@ -import { login, logout, getInfo, getUIinfo, languageApi } from '@/api/user' +import { login, logout, deLogout, getInfo, getUIinfo, languageApi } from '@/api/user' import { getToken, setToken, removeToken, setSysUI } from '@/utils/auth' import { resetRouter } from '@/router' import { format } from '@/utils/formatUi' @@ -136,13 +136,15 @@ const actions = { }, // user logout - logout({ commit, state }) { + logout({ commit, state }, param) { + const method = param && param.casEnable ? deLogout : logout return new Promise((resolve, reject) => { - logout(state.token).then(() => { + method(state.token).then(res => { + debugger removeToken() // must remove token first resetRouter() commit('RESET_STATE') - resolve() + resolve(res.data) }).catch(error => { reject(error) }) diff --git a/frontend/src/utils/auth.js b/frontend/src/utils/auth.js index e8093dcd03..b5d3ed9f7f 100644 --- a/frontend/src/utils/auth.js +++ b/frontend/src/utils/auth.js @@ -7,6 +7,8 @@ const IdTokenKey = Config.IdTokenKey const AccessTokenKey = Config.AccessTokenKey +const casSessionKey = Config.CASSESSION + const linkTokenKey = Config.LinkTokenKey export function getIdToken() { @@ -22,6 +24,7 @@ export function setToken(token) { } export function removeToken() { + Cookies.remove(casSessionKey) Cookies.remove(IdTokenKey) Cookies.remove(AccessTokenKey) return Cookies.remove(TokenKey) diff --git a/frontend/src/views/system/SysParam/BasicSetting.vue b/frontend/src/views/system/SysParam/BasicSetting.vue index 6e3657abe1..f242ebff5b 100644 --- a/frontend/src/views/system/SysParam/BasicSetting.vue +++ b/frontend/src/views/system/SysParam/BasicSetting.vue @@ -38,6 +38,7 @@ {{ $t('login.default_login') }} LDAP OIDC + CAS @@ -67,8 +68,8 @@