若依框架-RBAC权限控制-用户登录流程

目录

前端

表单校验

登录处理

表单验证

处理“记住密码”功能 (Cookies)

发送登录请求

整体流程概述

登录逻辑的核心功能

请求转发到后端

调用dispatch

Login action

请求转发

后端

业务逻辑

loginService的login方法


前端

前端对应文件在

ruoyi-ui/src/views/login.vue

表单校验

由于登录表单使用了:rules,所以用户在输入时,前端会进行校验:

具体校验规则如下:

loginRules: 包含表单验证规则,用于确保用户输入的账号、密码和验证码是有效的。上图的loginRules的内容的具体含义是:

username 字段的验证规则:

  • required: true: 表示密码字段是必填项。
  • trigger: "blur": 同样是当输入框失去焦点时触发验证。
  • message: "请输入您的密码": 验证失败时,显示的提示信息是 "请输入您的密码"

password 字段的验证规则:

  • required: true: 表示密码字段是必填项。
  • trigger: "blur": 同样是当输入框失去焦点时触发验证。
  • message: "请输入您的密码": 验证失败时,显示的提示信息是 "请输入您的密码"

code 字段的验证规则:

  • required: true: 表示验证码是必填项,用户必须输入验证码。
  • trigger: "change": 这里验证的触发事件是 change,即当输入框的内容发生变化时(即用户输入或修改验证码时)触发验证。
  • message: "请输入验证码": 验证失败时显示的提示信息是 "请输入验证码"

使用 trigger: "change" 可以在用户每次输入或修改验证码时进行检查,这样可以确保:

用户每次修改输入的验证码时,系统能及时检查验证码是否符合要求(例如长度、格式等),即使用户只是稍微修改了验证码,也能立刻得到反馈。

登录处理

当用户通过了前端的表单校验后,就可以点击登录按钮:

程序将执行下面的方法:

表单验证

this.$refs.loginForm.validate(valid => {
  if (valid) {
    // 进入登录逻辑
  }
});
  • this.$refs.loginForm.validate(valid => { ... }) 调用 Element UI 提供的表单验证方法,进行表单字段的验证。
  • valid 是一个布尔值,表示表单验证是否通过。如果通过(即 validtrue),则继续执行后续的登录逻辑,否则不执行。

处理“记住密码”功能 (Cookies)

if (this.loginForm.rememberMe) {
  Cookies.set("username", this.loginForm.username, { expires: 30 });
  Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
  Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
} else {
  Cookies.remove("username");
  Cookies.remove("password");
  Cookies.remove('rememberMe');
}
  • rememberMe: 这是一个用户勾选的复选框,表示是否“记住密码”。
  • 如果用户勾选了“记住密码”,会使用 Cookies.set() 将用户名、加密后的密码和 rememberMe 状态保存到浏览器的 cookies 中,并设置过期时间为 30 天。
  • 如果用户未勾选“记住密码”,则删除 cookies 中保存的用户名、密码和 rememberMe 信息(使用 Cookies.remove())。

发送登录请求

this.$store.dispatch("Login", this.loginForm).then(() => {
  this.$router.push({ path: this.redirect || "/" }).catch(() => {});
}).catch(() => {
  this.loading = false;
  if (this.captchaEnabled) {
    this.getCode();
  }
});
  • this.$store.dispatch("Login", this.loginForm): 调用 Vuex 的 dispatch 方法,触发 Login 动作来执行实际的登录操作。登录数据(如用户名、密码)通过 this.loginForm 传递到后端。
  • then() 中的回调函数:
    • this.$router.push({ path: this.redirect || "/" }): 登录成功后,路由会跳转到用户之前尝试访问的页面(通过 this.redirect 获取),如果没有指定,则跳转到根路径 /
  • catch() 中的回调函数:
    • this.loading = false: 登录失败时,停止加载状态(loading)。
    • if (this.captchaEnabled) { this.getCode(); }: 如果启用了验证码(this.captchaEnabled),则重新获取验证码。

如果不了解什么是vuex的dispatch方法,可以看这篇博客:vuex核心概念-actions_vuex mapactions-CSDN博客

整体流程概述

  1. 用户点击登录按钮,触发 handleLogin() 方法。
  2. 方法首先验证表单是否填写完整且合法。如果验证失败,则不会执行登录逻辑。
  3. 如果表单验证通过,根据是否勾选“记住密码”来保存或删除 cookies。
  4. 调用 Vuex 中的 Login 方法,发送登录请求。
  5. 登录成功后,页面跳转到指定的路径。
  6. 登录失败时,恢复加载状态,并(如果启用)重新获取验证码。

