This commit is contained in:
2023-07-13 17:30:09 +08:00
parent 672f66b40e
commit 6ed1224410
373 changed files with 566 additions and 409 deletions

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>qiaoba-api</artifactId>
<groupId>com.qiaoba</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>qiaoba-api-auth</artifactId>
<dependencies>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.qiaoba</groupId>
<artifactId>qiaoba-common-web</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,28 @@
package com.qiaoba.api.auth.annotation;
import java.lang.annotation.*;
/**
* 数据权限过滤注解
*
* @author ruoyi
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope {
/**
* 部门表的别名
*/
public String deptAlias() default "";
/**
* 用户表的别名
*/
public String userAlias() default "";
/**
* 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取多个权限用逗号分隔开来
*/
public String permission() default "";
}

View File

@ -0,0 +1,44 @@
package com.qiaoba.api.auth.constants;
/**
* 安全常量
*
* @author ailanyin
* @version 1.0
* @since 2022/9/8 0008 下午 14:54
*/
public class SecurityConstant {
public static final String LOGOUT_URI = "/logout";
public static final String HAS_BEEN_PULLED_BLACK = "你的IP已经被系统拉黑, 请联系管理员处理";
public static final String ACCESS_DENIED = "暂无权限访问, 请重新登录";
public static final String BLACKLIST_KEY = "login:blacklist:";
public static final String LOGIN_ERROR_COUNT = "login:errorCount:";
public static final String BLACKLIST_ON = "true";
public static final String CAPTCHA_KEY = "login:captcha:";
public static final String REDIS_SECRET_KEY = "sys:secret:secret";
public static final String USER_DETAILS_REDIS_KEY = "user_details:";
public static final String ONLINE_USER_REDIS_KEY = "online_user:";
public static final String LAST_LOGIN_USER_REDIS_KEY = "last_login_user:";
public static final String LOGIN_USER_DEVICES_REDIS_KEY = "login_user_devices:";
/**
* 密码错误
*/
public static final String PASSWORD_ERROR = "密码错误";
/**
* token header
*/
public static final String TOKEN_HEADER = "Authorization";
/**
* token前缀
*/
public static final String TOKEN_HEAD = "Bearer ";
}

View File

