first commit

This commit is contained in:
2023-07-10 21:13:35 +08:00
parent 373754c2bb
commit 2f9a4cbc8d
12 changed files with 49 additions and 73 deletions

View File

@ -18,6 +18,14 @@
<groupId>com.qiaoba</groupId>
<artifactId>qiaoba-api-auth</artifactId>
</dependency>
<dependency>
<groupId>com.qiaoba</groupId>
<artifactId>qiaoba-api-monitor</artifactId>
</dependency>
<dependency>
<groupId>com.qiaoba</groupId>
<artifactId>qiaoba-api-system</artifactId>
</dependency>
<!-- common-base -->
<dependency>
<groupId>com.qiaoba</groupId>

View File

@ -1,24 +0,0 @@
package com.qiaoba.auth.context;
import cn.hutool.core.convert.Convert;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
/**
* 权限信息
*
* @author ruoyi
*/
public class PermissionContextHolder {
private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT";
public static void setContext(String permission) {
RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission,
RequestAttributes.SCOPE_REQUEST);
}
public static String getContext() {
return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES,
RequestAttributes.SCOPE_REQUEST));
}
}

View File

@ -0,0 +1,64 @@
package com.qiaoba.auth.controller;
import com.qiaoba.api.auth.utils.SecurityUtil;
import com.qiaoba.api.system.entity.dto.LoginDto;
import com.qiaoba.api.system.entity.vo.SysMenuVo;
import com.qiaoba.api.system.service.SysMenuApiService;
import com.qiaoba.auth.service.SysLoginService;
import com.qiaoba.common.base.result.AjaxResult;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Set;
/**
* 登录 Web层
*
* @author ailanyin
* @version 1.0
* @since 2023/5/5 9:43
*/
@RestController
@RequiredArgsConstructor
public class SysLoginController {
private final SysMenuApiService sysMenuApiService;
private final SysLoginService sysLoginService;
@GetMapping("/captchaImage")
public AjaxResult getCaptchaImage() {
AjaxResult result = AjaxResult.success();
result.putAll(sysLoginService.getCaptchaImage());
return result;
}
@PostMapping("/login")
public AjaxResult login(@RequestBody LoginDto dto) {
String token = sysLoginService.login(dto);
AjaxResult ajax = AjaxResult.success();
ajax.put("token", token);
return ajax;
}
@GetMapping("/getInfo")
public AjaxResult getInfo() {
List<String> roles = SecurityUtil.getLoginUser().getRoleKeys();
Set<String> permissions = SecurityUtil.getLoginUser().getPermissions();
AjaxResult ajax = AjaxResult.success();
ajax.put("permissions", permissions);
ajax.put("roles", roles);
ajax.put("user", SecurityUtil.getLoginUser());
return ajax;
}
@GetMapping("/getRouters")
public AjaxResult getRouters() {
List<SysMenuVo> sysMenuVos = sysMenuApiService.selectByUserId(SecurityUtil.getLoginUserId());
return AjaxResult.success(sysMenuApiService.menusToRouters(sysMenuVos));
}
}

View File