登录逻辑的核心功能

  • 验证和反馈:表单验证确保用户输入的用户名、密码等字段是有效的。
  • 记住我功能:利用 cookies 存储用户的登录信息,并在下次访问时自动填充表单。
  • 动态验证码处理:当登录失败时,如果验证码开启,会重新加载验证码,增强登录的安全性。

请求转发到后端

调用dispatch

前端在通过表单验证后,会调用Vuex的dispatch方法:

"Login" 是在 Vuex store 中定义的 action 名称。在组件中调用 dispatch("Login", ...) 会执行这个 Login action。

Login action

这个Login action的具体位置如下图:

  • Login 是在 Vuex store 中定义的一个 action
  • 它的第一个参数 { commit } 是 Vuex 自动传递给 action 的对象。commit 用于提交 mutation,修改 state。
  • userInfo 是传递给 action 的第二个参数,它包含了用户登录所需要的信息,如用户名、密码、验证码、uuid 等。这里的userInfo对应的就是this.$store.dispatch("Login", this.loginForm)中的this.loginForm。
  • userInfo 中提取出用户名、密码、验证码和 uuid
  • 调用 login 函数发送异步请求,这个请求可能是一个 API 调用,用于验证用户名、密码和验证码。
    • login(username, password, code, uuid) 返回一个 Promise(假设它是一个异步的 API 调用)。
    • 如果请求成功,得到的 res(响应)包含一个 token,该 token 被用于身份验证。
  • 使用 setToken(res.token)token 保存到本地(存储在浏览器的cookies 中)。
  • 使用 commit('SET_TOKEN', res.token) 提交一个 mutation,更新 Vuex 的状态,保存 token
  • 最后,调用 resolve() 来表示 Promise 已成功完成。

请求转发

在login action中,调用了login:

这实际上调用的是名为login的api,用于将请求转发给后端,具体内容在:

函数定义

export function login(username, password, code, uuid) {
  const data = {
    username,
    password,
    code,
    uuid
  }
  • 该函数接受四个参数:username(用户名),password(密码),code(验证码),uuid(用户会话的唯一标识符)。
  • 这些参数随后被封装成一个对象 data,用来组织发送给服务器的登录数据。
    • username:用户输入的用户名。
    • password:用户输入的密码。
    • code:用户输入的验证码(如果启用了验证码)。
    • uuid:验证码的唯一标识符(通常是用来识别当前会话,防止攻击)。

发送请求

  return request({
    url: '/login',
    headers: {
      isToken: false,
      repeatSubmit: false
    },
    method: 'post',
    data: data
  })
}
  • requestrequest 是一个封装过的 HTTP 请求方法,是基于 axios 来发送请求。它接受一个配置对象,该对象定义了请求的 URL、请求头、请求方法和请求数据等内容。

  • url: '/login':请求的目标地址是 /login,这意味着该请求会发送到服务器的登录接口,通常是一个处理用户登录的 API。

  • headers

    • isToken: false:这个标头 isToken: false 表示该请求不需要携带身份验证的 Token,通常在登录请求中第一次提交时,不需要包含 Token。
    • repeatSubmit: false:这个标头 repeatSubmit: false 是为了防止重复提交登录请求。可以防止用户快速连续点击登录按钮,导致多次提交相同的请求。
  • method: 'post':请求的方法是 POST,表示这是一个数据提交请求,通常用于提交用户的登录信息。

  • data: data:这里传递的数据是之前封装的 data 对象,其中包含了 usernamepasswordcodeuuid,即用户的登录信息。


请求返回

  return request({ ... })
  • login 函数返回的是 request 方法的调用结果。request 返回一个 Promise,代表异步操作的结果。通常情况下,request 函数会发送 HTTP 请求并在成功后解析返回的响应数据,或者在请求失败时抛出错误。
  • 由于 request 返回的是一个 Promise,你可以在调用 login 函数时使用 .then()  来处理响应数据或错误。

总结

这段代码实现了一个发送用户登录请求的功能。函数通过将用户输入的数据(用户名、密码、验证码、UUID)封装到请求体中,使用 POST 方法发送给服务器的 /login 接口进行验证。

主要步骤:

  1. 封装数据:将 usernamepasswordcodeuuid 组成一个 data 对象。
  2. 发送请求:使用 request 方法发起一个 POST 请求,提交封装的数据到 /login 接口。
  3. 处理请求头:通过自定义请求头来控制请求的行为,如禁用 Token 和防止重复提交。
  4. 返回 Promise:返回一个 Promise,允许外部调用该函数时处理请求的结果或错误。