@ -0,0 +1,183 @@
package com.qiaoba.api.auth.entity;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.qiaoba.api.auth.entity.dto.RoleDto;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 登录用户身份权限
*
* @author ailanyin
* @version 1.0
* @since 2021/10/15 0015 上午 10:05
*/
public class LoginUser implements UserDetails {
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private String userId;
/**
* 部门ID
*/
private String deptId;
/**
* 登录账号
*/
private String username;
/**
* 用户名称
*/
private String nickname;
/**
* 角色列表
*/
private List<String> roleKeys;
/**
* 权限列表
*/
private Set<String> permissions;
private List<RoleDto> roles;
public LoginUser() {
}
public LoginUser(String userId, String deptId, String username, String nickname, List<RoleDto> roles, List<String> roleKeys, Set<String> permissions) {
this.userId = userId;
this.deptId = deptId;
this.username = username;
this.permissions = permissions;
this.nickname = nickname;
this.roleKeys = roleKeys;
this.roles = roles;
}
public List<RoleDto> getRoles() {
return roles;
}
public void setRoles(List<RoleDto> roles) {
this.roles = roles;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getDeptId() {
return deptId;
}
public void setDeptId(String deptId) {
this.deptId = deptId;
}
public List<String> getRoleKeys() {
return roleKeys;
}
public void setRoleKeys(List<String> roleKeys) {
this.roleKeys = roleKeys;
}
public Set<String> getPermissions() {
return permissions;
}
public void setPermissions(Set<String> permissions) {
this.permissions = permissions;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public String getPassword() {
return null;
}
/**
* 账户是否未过期,过期无法验证
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 指定用户是否解锁,锁定的用户无法进行身份验证
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
*
* @return boolean
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 是否可用 ,禁用的用户不能身份验证
*
* @return boolean
*/
@Override
public boolean isEnabled() {
return true;
}
@Override
@JsonIgnore
public Collection<? extends GrantedAuthority> getAuthorities() {
//返回当前用户的权限
return permissions.stream()
.filter(StrUtil::isNotBlank)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,63 @@
package com.qiaoba.api.auth.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* 在线用户
*
* @author ailanyin
* @version 1.0
* @since 2023/5/25 17:05
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OnlineUser implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 设备号 暂用UUID
*/
private String deviceSn;
/**
* 登录账号
*/
private String username;
/**
* 用户名称
*/
private String nickname;
/**
* 登录IP地址
*/
private String ip;
/**
* 登录地点
*/
private String address;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 登录时间
*/
private Date loginTime;
}

View File

@ -0,0 +1,60 @@
package com.qiaoba.api.auth.entity;
import com.qiaoba.api.auth.entity.dto.RoleDto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
/**
* @author ailanyin
* @version 1.0
* @since 2023/5/19 16:55
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class SecurityUser implements Serializable {
/**
* 用户ID
*/
private String userId;
/**
* 部门ID
*/
private String deptId;
/**
* 登录账号
*/
private String username;
/**
* 用户名称
*/
private String nickname;
/**
* 角色列表
*/
private List<RoleDto> roles;
/**
* 角色Key列表
*/
private List<String> roleKeys;
/**
* 权限列表
*/
private Set<String> permissions;
}

View File

@ -0,0 +1,33 @@
package com.qiaoba.api.auth.entity.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 在线用户
*
* @author ailanyin
* @version 1.0
* @since 2023/5/22 17:08
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OnlineUserDto implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 登录账号
*/
private String username;
/**
* 设备号 暂用UUID
*/
private String deviceSn;
}

View File

@ -0,0 +1,23 @@
package com.qiaoba.api.auth.entity.dto;
import lombok.Data;
import java.io.Serializable;
/**
* 角色
*
* @author ailanyin
* @version 1.0
* @since 2023/5/22 17:08
*/
@Data
public class RoleDto implements Serializable {
private static final long serialVersionUID = 1L;
private String roleId;
private String roleKey;
private String roleName;
private String dataScope;
}

View File

@ -0,0 +1,62 @@
package com.qiaoba.api.auth.service;
/**
* 安全配置 对外暴露接口
*
* @author ailanyin
* @version 1.0
* @since 2023/5/19 17:17
*/
public interface AuthConfigApiService {
/**
* 是否允许账号同时在线
*
* @return 是/否
*/
Boolean checkAllowBothOnline();
/**
* 是否开启验证码
*
* @return 是/否
*/
Boolean getCaptchaConfig();
/**
* 是否开启注册
*
* @return 是/否
*/
Boolean getRegisterConfig();
/**
* 是否开启黑名单
*
* @return 是/否
*/
Boolean getBlacklistConfig();
/**
* 黑名单过期时间(拉黑时间), 单位:秒
*
* @return 过期时间
*/
Long getBlacklistExpireTime();
/**
* 获取最大允许错误次数
*
* @return 最大允许错误次数
*/
Integer getAllowMaxErrorCount();
/**
* 验证验证码
*
* @param code result
* @param uuid uuid
*/
void validateCaptcha(String code, String uuid);
}

View File

@ -0,0 +1,75 @@
package com.qiaoba.api.auth.service;
import com.qiaoba.api.auth.entity.OnlineUser;
import com.qiaoba.common.base.entity.BasePage;
import java.util.List;
/**
* 在线用户 服务层
*
* @author ailanyin
* @version 1.0
* @since 2023/5/25 17:15
*/
public interface OnlineUserService {
/**
* 新增
*
* @param onlineUser onlineUser
*/
void insert(OnlineUser onlineUser);
/**
* 删除(强退)
*
* @param username 登录账号
* @param deviceSn 设备号
* @param deleteOwn 是否是删除自己
*/
void deleteOne(String username, String deviceSn, Boolean deleteOwn);
/**
* 删除(强退)
*
* @param username 登录账号
*/
void deleteAll(String username);
/**
* 查询
*
* @param username 登录账号
* @param deviceSn deviceSn
* @return 在线用户
*/
OnlineUser selectOne(String username, String deviceSn);
/**
* 批量查询
*
* @param username username
* @return list
*/
List<OnlineUser> selectList(String username);
/**
* 检查设备是否是最新登陆的设备
*
* @param username username
* @param deviceSn deviceSn
* @return 结果
*/
Boolean checkIsLastLogged(String username, String deviceSn);
/**
* 分页查询列表
*
* @param pageNum pageNum
* @param pageSize pageSize
* @param username username
* @return list
*/
BasePage selectPageList(Integer pageNum, Integer pageSize, String username);
}

View File

@ -0,0 +1,23 @@
package com.qiaoba.api.auth.service;
import org.springframework.security.core.userdetails.UserDetails;
/**
* SysUserDetails 对外暴露接口
*
* @author ailanyin
* @version 1.0
* @since 2023/5/19 17:17
*/
public interface SysUserDetailsApiService {
/**
* 查询UserDetails 并缓存到Redis中
*
* @param username username
* @param deviceSn deviceSn
* @return UserDetails
*/
UserDetails toCache(String username, String deviceSn);
}

View File

@ -0,0 +1,74 @@
package com.qiaoba.api.auth.utils;
import com.qiaoba.api.auth.entity.LoginUser;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* SecurityUtil
*
* @author ailanyin
* @version 1.0
* @since 2022/8/16 0016 下午 14:51
*/
public class SecurityUtil {
private static final BCryptPasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder();
/**
* 获取登录用户的账号
*
* @return username
*/
public static String getLoginUsername() {
return getLoginUser().getUsername();
}
/**
* 获取登录用户的账号
*
* @return nickname
*/
public static String getLoginNickname() {
return getLoginUser().getNickname();
}
/**
* 获取登录用户的ID
*
* @return userId
*/
public static String getLoginUserId() {
return getLoginUser().getUserId();
}
public static LoginUser getLoginUser() {
SecurityContext ctx = SecurityContextHolder.getContext();
Authentication auth = ctx.getAuthentication();
return (LoginUser) auth.getPrincipal();
}
/**
* 加密密码
*
* @param password 原密码
* @return 加密后密码
*/
public static String encryptPassword(String password) {
return PASSWORD_ENCODER.encode(password);
}
/**
* 判断密码是否相同
*
* @param rawPassword 真实密码
* @param encodedPassword 加密后字符
* @return 结果
*/
public static boolean matchesPassword(String rawPassword, String encodedPassword) {
return PASSWORD_ENCODER.matches(rawPassword, encodedPassword);
}
}

View File

@ -0,0 +1,72 @@
package com.qiaoba.api.auth.utils;
import cn.hutool.core.util.StrUtil;
import com.qiaoba.api.auth.constants.SecurityConstant;
import com.qiaoba.api.auth.entity.dto.OnlineUserDto;
import com.qiaoba.common.base.constant.BaseConstant;
import com.qiaoba.common.base.exception.ServiceException;
import javax.servlet.http.HttpServletRequest;
/**
* TokenUtil
*
* @author ailanyin
* @version 1.0
* @since 2022/6/8 0008 上午 11:44
*/
public class TokenUtil {
/**
* token 过期时间 单位: 小时
*/
public static final Integer TOKEN_EXPIRE_HOUR_TIME = 3;
private static final String TOKEN_TEMPLATE = "{}:{}";
public static String generateToken(String username, String deviceSn) {
return StrUtil.format(TOKEN_TEMPLATE, username, deviceSn);
}
public static String getToken(HttpServletRequest request, boolean allowNull) {
// 取Header中的Token
String authHeader = request.getHeader(SecurityConstant.TOKEN_HEADER);
if (StrUtil.isNotBlank(authHeader) && authHeader.startsWith(SecurityConstant.TOKEN_HEAD)) {
return authHeader.substring(SecurityConstant.TOKEN_HEAD.length());
}
if (allowNull) {
return null;
}
throw new ServiceException("Token不存在");
}
public static OnlineUserDto getUsernameAndDeviceSn(String token) {
try {
String[] split = token.split(BaseConstant.COLON_JOIN_STR);
return new OnlineUserDto(split[0], split[1]);
} catch (Exception e) {
throw new ServiceException("Token解析失败");
}
}
/**
* 解析 username
*
* @param request request
* @return username
*/
public static String analyzeUsername(HttpServletRequest request) {
// 取Header中的Token
try {
String authHeader = request.getHeader(SecurityConstant.TOKEN_HEADER);
if (StrUtil.isNotBlank(authHeader) && authHeader.startsWith(SecurityConstant.TOKEN_HEAD)) {
String token = authHeader.substring(SecurityConstant.TOKEN_HEAD.length());
return token.split(BaseConstant.COLON_JOIN_STR)[0];
}
return null;
} catch (Exception e) {
return null;
}
}
}