Ver código fonte

jwt websocket

bihuisong 1 ano atrás
pai
commit
e16ce5cecd

+ 14 - 23
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SecurityController.java

@@ -1,28 +1,21 @@
 package com.ruoyi.web.controller.system;
 
 
-import com.ruoyi.common.core.domain.R;
 import com.ruoyi.common.core.domain.Result;
+import com.ruoyi.framework.jwt.utils.JwtUtils;
 import com.ruoyi.system.domain.TokenRequest;
 import com.ruoyi.web.controller.tool.RsaUtil;
 import com.ruoyi.web.controller.tool.SecretKeyBo;
 import com.ruoyi.web.controller.tool.StringUtils;
 import com.ruoyi.common.redis.RedisService;
-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.springframework.web.bind.annotation.*;
-import com.ruoyi.common.utils.CookieUtils;
 import javax.annotation.Resource;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
+
 import java.security.NoSuchAlgorithmException;
 import java.util.concurrent.TimeUnit;
 
-import static com.ruoyi.common.core.domain.AjaxResult.error;
-import static com.ruoyi.common.core.domain.AjaxResult.success;
+
 
 @RestController
 @RequestMapping("/auth")
@@ -59,22 +52,20 @@ public class SecurityController {
      * 获取token
      */
     @PostMapping("/getToken")
-    public Result<?> getToken(@RequestBody TokenRequest form,HttpServletRequest request) {
+    public Result<?> getToken(@RequestBody TokenRequest form) {
         Result<Object> result = new Result<>();
         //解密密码字符串
-        String privateKey = redisService.getCacheObject("remoteSecretKey");
-        try {
-            form.setPassword(RsaUtil.decrypt(form.getPassword(), privateKey));
-        } catch (Exception e) {
-            result.setCode(40002);
-            result.setMsg("获取token失败");
-            return result;
-        }
-        UsernamePasswordToken token = new UsernamePasswordToken(form.getUsername(), form.getPassword(), true);
-        Subject subject = SecurityUtils.getSubject();
+//        String privateKey = redisService.getCacheObject("remoteSecretKey");
+//        try {
+//            form.setPassword(RsaUtil.decrypt(form.getPassword(), privateKey));
+//        } catch (Exception e) {
+//            result.setCode(40002);
+//            result.setMsg("获取token失败");
+//            return result;
+//        }
+        String token = JwtUtils.createToken(form.getUsername(), form.getPassword());
         try {
-            subject.login(token);
-            return Result.ok(subject);
+            return Result.ok(token);
         } catch (AuthenticationException e) {
             String msg = "用户或密码错误";
             if (com.ruoyi.common.utils.StringUtils.isNotEmpty(e.getMessage())) {

+ 49 - 18
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java

@@ -2,6 +2,7 @@ package com.ruoyi.web.controller.system;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.authc.AuthenticationException;
 import org.apache.shiro.authc.UsernamePasswordToken;
@@ -15,19 +16,23 @@ 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
-{
+public class SysLoginController extends BaseController {
     /**
      * 是否开启记住我功能
      */
@@ -35,14 +40,18 @@ public class SysLoginController extends BaseController
     private boolean rememberMe;
 
     @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private SysPasswordService passwordService;
+
+    @Autowired
     private ConfigService configService;
 
     @GetMapping("/login")
-    public String login(HttpServletRequest request, HttpServletResponse response, ModelMap mmap)
-    {
+    public String login(HttpServletRequest request, HttpServletResponse response, ModelMap mmap) {
         // 如果是Ajax请求,返回Json字符串。
-        if (ServletUtils.isAjaxRequest(request))
-        {
+        if (ServletUtils.isAjaxRequest(request)) {
             return ServletUtils.renderString(response, "{\"code\":\"1\",\"msg\":\"未登录或登录超时。请重新登录\"}");
         }
         // 是否开启记住我
@@ -54,29 +63,51 @@ public class SysLoginController extends BaseController
 
     @PostMapping("/login")
     @ResponseBody
-    public AjaxResult ajaxLogin(String username, String password, Boolean rememberMe)
-    {
+    public AjaxResult ajaxLogin(String username, String password, Boolean rememberMe) {
         UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
         Subject subject = SecurityUtils.getSubject();
-        try
-        {
+        try {
             subject.login(token);
             return success();
-        }
-        catch (AuthenticationException e)
-        {
+        } catch (AuthenticationException e) {
             String msg = "用户或密码错误";
-            if (StringUtils.isNotEmpty(e.getMessage()))
-            {
+            if (StringUtils.isNotEmpty(e.getMessage())) {
                 msg = e.getMessage();
             }
             return error(msg);
         }
     }
 
+    @PostMapping("/jwt/login")
+    @ResponseBody
+    public AjaxResult jwtLogin(String username, String password) {
+        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);
+    }
+
     @GetMapping("/unauth")
-    public String unauth()
-    {
+    public String unauth() {
         return "error/unauth";
     }
 }

+ 9 - 2
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ShiroConfig.java

@@ -25,6 +25,8 @@ 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;
@@ -184,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;
     }
 
@@ -287,15 +290,15 @@ public class ShiroConfig
         filterChainDefinitionMap.put("/ajax/**", "anon");
         filterChainDefinitionMap.put("/js/**", "anon");
         filterChainDefinitionMap.put("/ruoyi/**", "anon");
+        filterChainDefinitionMap.put("/auth/**", "anon");
         filterChainDefinitionMap.put("/captcha/captchaImage**", "anon");
         // 退出 logout地址,shiro去清除session
         filterChainDefinitionMap.put("/logout", "logout");
         // 不需要拦截的访问
         filterChainDefinitionMap.put("/login", "anon,captchaValidate");
+        filterChainDefinitionMap.put("/jwt/login", "anon");
         // 注册相关
         filterChainDefinitionMap.put("/register", "anon,captchaValidate");
-        //远程获取token和密钥白名单
-        filterChainDefinitionMap.put("/auth/**", "anon,captchaValidate");
         // 系统权限列表
         // filterChainDefinitionMap.putAll(SpringUtils.getBean(IMenuService.class).selectPermsAll());
 
@@ -304,10 +307,14 @@ 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/webSite/**", "jwt");
+
         // 所有请求需要认证
         filterChainDefinitionMap.put("/**", "user,kickout,onlineSession,syncOnlineSession");
         shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

+ 19 - 0
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;
+    }
+}

+ 38 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/auth/JwtToken.java

@@ -0,0 +1,38 @@
+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;
+    }
+}

+ 116 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/jwt/filter/JwtFilter.java

@@ -0,0 +1,116 @@
+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 final ThreadLocal<String> 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;
+        }
+        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");
+        }
+    }
+}

+ 66 - 0
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;
+        }
+    }
+}

+ 90 - 40
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;
     }
 
     /**

+ 2 - 26
ruoyi-framework/src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java

@@ -6,15 +6,14 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.validation.BindException;
 import org.springframework.web.HttpRequestMethodNotSupportedException;
-import org.springframework.web.bind.MissingPathVariableException;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
-import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
 import org.springframework.web.servlet.ModelAndView;
 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;
 
 /**
@@ -35,7 +34,7 @@ public class GlobalExceptionHandler
     {
         String requestURI = request.getRequestURI();
         log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
-        if (ServletUtils.isAjaxRequest(request))
+            if (ServletUtils.isAjaxRequest(request) || StringUtils.isNotEmpty(request.getHeader("token")))
         {
             return AjaxResult.error(PermissionUtils.getMsg(e.getMessage()));
         }
@@ -97,29 +96,6 @@ public class GlobalExceptionHandler
     }
 
     /**
-     * 请求路径中缺少必需的路径变量
-     */
-    @ExceptionHandler(MissingPathVariableException.class)
-    public AjaxResult handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request)
-    {
-        String requestURI = request.getRequestURI();
-        log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e);
-        return AjaxResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName()));
-    }
-
-    /**
-     * 请求参数类型不匹配
-     */
-    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
-    public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e,
-            HttpServletRequest request)
-    {
-        String requestURI = request.getRequestURI();
-        log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e);
-        return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), e.getValue()));
-    }
-
-    /**
      * 自定义验证异常
      */
     @ExceptionHandler(BindException.class)

+ 59 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/websocket/SemaphoreUtils.java

@@ -0,0 +1,59 @@
+package com.ruoyi.framework.websocket;
+
+import java.util.concurrent.Semaphore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 信号量相关处理
+ * 
+ * @author ruoyi
+ */
+public class SemaphoreUtils
+{
+
+    /**
+     * SemaphoreUtils 日志控制器
+     */
+    private static final Logger LOGGER = LoggerFactory.getLogger(SemaphoreUtils.class);
+
+    /**
+     * 获取信号量
+     * 
+     * @param semaphore
+     * @return
+     */
+    public static boolean tryAcquire(Semaphore semaphore)
+    {
+        boolean flag = false;
+
+        try
+        {
+            flag = semaphore.tryAcquire();
+        }
+        catch (Exception e)
+        {
+            LOGGER.error("获取信号量异常", e);
+        }
+
+        return flag;
+    }
+
+    /**
+     * 释放信号量
+     * 
+     * @param semaphore
+     */
+    public static void release(Semaphore semaphore)
+    {
+
+        try
+        {
+            semaphore.release();
+        }
+        catch (Exception e)
+        {
+            LOGGER.error("释放信号量异常", e);
+        }
+    }
+}

+ 20 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/websocket/WebSocketConfig.java

@@ -0,0 +1,20 @@
+package com.ruoyi.framework.websocket;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+/**
+ * websocket 配置
+ * 
+ * @author ruoyi
+ */
+@Configuration
+public class WebSocketConfig
+{
+    @Bean
+    public ServerEndpointExporter serverEndpointExporter()
+    {
+        return new ServerEndpointExporter();
+    }
+}

+ 103 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/websocket/WebSocketServer.java

@@ -0,0 +1,103 @@
+package com.ruoyi.framework.websocket;
+
+import java.util.concurrent.Semaphore;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import javax.websocket.OnOpen;
+import javax.websocket.Session;
+import javax.websocket.server.ServerEndpoint;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+/**
+ * websocket 消息处理
+ * 
+ * @author ruoyi
+ */
+@Component
+@ServerEndpoint("/websocket/message")
+public class WebSocketServer
+{
+    /**
+     * WebSocketServer 日志控制器
+     */
+    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class);
+
+    /**
+     * 默认最多允许同时在线人数100
+     */
+    public static int socketMaxOnlineCount = 100;
+
+    private static Semaphore socketSemaphore = new Semaphore(socketMaxOnlineCount);
+
+    /**
+     * 连接建立成功调用的方法
+     */
+    @OnOpen
+    public void onOpen(Session session) throws Exception
+    {
+        boolean semaphoreFlag = false;
+        // 尝试获取信号量
+        semaphoreFlag = SemaphoreUtils.tryAcquire(socketSemaphore);
+        if (!semaphoreFlag)
+        {
+            // 未获取到信号量
+            LOGGER.error("\n 当前在线人数超过限制数- {}", socketMaxOnlineCount);
+            WebSocketUsers.sendMessageToUserByText(session, "当前在线人数超过限制数:" + socketMaxOnlineCount);
+            session.close();
+        }
+        else
+        {
+            // 添加用户
+            WebSocketUsers.put(session.getId(), session);
+            LOGGER.info("\n 建立连接 - {}", session);
+            LOGGER.info("\n 当前人数 - {}", WebSocketUsers.getUsers().size());
+            WebSocketUsers.sendMessageToUserByText(session, "连接成功");
+        }
+    }
+
+    /**
+     * 连接关闭时处理
+     */
+    @OnClose
+    public void onClose(Session session)
+    {
+        LOGGER.info("\n 关闭连接 - {}", session);
+        // 移除用户
+        WebSocketUsers.remove(session.getId());
+        // 获取到信号量则需释放
+        SemaphoreUtils.release(socketSemaphore);
+    }
+
+    /**
+     * 抛出异常时处理
+     */
+    @OnError
+    public void onError(Session session, Throwable exception) throws Exception
+    {
+        if (session.isOpen())
+        {
+            // 关闭连接
+            session.close();
+        }
+        String sessionId = session.getId();
+        LOGGER.info("\n 连接异常 - {}", sessionId);
+        LOGGER.info("\n 异常信息 - {}", exception);
+        // 移出用户
+        WebSocketUsers.remove(sessionId);
+        // 获取到信号量则需释放
+        SemaphoreUtils.release(socketSemaphore);
+    }
+
+    /**
+     * 服务器接收到客户端消息时调用的方法
+     */
+    @OnMessage
+    public void onMessage(String message, Session session)
+    {
+        String msg = message.replace("你", "我").replace("吗", "");
+        WebSocketUsers.sendMessageToUserByText(session, msg);
+    }
+}

+ 140 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/websocket/WebSocketUsers.java

@@ -0,0 +1,140 @@
+package com.ruoyi.framework.websocket;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.websocket.Session;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * websocket 客户端用户集
+ * 
+ * @author ruoyi
+ */
+public class WebSocketUsers
+{
+    /**
+     * WebSocketUsers 日志控制器
+     */
+    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketUsers.class);
+
+    /**
+     * 用户集
+     */
+    private static Map<String, Session> USERS = new ConcurrentHashMap<String, Session>();
+
+    /**
+     * 存储用户
+     *
+     * @param key 唯一键
+     * @param session 用户信息
+     */
+    public static void put(String key, Session session)
+    {
+        USERS.put(key, session);
+    }
+
+    /**
+     * 移除用户
+     *
+     * @param session 用户信息
+     *
+     * @return 移除结果
+     */
+    public static boolean remove(Session session)
+    {
+        String key = null;
+        boolean flag = USERS.containsValue(session);
+        if (flag)
+        {
+            Set<Map.Entry<String, Session>> entries = USERS.entrySet();
+            for (Map.Entry<String, Session> entry : entries)
+            {
+                Session value = entry.getValue();
+                if (value.equals(session))
+                {
+                    key = entry.getKey();
+                    break;
+                }
+            }
+        }
+        else
+        {
+            return true;
+        }
+        return remove(key);
+    }
+
+    /**
+     * 移出用户
+     *
+     * @param key 键
+     */
+    public static boolean remove(String key)
+    {
+        LOGGER.info("\n 正在移出用户 - {}", key);
+        Session remove = USERS.remove(key);
+        if (remove != null)
+        {
+            boolean containsValue = USERS.containsValue(remove);
+            LOGGER.info("\n 移出结果 - {}", containsValue ? "失败" : "成功");
+            return containsValue;
+        }
+        else
+        {
+            return true;
+        }
+    }
+
+    /**
+     * 获取在线用户列表
+     *
+     * @return 返回用户集合
+     */
+    public static Map<String, Session> getUsers()
+    {
+        return USERS;
+    }
+
+    /**
+     * 群发消息文本消息
+     *
+     * @param message 消息内容
+     */
+    public static void sendMessageToUsersByText(String message)
+    {
+        Collection<Session> values = USERS.values();
+        for (Session value : values)
+        {
+            sendMessageToUserByText(value, message);
+        }
+    }
+
+    /**
+     * 发送文本消息
+     *
+     * @param userName 自己的用户名
+     * @param message 消息内容
+     */
+    public static void sendMessageToUserByText(Session session, String message)
+    {
+        if (session != null)
+        {
+            try
+            {
+                session.getBasicRemote().sendText(message);
+            }
+            catch (IOException e)
+            {
+                LOGGER.error("\n[发送消息异常]", e);
+            }
+        }
+        else
+        {
+            LOGGER.info("\n[你已离线]");
+        }
+    }
+}