• ruoyi登录功能源码分析


    Ruoyi登录功能源码分析

    上一篇文章我们分析了一下若依登录验证码生成的代码,今天我们来分析一下登录功能的代码

    1、发送登录请求

    前端通过http://localhost/dev-api/login向后端发送登录请求并携带用户的登录表单

    在后端中的com.ruoyi.web.controller.system包下的SysLoginController响应登录信息,后端接受到请求后首先将操作信息设置为成功,然后对用户的登录表单进行校验,校验通过返回token

    在这里插入图片描述

    2、登录校验

    接下来我们来看一看登录校验的代码

       /**
         * 校验验证码
         * 
         * @param username 用户名
         * @param code 验证码
         * @param uuid 唯一标识
         * @return 结果
         */
        public void validateCaptcha(String username, String code, String uuid)
        {
            // 判断当前是否需要生成验证码
            boolean captchaEnabled = configService.selectCaptchaEnabled();
            if (captchaEnabled)
            {
                // 根据生成验证码时返回给前端的uuid拼接验证码在redis中的key值
                String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
                // 从redis中获取验证码
                String captcha = redisCache.getCacheObject(verifyKey);
                // 删除验证码
                redisCache.deleteObject(verifyKey);
                if (captcha == null)
                {
                    // 开启异步任务记录用户信息
                    AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
                    throw new CaptchaExpireException();
                }
                if (!code.equalsIgnoreCase(captcha))
                {
                    AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
                    throw new CaptchaException();
                }
            }
        }
    

    2.1 前置校验

    后端在进行登录校验时首先会对登录验证码进行校验然后会对用户名和密码进行前置校验,验证码校验上一篇文章中已经说过所以这一章不再赘述

    登录前置校验会调用loginPreCheck函数

            // 验证码校验
            validateCaptcha(username, code, uuid);
            // 登录前置校验
            loginPreCheck(username, password);
    
        /**
         * 校验验证码
         * 
         * @param username 用户名
         * @param code 验证码
         * @param uuid 唯一标识
         * @return 结果
         */
        public void validateCaptcha(String username, String code, String uuid)
        {
            // 判断当前是否需要生成验证码
            boolean captchaEnabled = configService.selectCaptchaEnabled();
            if (captchaEnabled)
            {
                // 根据生成验证码时返回给前端的uuid拼接验证码在redis中的key值
                String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
                // 从redis中获取验证码
                String captcha = redisCache.getCacheObject(verifyKey);
                // 删除验证码
                redisCache.deleteObject(verifyKey);
                if (captcha == null)
                {
                    // 开启异步任务记录用户信息
                    AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
                    throw new CaptchaExpireException();
                }
                if (!code.equalsIgnoreCase(captcha))
                {
                    AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
                    throw new CaptchaException();
                }
            }
        }
    

    2.2、登录认证

    接下来是生成token,首先定义一个UsernamePasswordAuthenticationToken对象用来存放用户登录提供的认证凭证表示一个为认证的请求,然后是将用户的提供的登录认证凭证放到线程池的上下文中,调用authenticationManager的authenticate方法进行认证,最后记录登录信息并返回token

        /**
         * 登录验证
         * 
         * @param username 用户名
         * @param password 密码
         * @param code 验证码
         * @param uuid 唯一标识
         * @return 结果
         */
        public String login(String username, String password, String code, String uuid)
        {
            // 验证码校验
            validateCaptcha(username, code, uuid);
            // 登录前置校验
            loginPreCheck(username, password);
            // 用户验证
            Authentication authentication = null;
            try
            {
                // 将用户名和密码存放到UsernamePasswordAuthenticationToken中
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
                // 将authenticationToken放到线程池的上下文中
                AuthenticationContextHolder.setContext(authenticationToken);
                // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
                // 对用户信息进行认证
                authentication = authenticationManager.authenticate(authenticationToken);
            }
            catch (Exception e)
            {
                if (e instanceof BadCredentialsException)
                {
                    // 认证失败记录错误信息
                    AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                    throw new UserPasswordNotMatchException();
                }
                else
                {
                    AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                    throw new ServiceException(e.getMessage());
                }
            }
            finally
            {
                // 清除上下文中的信息
                AuthenticationContextHolder.clearContext();
            }
            // 认证成功 记录信息
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
            // 从认证成功的authentication中取出用户信息
            LoginUser loginUser = (LoginUser) authentication.getPrincipal();
            // 记录登录信息
            recordLoginInfo(loginUser.getUserId());
            // 生成token
            return tokenService.createToken(loginUser);
        }
    
    

    通过SpringSecurity的过滤器链调用UserDetailsServiceImpl进行用户名和密码的校验

    /**
     * 用户验证处理
     *
     * @author ruoyi
     */
    @Service
    public class UserDetailsServiceImpl implements UserDetailsService
    {
        private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
    
        @Autowired
        private ISysUserService userService;
        
        @Autowired
        private SysPasswordService passwordService;
    
        @Autowired
        private SysPermissionService permissionService;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
        {
            // 根据用户名查询用户
            SysUser user = userService.selectUserByUserName(username);
            if (StringUtils.isNull(user))
            {
                // 用户为空
                log.info("登录用户:{} 不存在.", username);
                throw new ServiceException(MessageUtils.message("user.not.exists"));
            }
            else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
            {
                log.info("登录用户:{} 已被删除.", username);
                throw new ServiceException(MessageUtils.message("user.password.delete"));
            }
            else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
            {
                log.info("登录用户:{} 已被停用.", username);
                throw new ServiceException(MessageUtils.message("user.blocked"));
            }
    
            // 校验密码
            passwordService.validate(user);
    
            return createLoginUser(user);
        }
    
        public UserDetails createLoginUser(SysUser user)
        {
            return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
        }
    }
    

    2.3 生成token令牌

    生成token

        /**
         * 创建令牌
         *
         * @param loginUser 用户信息
         * @return 令牌
         */
        public String createToken(LoginUser loginUser)
        {
            String token = IdUtils.fastUUID();
            loginUser.setToken(token);
            setUserAgent(loginUser);
            refreshToken(loginUser);
    
            Map<String, Object> claims = new HashMap<>();
            claims.put(Constants.LOGIN_USER_KEY, token);
            return createToken(claims);
        }
    

    设置用户信息

        /**
         * 设置用户代理信息
         *
         * @param loginUser 登录信息
         */
        public void setUserAgent(LoginUser loginUser)
        {
            UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
            String ip = IpUtils.getIpAddr();
            loginUser.setIpaddr(ip);
            loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
            loginUser.setBrowser(userAgent.getBrowser().getName());
            loginUser.setOs(userAgent.getOperatingSystem().getName());
        }
    

    将token缓存到redis并设置过期时间(默认30分钟)

        /**
         * 刷新令牌有效期
         *
         * @param loginUser 登录信息
         */
        public void refreshToken(LoginUser loginUser)
        {
            loginUser.setLoginTime(System.currentTimeMillis());
            loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
            // 根据uuid将loginUser缓存
            String userKey = getTokenKey(loginUser.getToken());
            redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
    
        }
    
    • expireTime * MILLIS_MINUTE);
      // 根据uuid将loginUser缓存
      String userKey = getTokenKey(loginUser.getToken());
      redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);

      }

    
    到就登录成功了并且后端成功返回token
    
  • 相关阅读:
    信奥中的数学:阶乘
    数据结构:赫夫曼编码
    49、C++/友元、常成员函数和常对象、运算符重载学习20240314
    C#关于set()和get()方法的理解及使用
    基于物联网的智能晾衣架研发
    米尔MYD-JX8MPQ yocto 编译流程 (记录)
    智芯传感MEMS压力传感器促进无人机跨入极其广阔的应用市场
    电脑重装系统word从第二页开始有页眉页脚如何设置
    周总结【java项目】
    Python正则表达式
  • 原文地址:https://blog.csdn.net/weixin_52661752/article/details/139689804