first commit
This commit is contained in:
@ -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>
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
Reference in New Issue
Block a user