登录界面
ruoyi-ui/src/views/login.vue
点击登录按钮进入handleLogin方法
handleLogin() {
//验证数据是否合法
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true;
//如果记住密码被勾选
if (this.loginForm.rememberMe) {
//直接在cookie中存入相关信息,过期时间为30天
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');
}
//调用vuex中的action
this.$store.dispatch("Login", this.loginForm).then(() => {
//登录成功后,直接访问主界面
this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
}).catch(() => {
this.loading = false;
//登录失败,重新生成验证码
if (this.captchaEnabled) {
this.getCode();
}
});
}
});
}
loginForm
data() {
return {
codeUrl: "",
loginForm: {
username: "admin",
password: "admin123",
rememberMe: false,
code: "",
uuid: ""
},
loginRules: {
username: [
{ required: true, trigger: "blur", message: "请输入您的账号" }
],
password: [
{ required: true, trigger: "blur", message: "请输入您的密码" }
],
code: [{ required: true, trigger: "change", message: "请输入验证码" }]
},
loading: false,
// 验证码开关
captchaEnabled: true,
// 注册开关
register: false,
redirect: undefined
};
},
actions中的Login方法
// 登录
Login({ commit }, userInfo) {
//将loginForm的值传递过来做赋值
const username = userInfo.username.trim()
const password = userInfo.password
const code = userInfo.code
const uuid = userInfo.uuid
return new Promise((resolve, reject) => {
//调用具体的login方法,对象传到后端
login(username, password, code, uuid).then(res => {
//用户登录成功后保存用户的登录状态,
let data = res.data
//保存token以及过期时间,调用auth.js中的具体的function给当前cookies存放token以及过期时间的
//把token存入cookies,调用了auth.js中的setToken
setToken(data.access_token)
//触发mutations,把token存入state
commit('SET_TOKEN', data.access_token)
setExpiresIn(data.expires_in)
commit('SET_EXPIRES_IN', data.expires_in)
resolve()
}).catch(error => {
reject(error)
})
})
},
后端登录
src/main/java/com/ruoyi/auth/controller/TokenController.java
private final SysLoginService sysLoginService;
/**
* 登录方法
*/
@PostMapping("login")
public R<Map<String, Object>> login(@Validated @RequestBody LoginBody form) {
// 用户登录
String accessToken = sysLoginService.login(form.getUsername(), form.getPassword());
// 接口返回信息
Map<String, Object> rspMap = new HashMap<>();
rspMap.put(Constants.ACCESS_TOKEN, accessToken);
return R.ok(rspMap);
}
src/main/java/com/ruoyi/auth/service/SysLoginService.java
@DubboReference
private RemoteLogService remoteLogService;
@DubboReference
private RemoteUserService remoteUserService;
@Autowired
private UserPasswordProperties userPasswordProperties;
/**
* 登录
*/
public String login(String username, String password) {
//通过用户名得到用户实体类
LoginUser userInfo = remoteUserService.getUserInfo(username);
//密码校验,登录次数判断
//BCrypt.checkpw验证密码是否正确
checkLogin(LoginType.PASSWORD, username, () -> !BCrypt.checkpw(password, userInfo.getPassword()));
// 获取登录token
LoginHelper.loginByDevice(userInfo, DeviceType.PC);
recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
return StpUtil.getTokenValue();
}
远程调用src/main/java/com/ruoyi/system/api/RemoteUserService.java
/**
* 用户服务
*
* @author Lion Li
*/
public interface RemoteUserService {
/**
* 通过用户名查询用户信息
*
* @param username 用户名
* @return 结果
*/
LoginUser getUserInfo(String username) throws UserException;
/**
* 通过手机号查询用户信息
*
* @param phonenumber 手机号
* @return 结果
*/
LoginUser getUserInfoByPhonenumber(String phonenumber) throws UserException;
/**
* 通过openid查询用户信息
*
* @param openid openid
* @return 结果
*/
XcxLoginUser getUserInfoByOpenid(String openid) throws UserException;
/**
* 注册用户信息
*
* @param sysUser 用户信息
* @return 结果
*/
Boolean registerUserInfo(SysUser sysUser);
/**
* 通过userId查询用户账户
*
* @param userId 用户id
* @return 结果
*/
String selectUserNameById(Long userId);
}
登录类型src/main/java/com/ruoyi/common/core/enums/LoginType.java
/**
* 登录类型
*
* @author Lion Li
*/
@Getter
@AllArgsConstructor
public enum LoginType {
/**
* 密码登录
*/
PASSWORD("user.password.retry.limit.exceed", "user.password.retry.limit.count"),
/**
* 短信登录
*/
SMS("sms.code.retry.limit.exceed", "sms.code.retry.limit.count"),
/**
* 小程序登录
*/
XCX("", "");
/**
* 登录重试超出限制提示
*/
final String retryLimitExceed;
/**
* 登录重试限制计数提示
*/
final String retryLimitCount;
}
src/main/java/com/ruoyi/system/controller/SysUserController.java#add()
user.setPassword(BCrypt.hashpw(user.getPassword()));
BCrypt#hashpw()
public static String hashpw(String password) {
return hashpw(password, gensalt());//随机创建盐
}
登录校验src/main/java/com/ruoyi/auth/service/SysLoginService.java#checkLogin()
/**
* 登录校验
*/
private void checkLogin(LoginType loginType, String username, Supplier<Boolean> supplier) {
String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
String loginFail = Constants.LOGIN_FAIL;
Integer maxRetryCount = userPasswordProperties.getMaxRetryCount();
Integer lockTime = userPasswordProperties.getLockTime();
// 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
Integer errorNumber = RedisUtils.getCacheObject(errorKey);
// 锁定时间内登录 则踢出
if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(maxRetryCount)) {
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
}
//密码不正确
if (supplier.get()) {
// 是否第一次
errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1;
// 达到规定错误次数 则锁定登录
if (errorNumber.equals(maxRetryCount)) {
RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
} else {
// 未达到规定错误次数 则递增
RedisUtils.setCacheObject(errorKey, errorNumber);
recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
throw new UserException(loginType.getRetryLimitCount(), errorNumber);
}
}
// 登录成功 清空错误次数
RedisUtils.deleteObject(errorKey);
}
src/main/java/com/ruoyi/common/satoken/utils/LoginHelper.java#loginByDevice()
/**
* 登录系统 基于 设备类型
* 针对相同用户体系不同设备
*
* @param loginUser 登录用户信息
*/
public static void loginByDevice(LoginUser loginUser, DeviceType deviceType) {
SaStorage storage = SaHolder.getStorage();//当前的请求中
storage.set(LOGIN_USER_KEY, loginUser);
storage.set(USER_KEY, loginUser.getUserId());
SaLoginModel model = new SaLoginModel();
if (ObjectUtil.isNotNull(deviceType)) {
model.setDevice(deviceType.getDevice());
}
//执行登录操作
StpUtil.login(loginUser.getLoginId(), model.setExtra(USER_KEY, loginUser.getUserId()));
//把登录信息存放发哦SaSession中
//由于SaTokenDao的实现类PlusSaTokenDao里面的方法都使用了Redis,用户信息都被存放到了Redis中
StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
}