后端

业务逻辑

后端对应代码如图所示:

AjaxResult ajax = AjaxResult.success();

这里创建了一个 AjaxResult 对象 ajax,并调用 AjaxResult.success() 方法。AjaxResult.success() 返回一个表示请求成功的标准化响应对象,通常会包含一个 code 字段和 msg 字段,用来表示响应状态(如成功与否),以及返回的数据(如果有的话)。


String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid());

该行调用了 loginServicelogin 方法进行业务处理,进行用户登录验证。

  • loginService 是一个服务类,负责处理实际的登录逻辑。
  • loginBody.getUsername()loginBody.getPassword()loginBody.getCode()loginBody.getUuid()loginBody 对象中获取用户输入的用户名、密码、验证码和 uuid
  • loginService.login() 方法返回一个 令牌(token),这个令牌是一个 JWT的身份认证 token,用于标识已认证的用户。

ajax.put(Constants.TOKEN, token);

ajax.put(Constants.TOKEN, token):将生成的 token 存储到 AjaxResult 的返回数据中。Constants.TOKEN 是一个常量,通常表示 "token" 这个字段的键,用于在响应中标识登录令牌。

  • 这样,客户端在收到响应时,可以从 AjaxResult 中获取 token,并将其用于后续的认证请求。

总结

这段代码实现了一个典型的用户登录接口,客户端向服务器发送登录请求,服务器验证用户信息并生成一个登录令牌(token),然后将其返回给客户端。客户端接收到令牌后可以在后续的请求中使用该令牌来验证用户身份。

loginService的login方法

我们来分析一下loginService中的login方法(下面是我从若依框架中截取的代码):

