diff --git a/backend/pom.xml b/backend/pom.xml index 8b1075bb04..c814656394 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -314,7 +314,17 @@ json-schema-validator 2.2.6 --> - + + + org.springframework.boot + spring-boot-starter-cache + + + + net.sf.ehcache + ehcache + 2.9.1 + diff --git a/backend/src/main/java/io/dataease/Application.java b/backend/src/main/java/io/dataease/Application.java index 635212b880..d48da83dbc 100644 --- a/backend/src/main/java/io/dataease/Application.java +++ b/backend/src/main/java/io/dataease/Application.java @@ -5,9 +5,10 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration; import org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration; import org.springframework.boot.web.servlet.ServletComponentScan; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.PropertySource; import org.springframework.scheduling.annotation.EnableScheduling; - +@EnableCaching @SpringBootApplication(exclude = { QuartzAutoConfiguration.class, LdapAutoConfiguration.class 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 f27473767e..f3e2a977e0 100644 --- a/backend/src/main/java/io/dataease/auth/api/AuthApi.java +++ b/backend/src/main/java/io/dataease/auth/api/AuthApi.java @@ -14,7 +14,7 @@ public interface AuthApi { @PostMapping("/login") - Object login(LoginDto loginDto); + Object login(LoginDto loginDto) throws Exception; @PostMapping("/userInfo") @@ -23,6 +23,9 @@ public interface AuthApi { @GetMapping("/isLogin") Boolean isLogin(); + @PostMapping("/logout") + String logout(); + @GetMapping("/test") String test(); diff --git a/backend/src/main/java/io/dataease/auth/config/F2CRealm.java b/backend/src/main/java/io/dataease/auth/config/F2CRealm.java index 9c9fed8526..2311e1f449 100644 --- a/backend/src/main/java/io/dataease/auth/config/F2CRealm.java +++ b/backend/src/main/java/io/dataease/auth/config/F2CRealm.java @@ -2,6 +2,7 @@ package io.dataease.auth.config; import io.dataease.auth.entity.JWTToken; 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 org.apache.shiro.authc.AuthenticationException; @@ -34,9 +35,8 @@ public class F2CRealm extends AuthorizingRealm { //验证资源权限 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { - String username = JWTUtils.getUsername(principals.toString()); - SysUserEntity user = authUserService.getUser(username); - Long userId = user.getUserId(); + Long userId = JWTUtils.tokenInfoByToken(principals.toString()).getUserId(); + //SysUserEntity user = authUserService.getUserById(userId); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); Set role = authUserService.roles(userId).stream().collect(Collectors.toSet()); simpleAuthorizationInfo.addRoles(role); @@ -50,12 +50,14 @@ public class F2CRealm extends AuthorizingRealm { protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException { String token = (String) auth.getCredentials(); // 解密获得username,用于和数据库进行对比 - String username = JWTUtils.getUsername(token); + TokenInfo tokenInfo = JWTUtils.tokenInfoByToken(token); + Long userId = tokenInfo.getUserId(); + String username = tokenInfo.getUsername(); if (username == null) { throw new AuthenticationException("token invalid"); } - SysUserEntity user = authUserService.getUser(username); + SysUserEntity user = authUserService.getUserById(userId); if (user == null) { throw new AuthenticationException("User didn't existed!"); } @@ -66,7 +68,7 @@ public class F2CRealm extends AuthorizingRealm { } catch (Exception e) { e.printStackTrace(); } - if (! JWTUtils.verify(token, username, pass)) { + if (! JWTUtils.verify(token, tokenInfo, pass)) { throw new AuthenticationException("Username or password error"); } return new SimpleAuthenticationInfo(token, token, "f2cReam"); diff --git a/backend/src/main/java/io/dataease/auth/config/RsaProperties.java b/backend/src/main/java/io/dataease/auth/config/RsaProperties.java new file mode 100644 index 0000000000..fc60282687 --- /dev/null +++ b/backend/src/main/java/io/dataease/auth/config/RsaProperties.java @@ -0,0 +1,17 @@ +package io.dataease.auth.config; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Data +@Component +public class RsaProperties { + + public static String privateKey; + + @Value("${rsa.private_key}") + public void setPrivateKey(String privateKey) { + RsaProperties.privateKey = privateKey; + } +} diff --git a/backend/src/main/java/io/dataease/auth/config/ShiroConfig.java b/backend/src/main/java/io/dataease/auth/config/ShiroConfig.java index 11366f935c..c8cdc9214f 100644 --- a/backend/src/main/java/io/dataease/auth/config/ShiroConfig.java +++ b/backend/src/main/java/io/dataease/auth/config/ShiroConfig.java @@ -22,6 +22,7 @@ public class ShiroConfig { + @Bean("securityManager") public DefaultWebSecurityManager getManager(F2CRealm f2cRealm) { DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); @@ -49,6 +50,7 @@ public class ShiroConfig { filterMap.put("f2cPerms", new F2CPermissionsFilter()); //filterMap.put("f2cRoles", new F2CRolesFilter()); filterMap.put("jwt", new JWTFilter()); + /*filterMap.put("jwt", jwtFilter);*/ filterMap.put("logout", new F2CLogoutFilter()); factoryBean.setSecurityManager(securityManager); factoryBean.setUnauthorizedUrl("/permissionMiss"); diff --git a/backend/src/main/java/io/dataease/auth/entity/TokenInfo.java b/backend/src/main/java/io/dataease/auth/entity/TokenInfo.java new file mode 100644 index 0000000000..83d4dc0e96 --- /dev/null +++ b/backend/src/main/java/io/dataease/auth/entity/TokenInfo.java @@ -0,0 +1,21 @@ +package io.dataease.auth.entity; + +import lombok.Builder; +import lombok.Data; + +import java.io.Serializable; + +@Data +@Builder +public class TokenInfo implements Serializable { + + private String username; + + private Long userId; + + private Long lastLoginTime; + + public String format(){ + return username + "," +userId; + } +} diff --git a/backend/src/main/java/io/dataease/auth/filter/JWTFilter.java b/backend/src/main/java/io/dataease/auth/filter/JWTFilter.java index a89e7673fa..7a0602d105 100644 --- a/backend/src/main/java/io/dataease/auth/filter/JWTFilter.java +++ b/backend/src/main/java/io/dataease/auth/filter/JWTFilter.java @@ -1,25 +1,31 @@ package io.dataease.auth.filter; import io.dataease.auth.entity.JWTToken; +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.CommonBeanFactory; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.RequestMethod; - import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; + public class JWTFilter extends BasicHttpAuthenticationFilter { private Logger LOGGER = LoggerFactory.getLogger(this.getClass()); + /*@Autowired + private AuthUserService authUserService;*/ + /** * 判断用户是否想要登入。 @@ -67,25 +73,22 @@ public class JWTFilter extends BasicHttpAuthenticationFilter { return false; } - private String refreshToken(ServletRequest request, ServletResponse response) { + private String refreshToken(ServletRequest request, ServletResponse response) throws Exception{ // 获取AccessToken(Shiro中getAuthzHeader方法已经实现) String token = this.getAuthzHeader(request); // 获取当前Token的帐号信息 - String username = JWTUtils.getUsername(token); - String password = "123456"; - try { - String newToken = JWTUtils.sign(username, password); - JWTToken jwtToken = new JWTToken(newToken); - this.getSubject(request, response).login(jwtToken); - // 设置响应的Header头新Token - HttpServletResponse httpServletResponse = (HttpServletResponse) response; - httpServletResponse.addHeader("Access-Control-Expose-Headers", "Authorization"); - httpServletResponse.setHeader("Authorization", newToken); - return newToken; - }catch (Exception e){ - e.printStackTrace(); - } - return null; + TokenInfo tokenInfo = JWTUtils.tokenInfoByToken(token); + AuthUserService authUserService = CommonBeanFactory.getBean(AuthUserService.class); + SysUserEntity user = authUserService.getUserById(tokenInfo.getUserId()); + String password = user.getPassword(); + String newToken = JWTUtils.sign(tokenInfo, password); + JWTToken jwtToken = new JWTToken(newToken); + this.getSubject(request, response).login(jwtToken); + // 设置响应的Header头新Token + HttpServletResponse httpServletResponse = (HttpServletResponse) response; + httpServletResponse.addHeader("Access-Control-Expose-Headers", "RefreshAuthorization"); + httpServletResponse.setHeader("RefreshAuthorization", newToken); + return newToken; } @@ -113,8 +116,10 @@ public class JWTFilter extends BasicHttpAuthenticationFilter { private void response401(ServletRequest req, ServletResponse resp) { try { HttpServletResponse httpServletResponse = (HttpServletResponse) resp; - httpServletResponse.sendRedirect("/401"); - } catch (IOException e) { + httpServletResponse.addHeader("Access-Control-Expose-Headers", "authentication-status"); + httpServletResponse.setHeader("authentication-status", "invalid"); + httpServletResponse.setStatus(401); + } catch (Exception e) { LOGGER.error(e.getMessage()); } } 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 11453e1886..bdb56b337b 100644 --- a/backend/src/main/java/io/dataease/auth/server/AuthServer.java +++ b/backend/src/main/java/io/dataease/auth/server/AuthServer.java @@ -4,14 +4,18 @@ import io.dataease.auth.api.AuthApi; import io.dataease.auth.api.dto.CurrentRoleDto; import io.dataease.auth.api.dto.CurrentUserDto; import io.dataease.auth.api.dto.LoginDto; +import io.dataease.auth.config.RsaProperties; 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.auth.util.RsaUtil; import io.dataease.commons.utils.BeanUtils; +import io.dataease.commons.utils.CodingUtil; import io.dataease.commons.utils.ServletUtils; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; @@ -26,36 +30,35 @@ public class AuthServer implements AuthApi { @Override - public Object login(@RequestBody LoginDto loginDto) { + public Object login(@RequestBody LoginDto loginDto) throws Exception { String username = loginDto.getUsername(); String password = loginDto.getPassword(); - SysUserEntity user = authUserService.getUser(username); + SysUserEntity user = authUserService.getUserByName(username); String realPwd = user.getPassword(); - if (StringUtils.isEmpty(realPwd)){ + if (ObjectUtils.isEmpty(user)){ throw new RuntimeException("没有该用户!"); } - /*String pwd = RsaUtil.decryptByPrivateKey(RsaProperties.privateKey, password); - String realPass = RsaUtil.decryptByPrivateKey(RsaProperties.privateKey, realPwd); - if (!StrUtil.equals(pwd, realPass)){ - throw new RuntimeException("密码错误!"); - }*/ - if (!StringUtils.equals(realPwd, password)){ + //私钥解密 + String pwd = RsaUtil.decryptByPrivateKey(RsaProperties.privateKey, password); + //md5加密 + pwd = CodingUtil.md5(pwd); + + if (!StringUtils.equals(pwd, realPwd)){ throw new RuntimeException("密码错误!"); } - /*Map result = new HashMap<>(); - result.put("token", JWTUtils.sign(username, realPwd));*/ - String token = JWTUtils.sign(username, realPwd); - ServletUtils.setToken(token); Map result = new HashMap<>(); + TokenInfo tokenInfo = TokenInfo.builder().userId(user.getUserId()).username(username).lastLoginTime(System.currentTimeMillis()).build(); + String token = JWTUtils.sign(tokenInfo, realPwd); result.put("token", token); + ServletUtils.setToken(token); return result; } @Override public CurrentUserDto userInfo() { String token = ServletUtils.getToken(); - String username = JWTUtils.getUsername(token); - SysUserEntity user = authUserService.getUser(username); + Long userId = JWTUtils.tokenInfoByToken(token).getUserId(); + SysUserEntity user = authUserService.getUserById(userId); CurrentUserDto currentUserDto = BeanUtils.copyBean(new CurrentUserDto(), user); List currentRoleDtos = authUserService.roleInfos(user.getUserId()); List permissions = authUserService.permissions(user.getUserId()); @@ -64,7 +67,7 @@ public class AuthServer implements AuthApi { return currentUserDto; } - @PostMapping("/logout") + @Override public String logout(){ return "success"; } 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 fe04b1aca2..2eb887365d 100644 --- a/backend/src/main/java/io/dataease/auth/service/AuthUserService.java +++ b/backend/src/main/java/io/dataease/auth/service/AuthUserService.java @@ -9,7 +9,9 @@ public interface AuthUserService { - SysUserEntity getUser(String username); + SysUserEntity getUserById(Long userId); + + SysUserEntity getUserByName(String username); List roles(Long userId); 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 314be618c6..e4fde7aba2 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 @@ -5,6 +5,7 @@ import io.dataease.auth.entity.SysUserEntity; import io.dataease.base.mapper.ext.AuthMapper; import io.dataease.auth.service.AuthUserService; import org.apache.commons.lang3.StringUtils; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import javax.annotation.Resource; @@ -15,14 +16,27 @@ import java.util.stream.Collectors; @Service public class AuthUserServiceImpl implements AuthUserService { + private final String USER_CACHE_NAME = "users_info"; + @Resource private AuthMapper authMapper; + /** + * 此处需被F2CRealm登录认证调用 也就是说每次请求都会被调用 所以最好加上缓存 + * @param userId + * @return + */ + @Cacheable(value = USER_CACHE_NAME, key = "'user' + #userId" ) + @Override + public SysUserEntity getUserById(Long userId){ + return authMapper.findUser(userId); + } @Override - public SysUserEntity getUser(String username){ - return authMapper.findUser(username); + public SysUserEntity getUserByName(String username) { + return authMapper.findUserByName(username); } + @Override public List roles(Long userId){ return authMapper.roleCodes(userId); 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 fb271b508a..83cb7d8c34 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 @@ -30,10 +30,14 @@ public class ShiroServiceImpl implements ShiroService { filterChainDefinitionMap.put("/v3/**","anon"); filterChainDefinitionMap.put("/static/**", "anon"); + + // filterChainDefinitionMap.put("/401", "anon"); + // filterChainDefinitionMap.put("/404", "anon"); // 登陆 + // filterChainDefinitionMap.put("/api/auth/logout", "anon"); filterChainDefinitionMap.put("/api/auth/login", "anon"); // 退出 - //filterChainDefinitionMap.put("/logout", "anon"); + // 放行未授权接口,重定向使用 filterChainDefinitionMap.put("/unauth", "anon"); filterChainDefinitionMap.put("/display/**", "anon"); @@ -43,7 +47,6 @@ public class ShiroServiceImpl implements ShiroService { // 被挤下线 filterChainDefinitionMap.put("/downline", "anon"); // 放行 end ---------------------------------------------------------- - filterChainDefinitionMap.put("/logout", "logout"); /*List extPermissionBeans = extUserMapper.getPermissions(); @@ -53,7 +56,7 @@ public class ShiroServiceImpl implements ShiroService { filterChainDefinitionMap.put(item.getPath(), "jwt," + f2cPerms); }); */ - + filterChainDefinitionMap.put("/api/auth/logout", "logout"); filterChainDefinitionMap.put("/**", "jwt"); return filterChainDefinitionMap; } diff --git a/backend/src/main/java/io/dataease/auth/util/JWTUtils.java b/backend/src/main/java/io/dataease/auth/util/JWTUtils.java index 53a277ac1f..67e27b8752 100644 --- a/backend/src/main/java/io/dataease/auth/util/JWTUtils.java +++ b/backend/src/main/java/io/dataease/auth/util/JWTUtils.java @@ -5,15 +5,19 @@ import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.interfaces.DecodedJWT; +import io.dataease.auth.entity.TokenInfo; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; import java.util.Date; public class JWTUtils { - // 过期时间5分钟 - private static final long EXPIRE_TIME = 5*60*1000; - + // token过期时间5分钟 (过期会自动刷新续命 目的是避免一直都是同一个token ) + private static final long EXPIRE_TIME = 1*60*1000; + // 登录间隔时间 超过这个时间强制重新登录 + private static final long Login_Interval = 2*60*1000; /** @@ -22,10 +26,12 @@ public class JWTUtils { * @param secret 用户的密码 * @return 是否正确 */ - public static boolean verify(String token, String username, String secret) { + public static boolean verify(String token, TokenInfo tokenInfo, String secret) { Algorithm algorithm = Algorithm.HMAC256(secret); JWTVerifier verifier = JWT.require(algorithm) - .withClaim("username", username) + .withClaim("lastLoginTime", tokenInfo.getLastLoginTime()) + .withClaim("username", tokenInfo.getUsername()) + .withClaim("userId", tokenInfo.getUserId()) .build(); verifier.verify(token); return true; @@ -35,18 +41,22 @@ public class JWTUtils { * 获得token中的信息无需secret解密也能获得 * @return token中包含的用户名 */ - public static String getUsername(String token) { - try { - DecodedJWT jwt = JWT.decode(token); - return jwt.getClaim("username").asString(); - } catch (JWTDecodeException e) { - e.printStackTrace(); - return null; + public static TokenInfo tokenInfoByToken(String token) { + DecodedJWT jwt = JWT.decode(token); + String username = jwt.getClaim("username").asString(); + Long userId = jwt.getClaim("userId").asLong(); + Long lastLoginTime = jwt.getClaim("lastLoginTime").asLong(); + if (StringUtils.isEmpty(username) || ObjectUtils.isEmpty(userId) || ObjectUtils.isEmpty(lastLoginTime)){ + throw new RuntimeException("token格式错误!"); } + TokenInfo tokenInfo = TokenInfo.builder().username(username).userId(userId).lastLoginTime(lastLoginTime).build(); + return tokenInfo; } + + public static boolean needRefresh(String token){ Date exp = JWTUtils.getExp(token); return new Date().getTime() >= exp.getTime(); @@ -64,17 +74,19 @@ public class JWTUtils { /** * 生成签名,5min后过期 - * @param username 用户名 + * @param tokenInfo 用户信息 * @param secret 用户的密码 * @return 加密的token */ - public static String sign(String username, String secret) { + public static String sign(TokenInfo tokenInfo, String secret) { try { Date date = new Date(System.currentTimeMillis()+EXPIRE_TIME); Algorithm algorithm = Algorithm.HMAC256(secret); // 附带username信息 return JWT.create() - .withClaim("username", username) + .withClaim("lastLoginTime", tokenInfo.getLastLoginTime()) + .withClaim("username", tokenInfo.getUsername()) + .withClaim("userId", tokenInfo.getUserId()) .withClaim("exp", date) .withExpiresAt(date) .sign(algorithm); diff --git a/backend/src/main/java/io/dataease/auth/util/RsaUtil.java b/backend/src/main/java/io/dataease/auth/util/RsaUtil.java new file mode 100644 index 0000000000..577693cc48 --- /dev/null +++ b/backend/src/main/java/io/dataease/auth/util/RsaUtil.java @@ -0,0 +1,29 @@ +package io.dataease.auth.util; + +import org.apache.commons.codec.binary.Base64; + +import javax.crypto.Cipher; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.spec.PKCS8EncodedKeySpec; + +public class RsaUtil { + + /** + * 私钥解密 + * + * @param privateKeyText 私钥 + * @param text 待解密的文本 + * @return / + * @throws Exception / + */ + public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception { + PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText)); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, privateKey); + byte[] result = cipher.doFinal(Base64.decodeBase64(text)); + return new String(result); + } +} diff --git a/backend/src/main/java/io/dataease/base/mapper/ext/AuthMapper.java b/backend/src/main/java/io/dataease/base/mapper/ext/AuthMapper.java index 59bab3da68..050702436a 100644 --- a/backend/src/main/java/io/dataease/base/mapper/ext/AuthMapper.java +++ b/backend/src/main/java/io/dataease/base/mapper/ext/AuthMapper.java @@ -15,11 +15,12 @@ public interface AuthMapper { List roleCodes(@Param("userId") Long userId); - List permissions(@Param("userId") Long userId); - SysUserEntity findUser(@Param("username") String username); + SysUserEntity findUser(@Param("userId") Long userId); + + SysUserEntity findUserByName(@Param("username") String username); List roles(@Param("userId") Long userId); diff --git a/backend/src/main/java/io/dataease/base/mapper/ext/AuthMapper.xml b/backend/src/main/java/io/dataease/base/mapper/ext/AuthMapper.xml index 4eacbdf053..0c5525e397 100644 --- a/backend/src/main/java/io/dataease/base/mapper/ext/AuthMapper.xml +++ b/backend/src/main/java/io/dataease/base/mapper/ext/AuthMapper.xml @@ -21,6 +21,10 @@ + + diff --git a/backend/src/main/java/io/dataease/commons/utils/AuthUtils.java b/backend/src/main/java/io/dataease/commons/utils/AuthUtils.java index adbfb5ce39..5fcc32d725 100644 --- a/backend/src/main/java/io/dataease/commons/utils/AuthUtils.java +++ b/backend/src/main/java/io/dataease/commons/utils/AuthUtils.java @@ -1,5 +1,6 @@ package io.dataease.commons.utils; +import io.dataease.auth.entity.TokenInfo; import io.dataease.auth.util.JWTUtils; import io.dataease.base.domain.SysUser; import io.dataease.service.sys.SysUserService; @@ -18,9 +19,9 @@ public class AuthUtils { public static SysUser getUser(){ String token = ServletUtils.getToken(); - String username = JWTUtils.getUsername(token); + TokenInfo tokenInfo = JWTUtils.tokenInfoByToken(token); SysUser sysUser = new SysUser(); - sysUser.setUsername(username); + sysUser.setUserId(tokenInfo.getUserId()); SysUser user = sysUserService.findOne(sysUser); return user; } diff --git a/backend/src/main/java/io/dataease/controller/sys/SysUserController.java b/backend/src/main/java/io/dataease/controller/sys/SysUserController.java index a1c8c8d6d0..4750140d4c 100644 --- a/backend/src/main/java/io/dataease/controller/sys/SysUserController.java +++ b/backend/src/main/java/io/dataease/controller/sys/SysUserController.java @@ -6,6 +6,7 @@ import com.github.pagehelper.PageHelper; import io.dataease.commons.utils.PageUtils; import io.dataease.commons.utils.Pager; import io.dataease.controller.sys.request.SysUserCreateRequest; +import io.dataease.controller.sys.request.SysUserPwdRequest; import io.dataease.controller.sys.request.SysUserStateRequest; import io.dataease.controller.sys.request.UserGridRequest; import io.dataease.controller.sys.response.SysUserGridResponse; @@ -55,4 +56,11 @@ public class SysUserController { public void updateStatus(@RequestBody SysUserStateRequest request){ sysUserService.updateStatus(request); } + + @ApiOperation("更新用户密码") + @PostMapping("/updatePwd") + public void updatePwd(@RequestBody SysUserPwdRequest request){ + + sysUserService.updatePwd(request); + } } diff --git a/backend/src/main/java/io/dataease/controller/sys/request/SysUserPwdRequest.java b/backend/src/main/java/io/dataease/controller/sys/request/SysUserPwdRequest.java new file mode 100644 index 0000000000..2627deeede --- /dev/null +++ b/backend/src/main/java/io/dataease/controller/sys/request/SysUserPwdRequest.java @@ -0,0 +1,17 @@ +package io.dataease.controller.sys.request; + + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class SysUserPwdRequest implements Serializable { + + private Long userId; + private String password; + private String repeatPassword; + private String newPassword; + + +} 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 f6cbaca31a..ba7f4d3fd4 100644 --- a/backend/src/main/java/io/dataease/service/sys/SysUserService.java +++ b/backend/src/main/java/io/dataease/service/sys/SysUserService.java @@ -10,6 +10,7 @@ import io.dataease.base.mapper.ext.ExtSysUserMapper; import io.dataease.commons.utils.BeanUtils; import io.dataease.commons.utils.CodingUtil; import io.dataease.controller.sys.request.SysUserCreateRequest; +import io.dataease.controller.sys.request.SysUserPwdRequest; import io.dataease.controller.sys.request.SysUserStateRequest; import io.dataease.controller.sys.request.UserGridRequest; import io.dataease.controller.sys.response.SysUserGridResponse; @@ -17,6 +18,7 @@ import io.dataease.controller.sys.response.SysUserRole; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; +import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; @@ -26,6 +28,7 @@ import java.util.stream.Collectors; @Service public class SysUserService { + private final static String USER_CACHE_NAME = "users_info"; private final static String DEFAULT_PWD = "DataEase123.."; @Resource @@ -83,6 +86,33 @@ public class SysUserService { return sysUserMapper.updateByPrimaryKeySelective(sysUser); } + /** + * 修改用户密码清楚缓存 + * @param request + * @return + */ + @CacheEvict(value = USER_CACHE_NAME, key = "'user' + #request.userId") + public int updatePwd(SysUserPwdRequest request) { + if (!StringUtils.equals(request.getPassword(), request.getRepeatPassword())){ + throw new RuntimeException("两次密码不一致"); + } + SysUser temp = new SysUser(); + temp.setUserId(request.getUserId()); + SysUser user = findOne(temp); + if (ObjectUtils.isEmpty(user)) { + throw new RuntimeException("用户不存在"); + } + if (!StringUtils.equals(request.getPassword(), user.getPassword())){ + throw new RuntimeException("密码错误"); + } + SysUser sysUser = new SysUser(); + sysUser.setUserId(request.getUserId()); + sysUser.setPassword(CodingUtil.md5(request.getNewPassword())); + return sysUserMapper.updateByPrimaryKeySelective(sysUser); + } + + + /** * 删除用户角色关联 * @param userId @@ -108,6 +138,7 @@ public class SysUserService { }); } + @CacheEvict(value = USER_CACHE_NAME, key = "'user' + #userId") @Transactional public int delete(Long userId){ deleteUserRoles(userId); diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index ac1943006c..df77d81e6d 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -50,6 +50,10 @@ spring.servlet.multipart.max-request-size=500MB management.server.port=8083 management.endpoints.web.exposure.include=* #spring.freemarker.checkTemplateLocation=false +#RSA非对称加密参数:私钥 +rsa.private_key=MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9pB6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZUBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3tTbklZkD2A== +spring.cache.type=ehcache +spring.cache.ehcache.config=classpath:/ehcache/ehcache.xml diff --git a/backend/src/main/resources/ehcache/ehcache.xml b/backend/src/main/resources/ehcache/ehcache.xml new file mode 100644 index 0000000000..6af1c3769d --- /dev/null +++ b/backend/src/main/resources/ehcache/ehcache.xml @@ -0,0 +1,49 @@ + + + + + + + + + + \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index dc68f96da9..16bc7e2550 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,6 +19,7 @@ "element-ui": "2.13.0", "fit2cloud-ui": "^0.1.12", "js-cookie": "2.2.0", + "jsencrypt": "^3.0.0-rc.1", "normalize.css": "7.0.0", "nprogress": "0.2.0", "screenfull": "4.2.0", diff --git a/frontend/src/api/system/user.js b/frontend/src/api/system/user.js index eaa7473916..248b7ddaa5 100644 --- a/frontend/src/api/system/user.js +++ b/frontend/src/api/system/user.js @@ -4,7 +4,7 @@ const pathMap = { deletePath: '/api/user/delete/', createPath: '/api/user/create', updatePath: '/api/user/update', - editPasswordPath: '/api/user/password', + editPasswordPath: '/api/user/updatePwd', editStatusPath: '/api/user/updateStatus' } export function userLists(page, size, data) { diff --git a/frontend/src/assets/401_images/401.gif b/frontend/src/assets/401_images/401.gif new file mode 100644 index 0000000000..cd6e0d9433 Binary files /dev/null and b/frontend/src/assets/401_images/401.gif differ diff --git a/frontend/src/permission.js b/frontend/src/permission.js index 00b155fa3b..b99ea5fa91 100644 --- a/frontend/src/permission.js +++ b/frontend/src/permission.js @@ -10,7 +10,7 @@ import { filterAsyncRouter } from '@/store/modules/permission' NProgress.configure({ showSpinner: false }) // NProgress Configuration -const whiteList = ['/login'] // no redirect whitelist +const whiteList = ['/login', '/401', '/404'] // no redirect whitelist router.beforeEach(async(to, from, next) => { // start progress bar diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index f023b5a7e4..26ae503f3e 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -53,6 +53,11 @@ export const constantRoutes = [ component: () => import('@/views/404'), hidden: true }, + { + path: '/401', + component: (resolve) => require(['@/views/401'], resolve), + hidden: true + }, { path: '/', diff --git a/frontend/src/settings.js b/frontend/src/settings.js index d70d24992f..305169c13e 100644 --- a/frontend/src/settings.js +++ b/frontend/src/settings.js @@ -1,5 +1,6 @@ module.exports = { TokenKey: 'Authorization', + RefreshTokenKey: 'refreshauthorization', title: 'DATA_EASE', /** diff --git a/frontend/src/store/modules/user.js b/frontend/src/store/modules/user.js index ee02cef92a..ab250c4e0f 100644 --- a/frontend/src/store/modules/user.js +++ b/frontend/src/store/modules/user.js @@ -60,6 +60,10 @@ const actions = { }) }) }, + refreshToken({ commit }, token) { + commit('SET_TOKEN', token) + setToken(token) + }, // get user info getInfo({ commit, state }) { diff --git a/frontend/src/utils/request.js b/frontend/src/utils/request.js index 837302bfad..05313732a2 100644 --- a/frontend/src/utils/request.js +++ b/frontend/src/utils/request.js @@ -9,6 +9,7 @@ import { tryShowLoading, tryHideLoading } from './loading' // import router from '@/router' const TokenKey = Config.TokenKey +const RefreshTokenKey = Config.RefreshTokenKey // create an axios instance const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url @@ -34,6 +35,7 @@ service.interceptors.request.use( return config }, error => { + error.config.loading && tryHideLoading(store.getters.currentPath) // do something with request error console.log(error) // for debug return Promise.reject(error) @@ -50,12 +52,20 @@ const checkAuth = response => { }) }) } + // token到期后自动续命 刷新token + if (response.headers[RefreshTokenKey]) { + const refreshToken = response.headers[RefreshTokenKey] + store.dispatch('user/refreshToken', refreshToken) + } } const checkPermission = response => { // 请根据实际需求修改 - if (response.status === 403) { - location.href = '/403' + if (response.status === 404) { + location.href = '/404' + } + if (response.status === 401) { + location.href = '/401' } } diff --git a/frontend/src/utils/rsaEncrypt.js b/frontend/src/utils/rsaEncrypt.js new file mode 100644 index 0000000000..83db4809c9 --- /dev/null +++ b/frontend/src/utils/rsaEncrypt.js @@ -0,0 +1,30 @@ +import JSEncrypt from 'jsencrypt/bin/jsencrypt' + +// 密钥对生成 http://web.chacuo.net/netrsakeypair + +const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANL378k3RiZHWx5AfJqdH9xRNBmD9wGD\n' + + '2iRe41HdTNF8RUhNnHit5NpMNtGL0NPTSSpPjjI1kJfVorRvaQerUgkCAwEAAQ==' + +const privateKey = 'MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8\n' + + 'mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9p\n' + + 'B6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue\n' + + '/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZ\n' + + 'UBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6\n' + + 'vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha\n' + + '4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3\n' + + 'tTbklZkD2A==' + +// 加密 +export function encrypt(txt) { + const encryptor = new JSEncrypt() + encryptor.setPublicKey(publicKey) // 设置公钥 + return encryptor.encrypt(txt) // 对需要加密的数据进行加密 +} + +// 解密 +export function decrypt(txt) { + const encryptor = new JSEncrypt() + encryptor.setPrivateKey(privateKey) + return encryptor.decrypt(txt) +} + diff --git a/frontend/src/views/401.vue b/frontend/src/views/401.vue new file mode 100644 index 0000000000..8a3b69e09a --- /dev/null +++ b/frontend/src/views/401.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/frontend/src/views/login/index.vue b/frontend/src/views/login/index.vue index 7917492114..891b3f5b8b 100644 --- a/frontend/src/views/login/index.vue +++ b/frontend/src/views/login/index.vue @@ -50,7 +50,7 @@