diff --git a/RuoYi-App-autoee b/RuoYi-App-autoee new file mode 160000 index 0000000000000000000000000000000000000000..50ae511392434b7d1338bbdd4f1fb598d9ef2986 --- /dev/null +++ b/RuoYi-App-autoee @@ -0,0 +1 @@ +Subproject commit 50ae511392434b7d1338bbdd4f1fb598d9ef2986 diff --git a/ruoyi-pro-autoee/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jwtApi/UserInfoController.java b/ruoyi-pro-autoee/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jwtApi/UserInfoController.java new file mode 100644 index 0000000000000000000000000000000000000000..bc78377d6f9bcb3df9f18be208ab337f7b7d02e8 --- /dev/null +++ b/ruoyi-pro-autoee/ruoyi-admin/src/main/java/com/ruoyi/web/controller/jwtApi/UserInfoController.java @@ -0,0 +1,103 @@ +package com.ruoyi.web.controller.jwtApi; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.constant.UserConstants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.framework.shiro.service.SysPasswordService; +import com.ruoyi.framework.web.service.ConfigService; +import com.ruoyi.system.service.ISysUserService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +/** + * 登录验证 + * @author ruoyi + */ +@RestController +@RequestMapping("/jwtapi/user") +public class UserInfoController extends BaseController { + private static final Logger logger = LoggerFactory.getLogger(UserInfoController.class); + + @Autowired + private ConfigService configService; + @Autowired + private ISysUserService userService; + @Autowired + private SysPasswordService passwordService; + + @GetMapping("getUserInfo") + public AjaxResult getUserInfo() { + SysUser user = ShiroUtils.getSysUser(); + logger.info(JSONUtil.toJsonStr(user)); + // 角色集合 + // Set roles = permissionService.getRolePermission(user); + // 权限集合 + // Set permissions = permissionService.getMenuPermission(user); + AjaxResult ajax = AjaxResult.success(); + ajax.put("user", user); + // ajax.put("roles", roles); + // ajax.put("permissions", permissions); + return ajax; + } + + @GetMapping("getUserProfile") + public AjaxResult getUserProfile() { + SysUser user = ShiroUtils.getSysUser(); + AjaxResult ajax = AjaxResult.success(user); + ajax.put("roleGroup", userService.selectUserRoleGroup(user.getUserId())); + ajax.put("postGroup", userService.selectUserPostGroup(user.getUserId())); + return ajax; + } + + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping("updateUserProfile") + public AjaxResult updateUserProfile(@RequestBody SysUser user) { + SysUser sysUser = ShiroUtils.getSysUser(); + user.setUserName(sysUser.getUserName()); + if (StrUtil.isNotEmpty(user.getPhonenumber()) + && UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) { + return error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + if (StrUtil.isNotEmpty(user.getEmail()) + && UserConstants.NOT_UNIQUE.equals(userService.checkEmailUnique(user))) { + return error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setUserId(sysUser.getUserId()); + user.setPassword(null); + user.setAvatar(null); + user.setDeptId(null); + if (userService.updateUserInfo(user) > 0) { + return success(); + } + return error("修改个人信息异常,请联系管理员"); + } + + @Log(title = "重置密码", businessType = BusinessType.UPDATE) + @PutMapping("/resetPwd") + public AjaxResult resetPwd(String oldPassword, String newPassword) { + SysUser user = getSysUser(); + if (!passwordService.matches(user, oldPassword)) { + return error("修改密码失败,旧密码错误"); + } + if (passwordService.matches(user, newPassword)) { + return error("新密码不能与旧密码相同"); + } + user.setSalt(ShiroUtils.randomSalt()); + user.setPassword(passwordService.encryptPassword(user.getLoginName(), newPassword, user.getSalt())); + user.setPwdUpdateDate(DateUtils.getNowDate()); + if (userService.resetUserPwd(user) > 0) { + setSysUser(userService.selectUserById(user.getUserId())); + return success(); + } + return error("修改密码异常,请联系管理员"); + } +} diff --git a/ruoyi-pro-autoee/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java b/ruoyi-pro-autoee/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java index 02d8f27bc932973bd03927c842a6d53353072ddf..84123b33f34748364990698fc37a919beeca5c57 100644 --- a/ruoyi-pro-autoee/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java +++ b/ruoyi-pro-autoee/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java @@ -2,43 +2,48 @@ package com.ruoyi.web.controller.system; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - -import com.ruoyi.common.config.RuoYiConfig; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.enums.UserStatus; import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.jwt.utils.JwtUtils; +import com.ruoyi.framework.shiro.service.SysPasswordService; import com.ruoyi.framework.web.service.ConfigService; +import com.ruoyi.system.service.ISysUserService; /** * 登录验证 - * + * * @author ruoyi */ @Controller public class SysLoginController extends BaseController { - private static final Logger logger = LoggerFactory.getLogger(SysLoginController.class); /** * 是否开启记住我功能 */ @Value("${shiro.rememberMe.enabled: false}") private boolean rememberMe; + + @Autowired + private ISysUserService userService; + + @Autowired + private SysPasswordService passwordService; @Autowired private ConfigService configService; @@ -51,8 +56,6 @@ public class SysLoginController extends BaseController { return ServletUtils.renderString(response, "{\"code\":\"1\",\"msg\":\"未登录或登录超时。请重新登录\"}"); } - mmap.put("name", RuoYiConfig.getName()); - // 是否开启记住我 mmap.put("isRemembered", rememberMe); // 是否开启用户注册 @@ -81,18 +84,10 @@ public class SysLoginController extends BaseController return error(msg); } } - + @GetMapping("/unauth") public String unauth() { return "error/unauth"; } - @GetMapping("/dealing/{id}") - public String dealing(@PathVariable("id") Long id) - { - logger.info("[提示]-请求尚未开发完成页面-传入id=[{}]",id); - // return "/ems/enterpriseBase/enterpriseBase"; - return "error/dealing"; - } - } diff --git a/ruoyi-pro-autoee/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginJwtController.java b/ruoyi-pro-autoee/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginJwtController.java new file mode 100644 index 0000000000000000000000000000000000000000..64044b3e4773592886af6ed308bf2d86612f8dee --- /dev/null +++ b/ruoyi-pro-autoee/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginJwtController.java @@ -0,0 +1,99 @@ +package com.ruoyi.web.controller.system; + +import com.ruoyi.common.config.RuoYiConfig; +import com.ruoyi.common.constant.Constants; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.core.domain.model.LoginBody; +import com.ruoyi.common.core.text.Convert; +import com.ruoyi.common.enums.UserStatus; +import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.jwt.utils.JwtUtils; +import com.ruoyi.framework.shiro.service.SysPasswordService; +import com.ruoyi.framework.web.service.ConfigService; +import com.ruoyi.system.service.ISysUserService; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.apache.shiro.subject.Subject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Set; + +/** + * 登录验证 + * @author ruoyi + */ +@RestController +@RequestMapping("/jwt") +public class SysLoginJwtController extends BaseController { + private static final Logger logger = LoggerFactory.getLogger(SysLoginJwtController.class); + /** + * 是否开启记住我功能 + */ + @Value("${shiro.rememberMe.enabled: false}") + private boolean rememberMe; + + @Autowired + private ConfigService configService; + @Autowired + private ISysUserService userService; + @Autowired + private SysPasswordService passwordService; + + /** + * 登录方法 + * @param loginBody 登录信息 + * @return 结果 + */ + @PostMapping("/loginByJwt") + public AjaxResult loginByJwt(@RequestBody LoginBody loginBody) { + String username = loginBody.getUsername(); + String password = loginBody.getPassword(); + if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) + { + return AjaxResult.error("账号和密码不能为空!"); + } + + SysUser user = userService.selectUserByLoginName(username); + if (user == null) + { + return AjaxResult.error("用户不存在/密码错误!"); + } + + if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) + { + return AjaxResult.error("对不起,您的账号已被删除!"); + } + + if (UserStatus.DISABLE.getCode().equals(user.getStatus())) + { + return AjaxResult.error("用户已封禁,请联系管理员!"); + } + + if (!passwordService.matches(user, password)) + { + return AjaxResult.error("用户不存在/密码错误!"); + } + + String token = JwtUtils.createToken(username, user.getPassword()); + return AjaxResult.success("登录成功,请妥善保管您的token信息").put("token", token); + } + /** + * 退出登录方法 + */ + @PostMapping("/logoutByJwt") + public AjaxResult logoutByJwt() { + return AjaxResult.success(); + } +} diff --git a/ruoyi-pro-autoee/ruoyi-admin/src/main/resources/application.yml b/ruoyi-pro-autoee/ruoyi-admin/src/main/resources/application.yml index 9b119f42f7d6f9cc4a5ecd3a7c9f7232b20bf1a1..8dee8fdb5bf2f7251a3eb93c23e727791caa309a 100644 --- a/ruoyi-pro-autoee/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-pro-autoee/ruoyi-admin/src/main/resources/application.yml @@ -143,3 +143,11 @@ swagger: enabled: true +# token配置 +token: + # 令牌自定义标识 + header: Authorization + # 令牌密钥 + secret: abcdefghijklmnopqrstuvwxyz + # 令牌有效期(默认30分钟) + expireTime: 30 \ No newline at end of file diff --git a/ruoyi-pro-autoee/ruoyi-admin/src/main/resources/static/ruoyi/login.js b/ruoyi-pro-autoee/ruoyi-admin/src/main/resources/static/ruoyi/login.js index 441399a26b7e98c31c3c94e69603d896fbe7d784..57ba3af5556cf1a01373afafae42b96ffcb2522d 100644 --- a/ruoyi-pro-autoee/ruoyi-admin/src/main/resources/static/ruoyi/login.js +++ b/ruoyi-pro-autoee/ruoyi-admin/src/main/resources/static/ruoyi/login.js @@ -9,7 +9,7 @@ $(function() { // TODO 测试时,自动登陆 自动登录系统,避免频繁点击,开发完成后需要去掉自动登陆 Fuxs // 需要给login.html页面上的用户名、密码设置默认值 - login(); + // login(); }); $.validator.setDefaults({ diff --git a/ruoyi-pro-autoee/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java b/ruoyi-pro-autoee/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java index b31614169dd00acece846e104771077bfe0dba76..155e1a24ab315fe97e47c9daa808c1d0d36f4eee 100644 --- a/ruoyi-pro-autoee/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java +++ b/ruoyi-pro-autoee/ruoyi-common/src/main/java/com/ruoyi/common/constant/UserConstants.java @@ -107,4 +107,8 @@ public class UserConstants * 邮箱格式限制 */ public static final String EMAIL_PATTERN = "^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.?"; + + /** 校验返回结果码 */ + public final static String UNIQUE = "0"; + public final static String NOT_UNIQUE = "1"; } diff --git a/ruoyi-pro-autoee/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java b/ruoyi-pro-autoee/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java new file mode 100644 index 0000000000000000000000000000000000000000..b5bc8c8e2fcb25591305b45f21fd93ee3c315b2c --- /dev/null +++ b/ruoyi-pro-autoee/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginBody.java @@ -0,0 +1,69 @@ +package com.ruoyi.common.core.domain.model; + +/** + * 用户登录对象 + * + * @author ruoyi + */ +public class LoginBody +{ + /** + * 用户名 + */ + private String username; + + /** + * 用户密码 + */ + private String password; + + /** + * 验证码 + */ + private String code; + + /** + * 唯一标识 + */ + private String uuid; + + public String getUsername() + { + return username; + } + + public void setUsername(String username) + { + this.username = username; + } + + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + public String getCode() + { + return code; + } + + public void setCode(String code) + { + this.code = code; + } + + public String getUuid() + { + return uuid; + } + + public void setUuid(String uuid) + { + this.uuid = uuid; + } +} diff --git a/ruoyi-pro-autoee/ruoyi-framework/pom.xml b/ruoyi-pro-autoee/ruoyi-framework/pom.xml index 5265250244253855b8ced4f17ce5b6faf19bbaff..fd6dade5dcb1ce45615313f03e9fdb0ad6f70ddc 100644 --- a/ruoyi-pro-autoee/ruoyi-framework/pom.xml +++ b/ruoyi-pro-autoee/ruoyi-framework/pom.xml @@ -17,8 +17,8 @@ - - + + org.springframework.boot spring-boot-starter-web @@ -77,6 +77,12 @@ ruoyi-system + + + com.auth0 + java-jwt + 3.4.0 + \ No newline at end of file diff --git a/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java b/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java index 536f4bf81a5817c50cdc369db8d21a9aacdc7f95..06b0027ddf6f433bd8b6803c1b294382fef7cad5 100644 --- a/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java +++ b/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java @@ -3,12 +3,9 @@ package com.ruoyi.framework.config; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; - import javax.servlet.Filter; - import org.apache.commons.io.IOUtils; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.codec.Base64; @@ -24,11 +21,12 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; - import com.ruoyi.common.constant.Constants; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.security.CipherUtils; import com.ruoyi.common.utils.spring.SpringUtils; +import com.ruoyi.framework.jwt.auth.AllowAllCredentialsMatcher; +import com.ruoyi.framework.jwt.filter.JwtFilter; import com.ruoyi.framework.shiro.realm.UserRealm; import com.ruoyi.framework.shiro.session.OnlineSessionDAO; import com.ruoyi.framework.shiro.session.OnlineSessionFactory; @@ -40,12 +38,11 @@ import com.ruoyi.framework.shiro.web.filter.online.OnlineSessionFilter; import com.ruoyi.framework.shiro.web.filter.sync.SyncOnlineSessionFilter; import com.ruoyi.framework.shiro.web.session.OnlineWebSessionManager; import com.ruoyi.framework.shiro.web.session.SpringSessionValidationScheduler; - import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; /** * 权限配置加载 - * + * * @author ruoyi */ @Configuration @@ -135,12 +132,6 @@ public class ShiroConfig @Value("${shiro.rememberMe.enabled: false}") private boolean rememberMe; - /** - * 后端免认证接口url - */ - @Value("${shiro.auth.anonUrls}") - private String anonUrls; - /** * 缓存管理器 使用Ehcache实现 */ @@ -195,6 +186,7 @@ public class ShiroConfig UserRealm userRealm = new UserRealm(); userRealm.setAuthorizationCacheName(Constants.SYS_AUTH_CACHE); userRealm.setCacheManager(cacheManager); + userRealm.setCredentialsMatcher(new AllowAllCredentialsMatcher()); return userRealm; } @@ -303,15 +295,9 @@ public class ShiroConfig filterChainDefinitionMap.put("/logout", "logout"); // 不需要拦截的访问 filterChainDefinitionMap.put("/login", "anon,captchaValidate"); - - // ADD BY yangzh 20221009 追加后端免登录接口配置 BEGIN - if (StringUtils.isNotBlank(anonUrls)) { - Arrays.asList(anonUrls.split(",")).stream().forEach( anonUrl -> { - filterChainDefinitionMap.put(anonUrl.trim(), "anon"); - }); - } - // ADD BY yangzh 20221009 追加后端免登录接口配置 END - + filterChainDefinitionMap.put("/jwt/login", "anon"); + filterChainDefinitionMap.put("/jwt/loginByJwt", "anon"); + filterChainDefinitionMap.put("/jwt/logoutByJwt", "anon"); // 注册相关 filterChainDefinitionMap.put("/register", "anon,captchaValidate"); // 系统权限列表 @@ -322,10 +308,16 @@ public class ShiroConfig filters.put("syncOnlineSession", syncOnlineSessionFilter()); filters.put("captchaValidate", captchaValidateFilter()); filters.put("kickout", kickoutSessionFilter()); + filters.put("jwt", new JwtFilter()); // 注销成功,则跳转到指定页面 filters.put("logout", logoutFilter()); shiroFilterFactoryBean.setFilters(filters); + // jwt 请求单独验证 + filterChainDefinitionMap.put("/api/**", "jwt"); + filterChainDefinitionMap.put("/jwt/**", "jwt"); + filterChainDefinitionMap.put("/jwtapi/**", "jwt"); + // 所有请求需要认证 filterChainDefinitionMap.put("/**", "user,kickout,onlineSession,syncOnlineSession"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); diff --git a/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/auth/AllowAllCredentialsMatcher.java b/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/auth/AllowAllCredentialsMatcher.java new file mode 100644 index 0000000000000000000000000000000000000000..9131fdcfe5132d12b10371ae4a8aee750f2ee5ee --- /dev/null +++ b/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/auth/AllowAllCredentialsMatcher.java @@ -0,0 +1,19 @@ +package com.ruoyi.framework.jwt.auth; + +import org.apache.shiro.authc.AuthenticationInfo; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.credential.SimpleCredentialsMatcher; + +/** + * 无需验证密码 + * + * @author ruoyi + */ +public class AllowAllCredentialsMatcher extends SimpleCredentialsMatcher +{ + @Override + public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) + { + return true; + } +} diff --git a/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/auth/JwtToken.java b/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/auth/JwtToken.java new file mode 100644 index 0000000000000000000000000000000000000000..3dfc19eac87fd28268d71576ac02c81181b6686d --- /dev/null +++ b/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/auth/JwtToken.java @@ -0,0 +1,45 @@ +package com.ruoyi.framework.jwt.auth; + +import org.apache.shiro.authc.UsernamePasswordToken; + +/** + * 自定义登录Token + * + * @author ruoyi + */ +public class JwtToken extends UsernamePasswordToken +{ + private static final long serialVersionUID = 1L; + + private String token; + + public JwtToken() + { + } + + public JwtToken(String username, String password, boolean rememberMe) + { + super(username, password, rememberMe); + } + + public JwtToken(String username, String password) + { + super(username, password, false); + } + + public JwtToken(String token) + { + super("", "", false); + this.token = token; + } + + public String getToken() + { + return token; + } + + public void setToken(String token) + { + this.token = token; + } +} \ No newline at end of file diff --git a/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/filter/JwtFilter.java b/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/filter/JwtFilter.java new file mode 100644 index 0000000000000000000000000000000000000000..4becc8e463db5ae872baccf330dca75949e7885c --- /dev/null +++ b/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/filter/JwtFilter.java @@ -0,0 +1,119 @@ +package com.ruoyi.framework.jwt.filter; + +import java.io.IOException; +import java.io.PrintWriter; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.shiro.authc.AuthenticationException; +import org.apache.shiro.web.filter.AccessControlFilter; +import org.apache.shiro.web.util.WebUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.RequestMethod; +import com.alibaba.fastjson.JSON; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.exceptions.TokenExpiredException; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.framework.jwt.auth.JwtToken; + +/** + * jwt 自定义拦截器 + * @author ruoyi + */ +public class JwtFilter extends AccessControlFilter { + private static final Logger LOGGER = LoggerFactory.getLogger(JwtFilter.class); + + private static final String AUTHZ_HEADER = "Authorization"; + // private static final String AUTHZ_HEADER = "token"; + + private final ThreadLocal MSG_HOLDER = new ThreadLocal<>(); + + @Override + public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { + return super.onPreHandle(request, response, mappedValue); + } + + @Override + protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { + return this.executeLogin(request, response); + } + + /** + * 执行登录方法(UserRealm判断,异常返回false) + */ + protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { + String token = WebUtils.toHttp(request).getHeader(AUTHZ_HEADER); + if (StringUtils.isEmpty(token)) { + MSG_HOLDER.set("消息头不正确,header需要携带token参数"); + return false; + } + if (StringUtils.isNotEmpty(token) && token.startsWith("Bearer ")) { + token = token.replace("Bearer ", ""); + } + try { + // 断是否有权限 + JwtToken jwtToken = new JwtToken(token); + this.getSubject(request, response).login(jwtToken); + return true; + } catch (AuthenticationException e) { + if (e.getCause() instanceof TokenExpiredException) { + MSG_HOLDER.set("token已过期"); + } else if (e.getCause() instanceof JWTVerificationException) { + MSG_HOLDER.set("用户密码错误"); + } else { + MSG_HOLDER.set("用户信息验证失败:" + e.getMessage()); + } + return false; + } + } + + /** + * 请求前处理,处理跨域 + */ + @Override + protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + HttpServletResponse httpServletResponse = (HttpServletResponse) response; + httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin")); + httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE"); + httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers")); + // 跨域时,option请求直接返回正常状态 + if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) { + httpServletResponse.setStatus(HttpStatus.OK.value()); + return false; + } + return super.preHandle(request, response); + } + + /** + * 异常处理 + */ + @Override + protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { + this.jwtFail(request, response, 401, "对不起,您无权限进行操作!"); + return false; + } + + /** + * 认证失败,异常返回 + */ + protected void jwtFail(ServletRequest request, ServletResponse response, int code, String message) { + HttpServletResponse httpResponse = WebUtils.toHttp(response); + String contentType = "application/json;charset=UTF-8"; + httpResponse.setStatus(401); + httpResponse.setContentType(contentType); + try { + String msg = StringUtils.isNotEmpty(MSG_HOLDER.get()) ? MSG_HOLDER.get() : message; + AjaxResult ajaxResult = new AjaxResult().put(AjaxResult.CODE_TAG, code).put(AjaxResult.MSG_TAG, msg); + PrintWriter printWriter = httpResponse.getWriter(); + printWriter.append(JSON.toJSONString(ajaxResult)); + } catch (IOException e) { + LOGGER.error("sendChallenge error,can not resolve httpServletResponse"); + } + } +} diff --git a/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/utils/JwtUtils.java b/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/utils/JwtUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..c0ce30121792346df49e38b27b334f16902fb657 --- /dev/null +++ b/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/utils/JwtUtils.java @@ -0,0 +1,66 @@ +package com.ruoyi.framework.jwt.utils; + +import java.util.Date; +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTDecodeException; +import com.auth0.jwt.exceptions.JWTVerificationException; +import com.auth0.jwt.exceptions.TokenExpiredException; +import com.auth0.jwt.interfaces.DecodedJWT; + +/** + * jwt 工具类 + * + * @author ruoyi + */ +public class JwtUtils +{ + private static final long EXPIRE_TIME = 30 * 60 * 1000; + + private static final String CLAIM_NAME = "username"; + + public static String createToken(String username, String password) + { + return createToken(username, password, EXPIRE_TIME); + } + + public static String createToken(String username, String password, long expireTime) + { + Date date = new Date(System.currentTimeMillis() + expireTime); + // 加密处理密码 + Algorithm algorithm = Algorithm.HMAC256(password); + return JWT.create().withClaim(CLAIM_NAME, username).withExpiresAt(date).sign(algorithm); + } + + public static void verify(String username, String dbPwd, String token) + { + Algorithm algorithm = Algorithm.HMAC256(dbPwd); + JWTVerifier jwtVerifier = JWT.require(algorithm).withClaim(CLAIM_NAME, username).build(); + try + { + jwtVerifier.verify(token); + } + catch (TokenExpiredException e) + { + throw new TokenExpiredException("token已过期"); + } + catch (JWTVerificationException e) + { + throw new JWTVerificationException("token验证失败"); + } + } + + public static String getUserName(String token) + { + try + { + DecodedJWT jwt = JWT.decode(token); + return jwt.getClaim(CLAIM_NAME).asString(); + } + catch (JWTDecodeException e) + { + return null; + } + } +} diff --git a/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/realm/UserRealm.java b/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/realm/UserRealm.java index e77fafc8b48eae85346ec49a07737362e3d89b2e..f7c00f992f64bcdbe57b06f788642c0ce7727ce4 100644 --- a/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/realm/UserRealm.java +++ b/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/shiro/realm/UserRealm.java @@ -2,6 +2,7 @@ package com.ruoyi.framework.shiro.realm; import java.util.HashSet; import java.util.Set; +import org.apache.shiro.authc.AccountException; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; @@ -21,16 +22,21 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.enums.UserStatus; import com.ruoyi.common.exception.user.CaptchaException; import com.ruoyi.common.exception.user.RoleBlockedException; import com.ruoyi.common.exception.user.UserBlockedException; +import com.ruoyi.common.exception.user.UserDeleteException; import com.ruoyi.common.exception.user.UserNotExistsException; import com.ruoyi.common.exception.user.UserPasswordNotMatchException; import com.ruoyi.common.exception.user.UserPasswordRetryLimitExceedException; import com.ruoyi.common.utils.ShiroUtils; +import com.ruoyi.framework.jwt.auth.JwtToken; +import com.ruoyi.framework.jwt.utils.JwtUtils; import com.ruoyi.framework.shiro.service.SysLoginService; import com.ruoyi.system.service.ISysMenuService; import com.ruoyi.system.service.ISysRoleService; +import com.ruoyi.system.service.ISysUserService; /** * 自定义Realm 处理登录 权限 @@ -50,6 +56,9 @@ public class UserRealm extends AuthorizingRealm @Autowired private SysLoginService loginService; + @Autowired + private ISysUserService userService; + /** * 授权 */ @@ -84,52 +93,93 @@ public class UserRealm extends AuthorizingRealm * 登录认证 */ @Override - protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException + protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) + throws AuthenticationException { - UsernamePasswordToken upToken = (UsernamePasswordToken) token; - String username = upToken.getUsername(); - String password = ""; - if (upToken.getPassword() != null) + if (authenticationToken instanceof JwtToken) { - password = new String(upToken.getPassword()); - } + JwtToken jwtToken = (JwtToken) authenticationToken; + String token = jwtToken.getToken(); + String username = JwtUtils.getUserName(token); + if (username == null) + { + throw new AccountException("token 验证失败"); + } + SysUser user = userService.selectUserByLoginName(username); + if (user == null) + { + throw new AuthenticationException("用户数据不存在"); + } - SysUser user = null; - try - { - user = loginService.login(username, password); - } - catch (CaptchaException e) - { - throw new AuthenticationException(e.getMessage(), e); - } - catch (UserNotExistsException e) - { - throw new UnknownAccountException(e.getMessage(), e); - } - catch (UserPasswordNotMatchException e) - { - throw new IncorrectCredentialsException(e.getMessage(), e); - } - catch (UserPasswordRetryLimitExceedException e) - { - throw new ExcessiveAttemptsException(e.getMessage(), e); - } - catch (UserBlockedException e) - { - throw new LockedAccountException(e.getMessage(), e); - } - catch (RoleBlockedException e) - { - throw new LockedAccountException(e.getMessage(), e); + try + { + JwtUtils.verify(username, user.getPassword(), jwtToken.getToken()); + + if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) + { + throw new UserDeleteException(); + } + + if (UserStatus.DISABLE.getCode().equals(user.getStatus())) + { + throw new UserBlockedException(); + } + } + catch (Exception e) + { + log.info("对用户[" + username + "]进行jwt登录验证..验证未通过{}", e.getMessage()); + throw new AuthenticationException(e.getMessage(), e); + } + + return new SimpleAuthenticationInfo(user, null, getName()); } - catch (Exception e) + else { - log.info("对用户[" + username + "]进行登录验证..验证未通过{}", e.getMessage()); - throw new AuthenticationException(e.getMessage(), e); + UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken; + String username = upToken.getUsername(); + String password = ""; + if (upToken.getPassword() != null) + { + password = new String(upToken.getPassword()); + } + + SysUser user = null; + try + { + user = loginService.login(username, password); + } + catch (CaptchaException e) + { + throw new AuthenticationException(e.getMessage(), e); + } + catch (UserNotExistsException e) + { + throw new UnknownAccountException(e.getMessage(), e); + } + catch (UserPasswordNotMatchException e) + { + throw new IncorrectCredentialsException(e.getMessage(), e); + } + catch (UserPasswordRetryLimitExceedException e) + { + throw new ExcessiveAttemptsException(e.getMessage(), e); + } + catch (UserBlockedException e) + { + throw new LockedAccountException(e.getMessage(), e); + } + catch (RoleBlockedException e) + { + throw new LockedAccountException(e.getMessage(), e); + } + catch (Exception e) + { + log.info("对用户[" + username + "]进行登录验证..验证未通过{}", e.getMessage()); + throw new AuthenticationException(e.getMessage(), e); + } + SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, null, getName()); + return info; } - SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName()); - return info; } /** diff --git a/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java b/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java index 6e63333a2867f029d1fe28106682467740bc3fa8..f600fa08dff977f935b7fb4823d1266a4e9ec0f6 100644 --- a/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java +++ b/ruoyi-pro-autoee/ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java @@ -1,11 +1,9 @@ package com.ruoyi.framework.web.exception; import javax.servlet.http.HttpServletRequest; - import org.apache.shiro.authz.AuthorizationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.dao.DuplicateKeyException; import org.springframework.validation.BindException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -15,37 +13,33 @@ import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.exception.DemoModeException; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.ServletUtils; +import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.security.PermissionUtils; /** * 全局异常处理器 + * * @author ruoyi */ @RestControllerAdvice -public class GlobalExceptionHandler { +public class GlobalExceptionHandler +{ private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); - /** - * Fuxs 自定义验证异常 - */ - @ExceptionHandler(DuplicateKeyException.class) - public AjaxResult handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) { - String requestURI = request.getRequestURI(); - log.error("【异常】-请求地址'{}'时出现主键冲突异常!", requestURI, e); - String message = e.getMessage(); - return AjaxResult.error("请求操作出现主键冲突异常!
" + message); - } - /** * 权限校验异常(ajax请求返回json,redirect请求跳转页面) */ @ExceptionHandler(AuthorizationException.class) - public Object handleAuthorizationException(AuthorizationException e, HttpServletRequest request) { + public Object handleAuthorizationException(AuthorizationException e, HttpServletRequest request) + { String requestURI = request.getRequestURI(); - log.error("【异常】-请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage()); - if (ServletUtils.isAjaxRequest(request)) { + log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage()); + if (ServletUtils.isAjaxRequest(request) || StringUtils.isNotEmpty(request.getHeader("token"))) + { return AjaxResult.error(PermissionUtils.getMsg(e.getMessage())); - } else { + } + else + { return new ModelAndView("error/unauth"); } } @@ -55,9 +49,10 @@ public class GlobalExceptionHandler { */ @ExceptionHandler(HttpRequestMethodNotSupportedException.class) public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, - HttpServletRequest request) { + HttpServletRequest request) + { String requestURI = request.getRequestURI(); - log.error("【异常】-请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); + log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); return AjaxResult.error(e.getMessage()); } @@ -65,9 +60,10 @@ public class GlobalExceptionHandler { * 拦截未知的运行时异常 */ @ExceptionHandler(RuntimeException.class) - public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) { + public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) + { String requestURI = request.getRequestURI(); - log.error("【异常】-请求地址'{}',发生未知异常.", requestURI, e); + log.error("请求地址'{}',发生未知异常.", requestURI, e); return AjaxResult.error(e.getMessage()); } @@ -75,9 +71,10 @@ public class GlobalExceptionHandler { * 系统异常 */ @ExceptionHandler(Exception.class) - public AjaxResult handleException(Exception e, HttpServletRequest request) { + public AjaxResult handleException(Exception e, HttpServletRequest request) + { String requestURI = request.getRequestURI(); - log.error("【异常】-请求地址'{}',发生系统异常.", requestURI, e); + log.error("请求地址'{}',发生系统异常.", requestURI, e); return AjaxResult.error(e.getMessage()); } @@ -85,12 +82,15 @@ public class GlobalExceptionHandler { * 业务异常 */ @ExceptionHandler(ServiceException.class) - public Object handleServiceException(ServiceException e, HttpServletRequest request) { - String requestURI = request.getRequestURI(); - log.error("【异常】-请求地址'{}'时出现业务异常!", requestURI, e); - if (ServletUtils.isAjaxRequest(request)) { + public Object handleServiceException(ServiceException e, HttpServletRequest request) + { + log.error(e.getMessage(), e); + if (ServletUtils.isAjaxRequest(request)) + { return AjaxResult.error(e.getMessage()); - } else { + } + else + { return new ModelAndView("error/service", "errorMessage", e.getMessage()); } } @@ -99,9 +99,9 @@ public class GlobalExceptionHandler { * 自定义验证异常 */ @ExceptionHandler(BindException.class) - public AjaxResult handleBindException(BindException e, HttpServletRequest request) { - String requestURI = request.getRequestURI(); - log.error("【异常】-请求地址'{}'时出现参数绑定异常!", requestURI, e); + public AjaxResult handleBindException(BindException e) + { + log.error(e.getMessage(), e); String message = e.getAllErrors().get(0).getDefaultMessage(); return AjaxResult.error(message); } @@ -110,7 +110,8 @@ public class GlobalExceptionHandler { * 演示模式异常 */ @ExceptionHandler(DemoModeException.class) - public AjaxResult handleDemoModeException(DemoModeException e) { + public AjaxResult handleDemoModeException(DemoModeException e) + { return AjaxResult.error("演示模式,不允许操作"); } }