ruoyi登录功能源码分析

Ruoyi登录功能源码分析

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

1、发送登录请求

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

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

2、登录校验

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

java 复制代码
   /**
     * 校验验证码
     * 
     * @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函数

java 复制代码
        // 验证码校验
        validateCaptcha(username, code, uuid);
        // 登录前置校验
        loginPreCheck(username, password);
java 复制代码
    /**
     * 校验验证码
     * 
     * @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

java 复制代码
    /**
     * 登录验证
     * 
     * @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进行用户名和密码的校验

java 复制代码
/**
 * 用户验证处理
 *
 * @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

java 复制代码
    /**
     * 创建令牌
     *
     * @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);
    }

设置用户信息

java 复制代码
    /**
     * 设置用户代理信息
     *
     * @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分钟)

java 复制代码
    /**
     * 刷新令牌有效期
     *
     * @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

相关推荐
九皇叔叔36 分钟前
Java循环结构全解析:从基础用法到性能优化
java·开发语言·性能优化
流星52112244 分钟前
GC 如何判断对象该回收?从可达性分析到回收时机的关键逻辑
java·jvm·笔记·学习·算法
csdn_aspnet44 分钟前
Java 圆台体积和表面积计算程序(Program for Volume and Surface area of Frustum of Cone)
java
杯莫停丶1 小时前
设计模式之:外观模式
java·设计模式·外观模式
乐之者v1 小时前
Mac常用软件
java·1024程序员节
TDengine (老段)1 小时前
TDengine 数据函数 ROUND 用户手册
java·大数据·数据库·物联网·时序数据库·tdengine·1024程序员节
TDengine (老段)1 小时前
TDengine 数学函数 RAND 用户手册
java·大数据·数据库·物联网·时序数据库·tdengine·涛思数据
從南走到北1 小时前
JAVA无人自助共享系统台球室源码自助开台约球交友系统源码小程序
java·微信·微信小程序·小程序·1024程序员节
JH30731 小时前
jvm,tomcat,spring的bean容器,三者的关系
jvm·spring·tomcat
野犬寒鸦1 小时前
从零起步学习MySQL || 第十章:深入了解B+树及B+树的性能优势(结合底层数据结构与数据库设计深度解析)
java·数据库·后端·mysql·1024程序员节