public String login(String username, String password, String code, String uuid)
    {
        // 验证码校验
        validateCaptcha(username, code, uuid);
        // 登录前置校验
        loginPreCheck(username, password);
        // 用户验证
        Authentication authentication = null;
        try
        {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
            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")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        recordLoginInfo(loginUser.getUserId());
        // 生成token
        return tokenService.createToken(loginUser);
    }

下面是具体的分析:

方法定义

public String login(String username, String password, String code, String uuid)
  • login 方法接收四个参数:
    • username:用户的用户名
    • password:用户的密码
    • code:验证码
    • uuid:验证码的唯一标识符
  • 返回值类型是 String,返回登录成功后生成的 token,这是一个 JWT 的身份验证令牌。

验证码校验

validateCaptcha(username, code, uuid);
  • 调用 validateCaptcha 方法进行验证码的校验,通常是用来防止暴力破解等攻击。
  • usernamecodeuuid 参数用于验证用户提交的验证码是否有效。
    • username:用户名,用于关联验证码。
    • code:用户输入的验证码。
    • uuid:验证码的唯一标识符,通常是生成验证码时提供的标识,防止不同用户之间混淆验证码。

登录前置校验

loginPreCheck(username, password);

调用 loginPreCheck 方法,执行一些登录前的校验逻辑。这个方法可能会校验以下内容:

  • 用户名是否存在。
  • 用户账户是否被锁定、禁用等。
  • 密码的格式是否符合要求等。

身份验证

Authentication authentication = null;
try
{
    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
    AuthenticationContextHolder.setContext(authenticationToken);
    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();
}
  • UsernamePasswordAuthenticationToken
    • 创建一个 UsernamePasswordAuthenticationToken 对象,表示用户名和密码的认证请求。此对象封装了用户的用户名和密码,用于后续的认证处理。
  • AuthenticationContextHolder.setContext(authenticationToken)
    • 将认证请求存入 AuthenticationContextHolder,以便后续可以使用上下文中的认证信息。AuthenticationContextHolder 是一个用于管理当前认证信息的上下文容器。
  • authenticationManager.authenticate(authenticationToken)
    • 调用 authenticationManager 对象的 authenticate 方法进行身份验证。这个方法会委托给具体的 UserDetailsService(通常是自定义的 UserDetailsServiceImpl)来加载用户详情,验证用户名和密码是否匹配。
  • 异常处理
    • BadCredentialsException:如果密码不正确,捕获此异常并记录登录失败信息。然后抛出自定义异常 UserPasswordNotMatchException,表示密码错误。
    • 其他异常:如果出现其他异常,记录登录失败信息并抛出 ServiceException,表示服务器端的错误。
  • AuthenticationContextHolder.clearContext()
    • 最终清除认证上下文,确保每次认证操作完成后都能清理上下文信息,避免造成信息泄露。

记录登录成功信息

AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
  • 调用 AsyncManager 异步执行记录登录信息的操作。这里会记录用户的登录成功信息,如用户名、登录时间等。
  • AsyncFactory.recordLogininfor 是一个工厂方法,用来创建记录日志的操作。

获取登录用户信息

LoginUser loginUser = (LoginUser) authentication.getPrincipal();
  • authentication.getPrincipal() 获取认证的用户信息,通常返回一个实现了 UserDetails 接口的对象,包含了用户的详细信息。
  • 这里将其强制转换为 LoginUser 类型(LoginUser 是自定义的用户类,包含用户ID、角色等信息)。

记录登录信息

recordLoginInfo(loginUser.getUserId());

记录用户的登录信息,例如登录时间、IP 地址等,存储到数据库或日志系统中。


生成并返回 Token

return tokenService.createToken(loginUser);
  • 调用 tokenService.createToken(loginUser) 方法生成一个令牌(JWT)。loginUser 是通过身份验证获得的用户信息。
  • 该方法会根据 loginUser 的信息生成一个 token,通常用于后续请求的身份验证。
  • 返回的 token 是一个 JWT,它会包含用户的标识、过期时间、角色等信息,用于后续的认证和授权。

总结

  • 验证码校验:首先检查用户提供的验证码是否正确。
  • 登录前置校验:进行一些前置的验证,如检查用户名和密码格式、账户状态等。
  • 身份验证:通过 UsernamePasswordAuthenticationToken 创建认证请求,并使用 authenticationManager 进行用户身份验证。
  • 异常处理:处理身份验证过程中可能出现的异常(如密码错误)。
  • 记录登录信息:通过 AsyncManager 异步记录登录信息(成功或失败)。
  • 生成 Token:成功认证后,生成一个令牌,并返回给客户端。
### 若依系统的RBAC模型概述 若依系统采用的是增强版的RBAC(Role-Based Access Control)权限管理模式,该模式不仅涵盖了基本的角色分配功能,还加入了额外的安全性和灵活性特性[^1]。通过这种方式,可以更好地满足现代企业对于复杂业务场景下的细粒度访问控制需求。 ### 数据权限管理实现方式 针对不同部门或岗位之间的敏感信息隔离需求,在若依系统中可以通过定义特定的数据范围来实施数据级别的权限控制- **按机构/部门划分**:允许管理员设定某一角色仅能查看所属单位内部的信息记录; - **自定义SQL过滤器**:利用数据库查询语句中的`WHERE`子句动态构建条件表达式,从而精确限定可操作的数据集; 这种做法有效防止了越权行为的发生,同时也简化了跨表关联时复杂的逻辑判断过程[^2]。 ```sql SELECT * FROM user_info WHERE dept_id IN (SELECT id FROM department WHERE parent_ids LIKE '%,001,%') ``` 上述例子展示了如何获取指定父节点下所有子节点对应用户的个人信息列表。 ### 字段级别权限设置策略 除了整体资源层面的读写限制外,有时还需要更精细地规定哪些用户可以在某些实体对象上修改具体属性值。为此,若依平台提供了如下解决方案: - **元数据驱动的设计理念**:通过对各业务表格内字段附加标签的方式标记其可见性及编辑状态; - **前端展示层面上的应用**:借助Vue框架组件化的优势,配合Element UI库快速搭建响应式的输入界面,并依据当前登录者的身份自动调整控件样式与交互能力; 例如,在员工档案维护页面中隐藏薪酬福利部分给非人力资源专员人员不可见[^3]。 ### 配置实例说明 假设现在要新增一个名为“项目负责人”的新职位类别,并为其单独授予对某几个关键项目的预算审批权力而不影响其他同事的工作流程,则按照以下步骤执行即可完成相应配置: #### 步骤一:创建并初始化基础要素 1. 新建一条记录至`t_role`表代表此职务名称及其描述文字; 2. 同步更新到关联映射关系集合里边去,比如将它绑定给某个具体的部门或者团队组织单元; #### 步骤二:定制专属规则集 1. 编辑对应的菜单项链接地址参数传递规则,确保点击进入后的视图加载过程中会带上必要的筛选参数; 2. 设定好相应的服务端接口API返回结果集中关于该项目成本估算数值列的显示与否标志位; #### 步骤三:测试验证效果准确性 最后务必进行全面的功能回归检验工作,确认预期的行为表现无误之后再正式上线投入使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值