@ -12,6 +12,7 @@ import com.qiaoba.common.web.utils.ResponseUtil;
import com.qiaoba.common.web.utils.UriUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
@ -34,6 +35,7 @@ import java.util.Objects;
@RequiredArgsConstructor
@Slf4j
@Order(FilterOrder.ONLINE_USER_FILTER_ORDER)
@Configuration
public class OnlineUserFilter extends OncePerRequestFilter {
private final AuthConfigProperties authConfigProperties;

View File

@ -7,6 +7,7 @@ import com.qiaoba.auth.properties.AuthConfigProperties;
import com.qiaoba.common.base.context.BaseContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
@ -30,6 +31,7 @@ import java.util.Objects;
*/
@RequiredArgsConstructor
@Slf4j
@Configuration
public class SecurityContextHolderFilter extends OncePerRequestFilter {

View File

@ -0,0 +1,31 @@
package com.qiaoba.auth.service;
import com.qiaoba.api.system.entity.dto.LoginDto;
import java.util.Map;
/**
* 登录 服务层
*
* @author ailanyin
* @version 1.0
* @since 2023/5/15 15:31
*/
public interface SysLoginService {
/**
* 生成图片验证码
*
* @return uuid + base64
*/
Map<String, Object> getCaptchaImage();
/**
* 登录
*
* @param dto dto
* @return token
*/
String login(LoginDto dto);
}

View File

@ -0,0 +1,187 @@
package com.qiaoba.auth.service.impl;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.qiaoba.api.auth.constants.SecurityConstant;
import com.qiaoba.api.auth.entity.OnlineUser;
import com.qiaoba.api.auth.service.AuthConfigApiService;
import com.qiaoba.api.auth.service.OnlineUserService;
import com.qiaoba.api.auth.service.SysUserDetailsApiService;
import com.qiaoba.api.auth.utils.SecurityUtil;
import com.qiaoba.api.auth.utils.TokenUtil;
import com.qiaoba.api.monitor.entity.SysLoginLog;
import com.qiaoba.api.monitor.service.SysLoginLogApiService;
import com.qiaoba.api.system.entity.SysUser;
import com.qiaoba.api.system.entity.dto.LoginDto;
import com.qiaoba.api.system.service.SysUserApiService;
import com.qiaoba.auth.service.SysLoginService;
import com.qiaoba.common.base.constants.BaseConstant;
import com.qiaoba.common.base.constants.ConfigConstant;
import com.qiaoba.common.base.enums.BaseEnum;
import com.qiaoba.common.base.exceptions.ServiceException;
import com.qiaoba.common.redis.service.RedisService;
import com.qiaoba.common.web.utils.IpUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 登录 服务层实现
*
* @author ailanyin
* @version 1.0
* @since 2023/5/15 15:31
*/
@Service
@RequiredArgsConstructor
public class SysLoginServiceImpl implements SysLoginService {
private final RedisService redisService;
private final HttpServletRequest request;
private final SysUserDetailsApiService userDetailsService;
private final SysUserApiService sysUserApiService;
private final OnlineUserService onlineUserService;
private final AuthConfigApiService authConfigApiService;
private final SysLoginLogApiService sysLoginLogApiService;
@Override
public Map<String, Object> getCaptchaImage() {
Map<String, Object> map = new HashMap<String, Object>(4);
map.put("register", authConfigApiService.getRegisterConfig());
if (!authConfigApiService.getCaptchaConfig()) {
map.put("captchaEnabled", false);
return map;
}
String uuid = UUID.randomUUID().toString(true);
LineCaptcha captcha = CaptchaUtil.createLineCaptcha(150, 50, 4, 20);
map.put("uuid", uuid);
map.put("img", captcha.getImageBase64());
redisService.set(SecurityConstant.CAPTCHA_KEY + uuid, captcha.getCode(), 120);
return map;
}
@Override
public String login(LoginDto dto) {
Boolean blacklistSwitch = authConfigApiService.getBlacklistConfig();
// 校验黑名单
validateBlacklist(blacklistSwitch);
// 校验验证码
authConfigApiService.validateCaptcha(dto.getCode(), dto.getUuid());
// username查询用户信息
SysUser sysUser = sysUserApiService.selectByUsername(dto.getUsername());
// 检查账号信息
validateUser(dto.getUsername(), sysUser);
// 检验密码
validatePassword(blacklistSwitch, dto.getUsername(), sysUser.getPassword(), dto.getPassword());
// 缓存在线用户
OnlineUser onlineUser = cacheOnlineUser(dto.getUsername(), sysUser.getNickname());
// 缓存userDetails
userDetailsService.toCache(sysUser.getUsername(), onlineUser.getDeviceSn());
// 新增登录日志
addLoginLog(onlineUser);
// 生成Token
return TokenUtil.generateToken(sysUser.getUsername(), onlineUser.getDeviceSn());
}
private void validateBlacklist(Boolean blacklistSwitch) {
String ip = IpUtil.getIp(request);
if (blacklistSwitch && redisService.hasKey(SecurityConstant.BLACKLIST_KEY + ip)) {
throw new ServiceException(SecurityConstant.HAS_BEEN_PULLED_BLACK);
}
}
private void validatePassword(Boolean blacklistSwitch, String username, String password, String inputPassword) {
boolean result = SecurityUtil.matchesPassword(inputPassword, password);
// 密码正确
if (result) {
if (blacklistSwitch) {
// 删除错误次数
String ip = IpUtil.getIp(request);
redisService.del(SecurityConstant.LOGIN_ERROR_COUNT + ip);
}
} else {
// 密码错误
String msg = beforePasswordError(blacklistSwitch, username);
throw new ServiceException(msg);
}
}
private String beforePasswordError(Boolean blacklistSwitch, String username) {
String ip = IpUtil.getIp(request);
// 未开启->直接结束
if (!blacklistSwitch) {
return "密码错误";
}
// 开启->继续
// 错误次数是否到达允许最大错误次数
Integer maxAllowCount = authConfigApiService.getAllowMaxErrorCount();
Integer ipErrorCount = getIpErrorCount(ip);
if (ipErrorCount >= maxAllowCount) {
// 是-> 进入黑名单库 && 返回"IP已被拉黑"
redisService.set(SecurityConstant.BLACKLIST_KEY + ip, username, 60 * authConfigApiService.getBlacklistExpireTime());
redisService.del(SecurityConstant.LOGIN_ERROR_COUNT + ip);
return SecurityConstant.HAS_BEEN_PULLED_BLACK;
} else {
// 否-> 错误次数+1 && 返回"你还剩xx次错误机会"
ipErrorCount++;
redisService.set(SecurityConstant.LOGIN_ERROR_COUNT + ip, ipErrorCount, 600);
return StrUtil.format("密码错误, 还有[{}]次错误机会", (maxAllowCount - ipErrorCount));
}
}
private Integer getIpErrorCount(String ip) {
Object ipErrorCount = redisService.get(SecurityConstant.LOGIN_ERROR_COUNT + ip);
return Objects.isNull(ipErrorCount) ? 0 : Integer.parseInt(ipErrorCount.toString());
}
private void validateUser(String username, SysUser user) {
if (ObjectUtil.isNull(user)) {
throw new ServiceException(StrUtil.format("登录用户:{} 不存在", username));
} else if (BaseEnum.ABNORMAL.getCode().equals(user.getStatus())) {
throw new ServiceException(StrUtil.format("对不起, 您的账号:{} 已被禁用", username));
}
}
private OnlineUser cacheOnlineUser(String username, String nickname) {
String deviceSn = UUID.fastUUID().toString(true);
String ip = IpUtil.getIp(request);
String address = IpUtil.getIpAddr(ip);
UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
String browser = userAgent.getBrowser().getName() + BaseConstant.LINE_JOIN_STR + userAgent.getVersion();
String os = userAgent.getOs().getName();
OnlineUser onlineUser = new OnlineUser(deviceSn, username, nickname, ip, address, browser, os, new Date());
onlineUserService.insert(onlineUser);
redisService.set(SecurityConstant.LAST_LOGIN_USER_REDIS_KEY + username, deviceSn, TokenUtil.TOKEN_EXPIRE_HOUR_TIME * 3600);
// 允许同时在线
if (authConfigApiService.checkAllowBothOnline()) {
redisService.hSet(SecurityConstant.LOGIN_USER_DEVICES_REDIS_KEY + username, deviceSn, 1, TokenUtil.TOKEN_EXPIRE_HOUR_TIME * 3600);
}
return onlineUser;
}
private void addLoginLog(OnlineUser onlineUser) {
// 查询系统配置 - 是否开启登录日志
String onOff = redisService.getObject(ConfigConstant.LOGIN_LOG_ON_OFF_KEY, String.class);
if (ConfigConstant.COMMON_ON_VALUE.equals(onOff)) {
SysLoginLog sysLoginLog = BeanUtil.copyProperties(onlineUser, SysLoginLog.class);
sysLoginLogApiService.insert(sysLoginLog);
}
}
}

View File

@ -1,11 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.qiaoba.auth.properties.AuthConfigProperties,\
com.qiaoba.auth.handler.AccessDeniedHandler,\
com.qiaoba.auth.handler.LogoutHandler,\
com.qiaoba.auth.filters.OnlineUserFilter,\
com.qiaoba.auth.filters.SecurityContextHolderFilter,\
com.qiaoba.auth.advice.SecurityExceptionAdvice,\
com.qiaoba.auth.aspectj.DataScopeAspect,\
com.qiaoba.auth.service.impl.OnlineUserServiceImpl,\
com.qiaoba.auth.service.impl.AuthConfigServiceImpl,\
com.qiaoba.auth.config.SpringSecurityConfig