diff --git a/Cpop-Core/pom.xml b/Cpop-Core/pom.xml
index 6205d73..508ff43 100644
--- a/Cpop-Core/pom.xml
+++ b/Cpop-Core/pom.xml
@@ -38,6 +38,11 @@
org.springframework.boot
spring-boot-starter-security
+
+
+ io.jsonwebtoken
+ jjwt
+
org.springframework.boot
@@ -57,6 +62,11 @@
com.alibaba
easyexcel
+
+
+ org.apache.commons
+ commons-compress
+
diff --git a/Cpop-Core/src/main/java/com/cpop/core/aspect/OperationLogAspect.java b/Cpop-Core/src/main/java/com/cpop/core/aspect/OperationLogAspect.java
index 16e726a..aeefd54 100644
--- a/Cpop-Core/src/main/java/com/cpop/core/aspect/OperationLogAspect.java
+++ b/Cpop-Core/src/main/java/com/cpop/core/aspect/OperationLogAspect.java
@@ -1,6 +1,5 @@
package com.cpop.core.aspect;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.cpop.common.constant.HttpStatus;
import com.cpop.common.enums.ErrorCodeEnum;
import com.cpop.core.base.R;
@@ -11,6 +10,7 @@ import com.cpop.core.base.table.SysOperationLog;
import com.cpop.core.event.OperationLogEvent;
import com.cpop.core.utils.MessageUtils;
import com.cpop.core.utils.SecurityUtils;
+import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/entity/LoginForm.java b/Cpop-Core/src/main/java/com/cpop/core/base/entity/LoginForm.java
new file mode 100644
index 0000000..c3f5bd3
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/base/entity/LoginForm.java
@@ -0,0 +1,35 @@
+package com.cpop.core.base.entity;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author DB
+ * @create 2023-04-05 17:53
+ */
+@Data
+public class LoginForm implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 用户名
+ */
+ private String username;
+
+ /**
+ * 密码
+ */
+ private String password;
+
+ /**
+ * 验证码
+ */
+ private String code;
+
+ /**
+ * 单次标识
+ */
+ private String userKey;
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/entity/LoginSuccess.java b/Cpop-Core/src/main/java/com/cpop/core/base/entity/LoginSuccess.java
new file mode 100644
index 0000000..d90fc41
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/base/entity/LoginSuccess.java
@@ -0,0 +1,34 @@
+package com.cpop.core.base.entity;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Set;
+
+/**
+ * @author: DB
+ * @date: 2022-09-26 22:48
+ * @Description: 登录成功vo
+ */
+@Data
+@Accessors(chain = true)
+public class LoginSuccess implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 用户id
+ */
+ private String userId;
+
+ /**
+ * token
+ */
+ private String token;
+
+ /**
+ * 权限
+ */
+ private Set role;
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/entity/loginInfo/OamStaffLoginInfo.java b/Cpop-Core/src/main/java/com/cpop/core/base/entity/loginInfo/OamStaffLoginInfo.java
new file mode 100644
index 0000000..14c0a96
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/base/entity/loginInfo/OamStaffLoginInfo.java
@@ -0,0 +1,47 @@
+package com.cpop.core.base.entity.loginInfo;
+
+import com.cpop.core.base.table.SysUser;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * @author DB
+ * @createTime 2023/09/10 13:02
+ * @description 系统员工登陆信息
+ */
+@Data
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = true)
+public class OamStaffLoginInfo extends SysUser {
+
+ /**
+ * 员工id(staffId)
+ */
+ private String id;
+
+ /**
+ * 姓名
+ */
+ private String name;
+
+ /**
+ * 部门id
+ */
+ private String deptId;
+
+ /**
+ * 用户id
+ */
+ private String userId;
+
+ /**
+ * 员工类型(0:技术人员;1:售后人员;2:管理人员)
+ */
+ private Integer staffType;
+
+ /**
+ * 角色id
+ */
+ private String roleId;
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/enums/OperationLogEnum.java b/Cpop-Core/src/main/java/com/cpop/core/base/enums/OperationLogEnum.java
index 6882fad..8a03df2 100644
--- a/Cpop-Core/src/main/java/com/cpop/core/base/enums/OperationLogEnum.java
+++ b/Cpop-Core/src/main/java/com/cpop/core/base/enums/OperationLogEnum.java
@@ -9,7 +9,14 @@ import lombok.Getter;
*/
@Getter
public enum OperationLogEnum {
-
+ /**
+ * 系统登录
+ */
+ SYSTEM_LOGIN(0, "i18n_operationLog_systemLogin", "SystemLoginModel", 6, 4),
+ /**
+ * 系统用户退出登录
+ */
+ SYSTEM_LOGOUT(1, "i18n_operationLog_systemLogout", "SystemLoginModel", 6, 4),
;
/**
diff --git a/Cpop-Core/src/main/java/com/cpop/core/base/exception/CpopAuthenticationException.java b/Cpop-Core/src/main/java/com/cpop/core/base/exception/CpopAuthenticationException.java
new file mode 100644
index 0000000..46e213e
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/base/exception/CpopAuthenticationException.java
@@ -0,0 +1,26 @@
+package com.cpop.core.base.exception;
+
+import org.springframework.security.core.AuthenticationException;
+
+/**
+ * @author: DB
+ * @Date: 2023/08/24/18:10
+ * @Description: 自定义认证失败异常
+ */
+public class CpopAuthenticationException extends AuthenticationException {
+
+ private Object params;
+
+ public CpopAuthenticationException(String msg) {
+ super(msg);
+ }
+
+ public CpopAuthenticationException(String msg, Object params) {
+ super(msg);
+ this.params = params;
+ }
+
+ public Object getParams() {
+ return params;
+ }
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/config/CpopConfig.java b/Cpop-Core/src/main/java/com/cpop/core/config/CpopConfig.java
index e49fc49..7110a96 100644
--- a/Cpop-Core/src/main/java/com/cpop/core/config/CpopConfig.java
+++ b/Cpop-Core/src/main/java/com/cpop/core/config/CpopConfig.java
@@ -47,7 +47,7 @@ public class CpopConfig {
this.version = version;
}
- public static String getProfile() {
+ public String getProfile() {
return profile;
}
@@ -66,28 +66,28 @@ public class CpopConfig {
/**
* 获取导入上传路径
*/
- public static String getImportPath() {
+ public String getImportPath() {
return getProfile() + "/import";
}
/**
* 获取头像上传路径
*/
- public static String getAvatarPath() {
+ public String getAvatarPath() {
return getProfile() + "/avatar";
}
/**
* 获取下载路径
*/
- public static String getDownloadPath() {
+ public String getDownloadPath() {
return getProfile() + "/download/";
}
/**
* 获取上传路径
*/
- public static String getUploadPath() {
+ public String getUploadPath() {
return getProfile() + "/upload";
}
}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/config/GenerateKeyPairConfig.java b/Cpop-Core/src/main/java/com/cpop/core/config/GenerateKeyPairConfig.java
new file mode 100644
index 0000000..1b181c7
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/config/GenerateKeyPairConfig.java
@@ -0,0 +1,67 @@
+package com.cpop.core.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author DB
+ * @Description: 生成密钥对的配置文件
+ * @create: 2023-08-04 23:06
+ */
+@Component
+@ConfigurationProperties(prefix = "cpop.gateway.rsa-keypair")
+public class GenerateKeyPairConfig {
+
+ /**
+ * 加密方式
+ */
+ private String algorithm;
+
+ /**
+ * 初始化大小
+ */
+ private Integer keySize;
+
+ /**
+ * 公钥文件
+ */
+ private String publicKeyFile;
+
+ /**
+ * 私钥文件
+ */
+ private String privateKeyFile;
+
+ /**
+ * 获取指定加密算法
+ * @return 读取YAML文件的 SystemConfig.rsa-keypair.algorithm 属性
+ */
+ public String getAlgorithm() {
+ return algorithm;
+ }
+
+ /**
+ * 获取密钥长度,用来初始化
+ * @return 读取YAML文件的 SystemConfig.rsa-keypair.key-size 属性
+ */
+ public Integer getKeySize() {
+ return keySize;
+ }
+
+ /**
+ * 获取公钥存放文件
+ * @return 读取YAML文件的 SystemConfig.rsa-keypair.public-key-file 属性
+ */
+ public String getPublicKeyFile() {
+ return publicKeyFile;
+ }
+
+ /**
+ * 获取私钥存放文件
+ * @return 读取YAML文件的 SystemConfig.rsa-keypair.private-key-file 属性
+ */
+ public String getPrivateKeyFile() {
+ return privateKeyFile;
+ }
+}
+
diff --git a/Cpop-Core/src/main/java/com/cpop/core/config/MybatisFlexConfig.java b/Cpop-Core/src/main/java/com/cpop/core/config/MybatisFlexConfig.java
new file mode 100644
index 0000000..d9fc961
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/config/MybatisFlexConfig.java
@@ -0,0 +1,19 @@
+package com.cpop.core.config;
+
+import com.mybatisflex.core.mybatis.FlexConfiguration;
+import com.mybatisflex.spring.boot.ConfigurationCustomizer;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author DB
+ * @Description:
+ * @create 2023-08-27 12:13
+ */
+@Configuration
+public class MybatisFlexConfig implements ConfigurationCustomizer {
+
+ @Override
+ public void customize(FlexConfiguration configuration) {
+ // 在这里为 configuration 进行配置
+ }
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/config/SecurityConfig.java b/Cpop-Core/src/main/java/com/cpop/core/config/SecurityConfig.java
new file mode 100644
index 0000000..530fad5
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/config/SecurityConfig.java
@@ -0,0 +1,225 @@
+package com.cpop.core.config;
+
+import com.cpop.common.constant.Constants;
+import com.cpop.common.utils.StringUtils;
+import com.cpop.core.filter.JwtAuthenticationFilter;
+import com.cpop.core.filter.RepeatableFilter;
+import com.cpop.core.gateway.miniProgram.MiniProgramAuthenticationFilter;
+import com.cpop.core.gateway.miniProgram.MiniProgramAuthenticationProvider;
+import com.cpop.core.gateway.oam.OamUsernamePasswordAuthenticationFilter;
+import com.cpop.core.handler.*;
+import com.cpop.core.service.impl.OamStaffDetailsServiceImpl;
+import com.cpop.core.utils.JwtUtils;
+import com.cpop.core.utils.PasswordEncoder;
+import com.cpop.core.utils.SpringUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.ProviderManager;
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.authentication.logout.LogoutFilter;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * @author DB
+ * @Description: Security配置中心
+ * @since 2023-08-05 8:01
+ */
+@Configuration
+@EnableWebSecurity
+@RequiredArgsConstructor
+@EnableMethodSecurity
+public class SecurityConfig implements WebMvcConfigurer {
+
+ @Autowired
+ private JwtLogoutSuccessHandler jwtLogoutSuccessHandler;
+
+ @Autowired
+ private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
+
+ @Autowired
+ private JwtAccessDeniedHandler jwtAccessDeniedHandler;
+
+ @Autowired
+ private RepeatableFilter repeatableFilter;
+
+ @Autowired
+ private LoginSuccessHandler loginSuccessHandler;
+
+ @Autowired
+ private LoginFailureHandler loginFailureHandler;
+
+ @Autowired
+ private CpopConfig cpopConfig;
+
+ @Autowired
+ private JwtUtils jwtUtils;
+
+ /**
+ * Security过滤拦截配置
+ *
+ * @param http 请求
+ * @return SecurityFilterChain 认证过滤链
+ * @throws Exception 异常
+ */
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ http
+ .cors(AbstractHttpConfigurer::disable)
+ .csrf(AbstractHttpConfigurer::disable)
+ //登录配置(多登陆模式自定义成功与失败->现已注释)
+ /*.formLogin(formLogin -> {
+ formLogin.successHandler(loginSuccessHandler)
+ .failureHandler(loginFailureHandler);
+ })*/
+ //登出配置
+ .logout(logout -> logout.logoutSuccessHandler(jwtLogoutSuccessHandler))
+ //禁用session
+ .sessionManagement(sessionManagement -> {
+ sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
+ })
+ //配置拦截规则
+ .authorizeHttpRequests(authorizeHttpRequests -> {
+ authorizeHttpRequests.antMatchers(StringUtils.split(jwtUtils.getWhiteList(), ",")).permitAll()
+ .anyRequest().authenticated();
+ })
+ //异常处理器
+ .exceptionHandling(exceptionHandling -> {
+ exceptionHandling.authenticationEntryPoint(jwtAuthenticationEntryPoint)
+ .accessDeniedHandler(jwtAccessDeniedHandler);
+ })
+ //自定义认证管理器
+ .authenticationManager(authenticationManager())
+ //流重复使用
+ .addFilterBefore(repeatableFilter, UsernamePasswordAuthenticationFilter.class)
+ // 添加 JWT 过滤器,JWT 过滤器在退出认证过滤器之前
+ .addFilterBefore(authFilter(), LogoutFilter.class)
+ .addFilterAt(oamLoginFilter(http.getSharedObject(AuthenticationManager.class)), UsernamePasswordAuthenticationFilter.class)
+ //小程序认证
+ .addFilterAt(miniProgramAuthenticationFilter(http.getSharedObject(AuthenticationManager.class)), UsernamePasswordAuthenticationFilter.class);
+ return http.build();
+ }
+
+ /**
+ * 自定义用户密码过滤器
+ *
+ * @param authenticationManager 认证管理器
+ * @return OamUsernamePasswordAuthenticationFilter oma管理器
+ */
+ @Bean
+ public OamUsernamePasswordAuthenticationFilter oamLoginFilter(AuthenticationManager authenticationManager) {
+ OamUsernamePasswordAuthenticationFilter loginFilter = new OamUsernamePasswordAuthenticationFilter();
+ loginFilter.setFilterProcessesUrl("/login");
+ loginFilter.setAuthenticationManager(authenticationManager);
+ loginFilter.setAuthenticationSuccessHandler(loginSuccessHandler);
+ loginFilter.setAuthenticationFailureHandler(loginFailureHandler);
+ return loginFilter;
+ }
+
+ /**
+ * @Description: 默认的认证方式
+ * @return DaoAuthenticationProvider
+ * @author DB
+ * @Date: 2023/8/24 0024 16:37
+ */
+ @Bean
+ public DaoAuthenticationProvider authenticationProvider() {
+ DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
+ authProvider.setUserDetailsService(SpringUtils.getBean(OamStaffDetailsServiceImpl.class));
+ authProvider.setPasswordEncoder(passwordEncoder());
+ return authProvider;
+ }
+
+ /**
+ * @Description: 小程序登陆认证方式(可自定义其他模式)
+ * @param authenticationManager 认证管理器
+ * @return MiniProgramAuthenticationFilter
+ * @author DB
+ * @Date: 2023/8/24 0024 10:43
+ */
+ @Bean
+ public MiniProgramAuthenticationFilter miniProgramAuthenticationFilter(AuthenticationManager authenticationManager) {
+ MiniProgramAuthenticationFilter filter = new MiniProgramAuthenticationFilter();
+ filter.setAuthenticationManager(authenticationManager);
+ filter.setAuthenticationSuccessHandler(loginSuccessHandler);
+ filter.setAuthenticationFailureHandler(loginFailureHandler);
+ return filter;
+ }
+
+ @Bean
+ public MiniProgramAuthenticationProvider miniProgramAuthenticationProvider() {
+ return new MiniProgramAuthenticationProvider();
+ }
+
+ /**
+ * 获取AuthenticationManager(认证管理器),登录时认证使用
+ * @return AuthenticationManager
+ */
+ @Bean
+ public AuthenticationManager authenticationManager(){
+ return new ProviderManager(Arrays.asList(authenticationProvider(), miniProgramAuthenticationProvider()));
+ }
+
+ /**
+ * 密码明文加密方式配置
+ * @return PasswordEncoder
+ */
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new PasswordEncoder();
+ }
+
+ /**
+ * jwt 校验过滤器,从 http 头部 Authorization 字段读取 token 并校验
+ * @return JwtAuthenticationFilter
+ */
+ @Bean
+ public JwtAuthenticationFilter authFilter() {
+ return new JwtAuthenticationFilter();
+ }
+
+ /**
+ * 配置跨源访问(CORS)
+ * @return CorsConfigurationSource
+ */
+ @Bean
+ public CorsConfigurationSource corsConfigurationSource() {
+ CorsConfiguration configuration = new CorsConfiguration();
+ configuration.setAllowedOrigins(Collections.singletonList("*"));
+ configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
+ configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
+ configuration.applyPermitDefaultValues();
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+ source.registerCorsConfiguration("/**", configuration);
+ return source;
+ }
+
+ /**
+ * @param registry 资源注册
+ * @Description: 资源映射
+ * @Author DB
+ * @Date: 2023/5/27 13:57
+ */
+ @Override
+ public void addResourceHandlers(ResourceHandlerRegistry registry) {
+ //本地文件上传路径
+ registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**")
+ .addResourceLocations("file:" + cpopConfig.getProfile() + "/");
+ }
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/filter/JwtAuthenticationFilter.java b/Cpop-Core/src/main/java/com/cpop/core/filter/JwtAuthenticationFilter.java
new file mode 100644
index 0000000..0b5a1de
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/filter/JwtAuthenticationFilter.java
@@ -0,0 +1,125 @@
+package com.cpop.core.filter;
+
+import com.alibaba.fastjson.JSONObject;
+import com.cpop.common.constant.Constants;
+import com.cpop.common.utils.StringUtils;
+import com.cpop.common.utils.ip.IpUtils;
+import com.cpop.core.base.entity.LoginUser;
+import com.cpop.core.base.enums.UserType;
+import com.cpop.core.service.RedisService;
+import com.cpop.core.utils.JwtUtils;
+import com.pupu.core.security.miniProgram.MiniProgramAuthenticationToken;
+import com.pupu.core.service.impl.OamStaffDetailsServiceImpl;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.JwtException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * @author: DB
+ * @date: 2022-09-25 16:34
+ * @Description: jwt认证过滤器
+ */
+@Component
+public class JwtAuthenticationFilter extends OncePerRequestFilter {
+
+ @Autowired
+ private JwtUtils jwtUtils;
+
+ @Autowired
+ private OamStaffDetailsServiceImpl oamStaffDetailsService;
+
+ @Autowired
+ private RedisService redisService;
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
+ String jwt = request.getHeader(jwtUtils.getHeader());
+ // 这里如果没有jwt,继续往后走,因为后面还有鉴权管理器等去判断是否拥有身份凭证,所以是可以放行的
+ // 没有jwt相当于匿名访问,若有一些接口是需要权限的,则不能访问这些接口
+ if (StringUtils.isEmpty(jwt)) {
+ chain.doFilter(request, response);
+ return;
+ }
+ Jws jws = jwtUtils.getClaimsByToken(jwt);
+ Claims claim = jws.getBody();
+ if (claim == null) {
+ throw new JwtException("token 异常");
+ }
+ if (jwtUtils.isTokenExpired(claim)) {
+ throw new JwtException("token 已过期");
+ }
+ String username = claim.getSubject();
+ UserType userType = UserType.valueOf(jws.getHeader().get(Constants.USER_TYPE).toString());
+ //从缓存中获取
+ JSONObject jsonObject = redisService.getCacheObject(userType.getKey() + username);
+ LoginUser loginUser;
+ if (null == jsonObject){
+ loginUser = multipleLoadUser(userType, username);
+ //存入缓存
+ redisService.setCacheObject(userType.getKey() + username, loginUser);
+ } else {
+ loginUser = jsonObject.toJavaObject(LoginUser.class);
+ }
+ //获取当前请求ip
+ loginUser.setIpAddr(IpUtils.getIpAddr(request));
+ multipleAuth(loginUser,username);
+ chain.doFilter(request, response);
+ }
+
+ /**
+ * @descriptions 多种获取用户信息
+ * @author DB
+ * @date 2023/09/11 13:49
+ * @param userType 用户类型
+ * @param username 用户名
+ * @return com.pupu.core.base.entity.LoginUser
+ */
+ private LoginUser multipleLoadUser(UserType userType, String username) {
+ LoginUser loginUser = null;
+ switch (userType) {
+ //OAM
+ case OAM_USER:
+ // 获取用户的权限等信息
+ loginUser = (LoginUser) oamStaffDetailsService.loadUserByUsername(username);
+ break;
+ case MINI_USER:
+ break;
+ default:
+ }
+ return loginUser;
+ }
+
+ /**
+ * @Description: 多种认证方式
+ * @param loginUser 登陆用户信息
+ * @author DB
+ * @Date: 2023/8/24 0024 17:12
+ */
+ private void multipleAuth(LoginUser loginUser, String username) {
+ switch (loginUser.getUserType()) {
+ case OAM_USER:
+ // 构建UsernamePasswordAuthenticationToken,这里密码为null,是因为提供了正确的JWT,实现自动登录
+ UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null);
+ SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
+ break;
+ case MINI_USER:
+ // MiniProgramAuthenticationToken,实现自动登录
+ MiniProgramAuthenticationToken miniProgramAuthenticationToken = new MiniProgramAuthenticationToken(username, loginUser);
+ SecurityContextHolder.getContext().setAuthentication(miniProgramAuthenticationToken);
+ break;
+ default:
+ }
+
+ }
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/gateway/miniProgram/MiniProgramAuthenticationFilter.java b/Cpop-Core/src/main/java/com/cpop/core/gateway/miniProgram/MiniProgramAuthenticationFilter.java
new file mode 100644
index 0000000..49e58ff
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/gateway/miniProgram/MiniProgramAuthenticationFilter.java
@@ -0,0 +1,55 @@
+package com.cpop.core.gateway.miniProgram;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+/**
+ * @author DB
+ * @Description: 小程序认证过滤器
+ * @create 2023-08-23 21:03
+ */
+public class MiniProgramAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
+
+
+ public MiniProgramAuthenticationFilter() {
+ super(new AntPathRequestMatcher("/miniProgramLogin", "POST"));
+ }
+
+ @Override
+ public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
+ if (!"POST".equals(request.getMethod())) {
+ throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
+ }
+ ObjectMapper mapper = new ObjectMapper();
+ InputStream inputStream;
+ Map authenticationBean;
+ try {
+ inputStream = request.getInputStream();
+ authenticationBean = mapper.readValue(inputStream, Map.class);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ String username = (String) authenticationBean.get("username");
+ String password = (String) authenticationBean.get("password");
+ username = username.trim();
+ MiniProgramAuthenticationToken authRequest = new MiniProgramAuthenticationToken(username, password);
+ this.setDetails(request, authRequest);
+ return this.getAuthenticationManager().authenticate(authRequest);
+ }
+
+ protected void setDetails(HttpServletRequest request, AbstractAuthenticationToken authRequest) {
+ authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
+ }
+
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/gateway/miniProgram/MiniProgramAuthenticationProvider.java b/Cpop-Core/src/main/java/com/cpop/core/gateway/miniProgram/MiniProgramAuthenticationProvider.java
new file mode 100644
index 0000000..92e4487
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/gateway/miniProgram/MiniProgramAuthenticationProvider.java
@@ -0,0 +1,38 @@
+package com.cpop.core.gateway.miniProgram;
+
+import com.cpop.core.base.entity.LoginUser;
+import com.cpop.core.base.enums.UserType;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+
+/**
+ * @author DB
+ * @Description: 自定义校验过程
+ * @create 2023-08-23 21:13
+ */
+public class MiniProgramAuthenticationProvider implements AuthenticationProvider {
+
+ @Override
+ public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+ // 这个获取表单输入中返回的用户名;
+ String username = authentication.getName();
+ // 这个是表单中输入的密码;
+ String password = (String)authentication.getCredentials();
+ //TODO:此处添加小程序认证失败返回 throw new RockBladeAuthenticationException("测试抛异常", UserType.MINI_USER);
+ //设置用户详情
+ LoginUser loginUser = new LoginUser();
+ loginUser.setUserName(username)
+ .setUserType(UserType.MINI_USER);
+ MiniProgramAuthenticationToken result = new MiniProgramAuthenticationToken(username, loginUser);
+ result.setDetails(loginUser);
+ return result;
+ }
+
+ @Override
+ public boolean supports(Class> authentication) {
+ //providerManager会遍历所有;securityConfig中注册的provider集合;根据此方法返回true或false来决定由哪个provider;去校验请求过来的authentication
+ return (MiniProgramAuthenticationToken.class.isAssignableFrom(authentication));
+ }
+
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/gateway/miniProgram/MiniProgramAuthenticationToken.java b/Cpop-Core/src/main/java/com/cpop/core/gateway/miniProgram/MiniProgramAuthenticationToken.java
new file mode 100644
index 0000000..fa2fa9a
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/gateway/miniProgram/MiniProgramAuthenticationToken.java
@@ -0,0 +1,72 @@
+package com.cpop.core.gateway.miniProgram;
+
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.GrantedAuthority;
+
+import java.util.Collection;
+
+/**
+ * @author DB
+ * @Description:
+ * @create 2023-08-23 21:11
+ */
+public class MiniProgramAuthenticationToken extends AbstractAuthenticationToken {
+
+ private final Object principal;
+ private Object credentials;
+
+ /**
+ * This constructor can be safely used by any code that wishes to create a
+ * UsernamePasswordAuthenticationToken, as the {@link #isAuthenticated()}
+ * will return false.
+ *
+ */
+ public MiniProgramAuthenticationToken(Object principal, Object credentials) {
+ super(null);
+ this.principal = principal;
+ this.credentials = credentials;
+ setAuthenticated(false);
+ }
+
+ /**
+ * This constructor should only be used by AuthenticationManager or
+ * AuthenticationProvider implementations that are satisfied with
+ * producing a trusted (i.e. {@link #isAuthenticated()} = true)
+ * authentication token.
+ *
+ * @param principal
+ * @param credentials
+ * @param authorities
+ */
+ public MiniProgramAuthenticationToken(Object principal, Object credentials, Collection extends GrantedAuthority> authorities) {
+ super(authorities);
+ this.principal = principal;
+ this.credentials = credentials;
+ // must use super, as we override
+ super.setAuthenticated(true);
+ }
+
+ @Override
+ public Object getCredentials() {
+ return this.credentials;
+ }
+
+ @Override
+ public Object getPrincipal() {
+ return this.principal;
+ }
+
+ @Override
+ public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
+ if (isAuthenticated) {
+ throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
+ }
+ super.setAuthenticated(false);
+ }
+
+ @Override
+ public void eraseCredentials() {
+ super.eraseCredentials();
+ credentials = null;
+ }
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/gateway/oam/OamUsernamePasswordAuthenticationFilter.java b/Cpop-Core/src/main/java/com/cpop/core/gateway/oam/OamUsernamePasswordAuthenticationFilter.java
new file mode 100644
index 0000000..620419a
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/gateway/oam/OamUsernamePasswordAuthenticationFilter.java
@@ -0,0 +1,45 @@
+package com.cpop.core.gateway.oam;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.security.authentication.AuthenticationServiceException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+
+/**
+ * @author DB
+ * @create 2023-04-05 17:38
+ * OAM管理系统为基础认证系统,使用默认的过滤认证方式
+ */
+public class OamUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
+ if ("POST".equalsIgnoreCase(request.getMethod())) {
+ ObjectMapper mapper = new ObjectMapper();
+ InputStream inputStream;
+ Map authenticationBean;
+ try {
+ inputStream = request.getInputStream();
+ authenticationBean = mapper.readValue(inputStream, Map.class);
+ String username = (String) authenticationBean.get("username");
+ String password = (String) authenticationBean.get("password");
+ UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
+ this.setDetails(request, authRequest);
+ return this.getAuthenticationManager().authenticate(authRequest);
+ } catch (IOException e) {
+ throw new AuthenticationServiceException("Authentication method io exception: " + request.getMethod());
+ }
+ }
+ // 只能是post请求
+ throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
+ }
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/handler/JwtAccessDeniedHandler.java b/Cpop-Core/src/main/java/com/cpop/core/handler/JwtAccessDeniedHandler.java
new file mode 100644
index 0000000..a4e03d4
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/handler/JwtAccessDeniedHandler.java
@@ -0,0 +1,33 @@
+package com.cpop.core.handler;
+
+import com.alibaba.fastjson.JSONObject;
+import com.cpop.core.base.R;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * @author: DB
+ * @date: 2022-09-25 16:55
+ * @Description: 无权限访问处理器
+ */
+@Component
+public class JwtAccessDeniedHandler implements AccessDeniedHandler {
+
+ @Override
+ public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException {
+ httpServletResponse.setContentType("application/json;charset=UTF-8");
+ httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
+ ServletOutputStream outputStream = httpServletResponse.getOutputStream();
+ R result = R.fail(e.getMessage());
+ outputStream.write(JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8));
+ outputStream.flush();
+ outputStream.close();
+ }
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/handler/JwtAuthenticationEntryPoint.java b/Cpop-Core/src/main/java/com/cpop/core/handler/JwtAuthenticationEntryPoint.java
new file mode 100644
index 0000000..25fc1a7
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/handler/JwtAuthenticationEntryPoint.java
@@ -0,0 +1,33 @@
+package com.cpop.core.handler;
+
+import com.alibaba.fastjson.JSONObject;
+import com.cpop.core.base.R;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * @author: DB
+ * @date: 2022-09-25 16:40
+ * @Description: jwt认证失败处理器
+ */
+@Component
+public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
+
+ @Override
+ public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
+ httpServletResponse.setContentType("application/json;charset=UTF-8");
+ httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ ServletOutputStream outputStream = httpServletResponse.getOutputStream();
+ R result = R.fail("请先登录");
+ outputStream.write(JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8));
+ outputStream.flush();
+ outputStream.close();
+ }
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/handler/JwtLogoutSuccessHandler.java b/Cpop-Core/src/main/java/com/cpop/core/handler/JwtLogoutSuccessHandler.java
new file mode 100644
index 0000000..bb181f6
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/handler/JwtLogoutSuccessHandler.java
@@ -0,0 +1,77 @@
+package com.cpop.core.handler;
+
+import com.alibaba.fastjson.JSONObject;
+import com.cpop.common.constant.Constants;
+import com.cpop.core.base.R;
+import com.cpop.core.base.entity.LoginUser;
+import com.cpop.core.base.enums.OperationLogEnum;
+import com.cpop.core.base.enums.UserType;
+import com.cpop.core.service.CoreService;
+import com.cpop.core.service.RedisService;
+import com.cpop.core.utils.JwtUtils;
+import com.cpop.core.utils.MessageUtils;
+import com.cpop.core.utils.SpringUtils;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.JwtException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * @author: DB
+ * @date: 2022-09-25 16:56
+ * @Description: 登出成功处理器
+ */
+@Component
+public class JwtLogoutSuccessHandler implements LogoutSuccessHandler {
+ @Autowired
+ private JwtUtils jwtUtils;
+
+ @Autowired
+ private RedisService redisService;
+
+ @Override
+ public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException {
+ if (authentication != null) {
+ new SecurityContextLogoutHandler().logout(httpServletRequest, httpServletResponse, authentication);
+ }
+ String jwt = httpServletRequest.getHeader(jwtUtils.getHeader());
+ //添加退出登录成功日志
+ Jws jws = jwtUtils.getClaimsByToken(jwt);
+ Claims claim = jws.getBody();
+ if (claim == null) {
+ throw new JwtException("token 异常");
+ }
+ if (jwtUtils.isTokenExpired(claim)) {
+ throw new JwtException("token 已过期");
+ }
+ String username = claim.getSubject();
+ UserType userType = UserType.valueOf(jws.getHeader().get(Constants.USER_TYPE).toString());
+ //从缓存中获取
+ JSONObject jsonObject = redisService.getCacheObject(userType.getKey() + username);
+ if (null != jsonObject) {
+ LoginUser loginUser = jsonObject.toJavaObject(LoginUser.class);
+ SpringUtils.getBean(CoreService.class).insertOperationLog(Constants.SUCCESS, OperationLogEnum.SYSTEM_LOGOUT, loginUser, MessageUtils.message("i18n_operationLog_systemLogout"));
+ }
+ //清除缓存
+ redisService.deleteObject(userType.getKey() + username);
+ httpServletResponse.setContentType("application/json;charset=UTF-8");
+ ServletOutputStream outputStream = httpServletResponse.getOutputStream();
+ String header = jwtUtils.getHeader();
+ httpServletResponse.setHeader(header, "");
+ R result = R.ok(MessageUtils.message("i18n_loginOut_success"));
+ outputStream.write(JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8));
+ outputStream.flush();
+ outputStream.close();
+ }
+
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/handler/LoginFailureHandler.java b/Cpop-Core/src/main/java/com/cpop/core/handler/LoginFailureHandler.java
new file mode 100644
index 0000000..4b150a9
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/handler/LoginFailureHandler.java
@@ -0,0 +1,63 @@
+package com.cpop.core.handler;
+
+import com.alibaba.fastjson.JSONObject;
+import com.cpop.common.constant.Constants;
+import com.cpop.common.utils.ip.IpUtils;
+import com.cpop.core.base.R;
+import com.cpop.core.base.entity.LoginForm;
+import com.cpop.core.base.entity.LoginUser;
+import com.cpop.core.base.enums.OperationLogEnum;
+import com.cpop.core.base.enums.UserType;
+import com.cpop.core.base.exception.CpopAuthenticationException;
+import com.cpop.core.service.CoreService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.stream.Collectors;
+
+/**
+ * @author: DB
+ * @date: 2022-09-25 16:24
+ * @Description: 登录失败handler
+ */
+@Component
+public class LoginFailureHandler implements AuthenticationFailureHandler {
+
+ @Autowired
+ private CoreService coreService;
+
+ @Override
+ public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
+ httpServletResponse.setContentType("application/json;charset=UTF-8");
+ ServletOutputStream outputStream = httpServletResponse.getOutputStream();
+ String jsonString = httpServletRequest.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
+ //映射登录表单
+ LoginForm loginFormBo = JSONObject.parseObject(jsonString, LoginForm.class);
+ //构建用户信息
+ LoginUser loginUser = new LoginUser();
+ loginUser.setUserName(loginFormBo.getUsername())
+ .setIpAddr(IpUtils.getIpAddr(httpServletRequest));
+ String errorMessage = "用户名或密码错误";
+ R result;
+ //自定义认证异常,可以从这里获取到想要的参数
+ if(e instanceof CpopAuthenticationException) {
+ loginUser.setUserType((UserType) ((CpopAuthenticationException) e).getParams());
+ errorMessage = e.getMessage();
+ }
+ result = R.fail(errorMessage);
+ if (loginUser.getUserType() == UserType.OAM_USER) {
+ //添加登录失败日志
+ coreService.insertOperationLog(Constants.FAIL, OperationLogEnum.SYSTEM_LOGIN, loginUser, errorMessage);
+ }
+ outputStream.write(JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8));
+ outputStream.flush();
+ outputStream.close();
+ }
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/handler/LoginSuccessHandler.java b/Cpop-Core/src/main/java/com/cpop/core/handler/LoginSuccessHandler.java
new file mode 100644
index 0000000..12e7c80
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/handler/LoginSuccessHandler.java
@@ -0,0 +1,90 @@
+package com.cpop.core.handler;
+
+import com.alibaba.fastjson.JSONObject;
+import com.cpop.common.constant.Constants;
+import com.cpop.common.utils.ip.IpUtils;
+import com.cpop.core.base.R;
+import com.cpop.core.base.entity.LoginSuccess;
+import com.cpop.core.base.entity.LoginUser;
+import com.cpop.core.base.enums.OperationLogEnum;
+import com.cpop.core.service.CoreService;
+import com.cpop.core.service.RedisService;
+import com.cpop.core.utils.JwtUtils;
+import com.cpop.core.utils.MessageUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * @author: DB
+ * @date: 2022-09-25 16:19
+ * @Description: 登录成功handler
+ */
+@Component
+public class LoginSuccessHandler implements AuthenticationSuccessHandler {
+
+ @Autowired
+ private JwtUtils jwtUtils;
+
+ @Autowired
+ private CoreService coreService;
+
+ @Autowired
+ private RedisService redisService;
+
+ @Override
+ public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException{
+ httpServletResponse.setContentType("application/json;charset=UTF-8");
+ ServletOutputStream outputStream = httpServletResponse.getOutputStream();
+ LoginSuccess loginSuccessVo = new LoginSuccess();
+ LoginUser loginUser;
+ if (null != authentication.getPrincipal()) {
+ loginUser = (LoginUser) authentication.getPrincipal();
+ } else {
+ loginUser = (LoginUser) authentication.getDetails();
+ }
+ // 生成JWT,并放置到请求头中
+ String jwt = jwtUtils.generateToken(authentication.getName(), loginUser.getUserType());
+ loginSuccessVo.setToken(jwt).setUserId(loginUser.getUserId()).setRole(loginUser.getPermissions());
+ String ipAddr = IpUtils.getIpAddr(httpServletRequest);
+ loginUser.setIpAddr(ipAddr);
+ //多种登陆方式
+ multipleLogin(loginUser);
+ R result = R.ok(loginSuccessVo, MessageUtils.message("i18n_login_success"));
+ outputStream.write(JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8));
+ outputStream.flush();
+ outputStream.close();
+ }
+
+ /**
+ * @Description: 多种登陆方式
+ * @param loginUser 登陆用户
+ * @author DB
+ * @Date: 2023/8/24 0024 17:12
+ */
+ private void multipleLogin(LoginUser loginUser){
+ switch (loginUser.getUserType()) {
+ case OAM_USER:
+ //更新登录地址
+ coreService.updateSysUserLoginIp(loginUser.getIpAddr(), loginUser.getUsername());
+ //添加登录成功日志
+ coreService.insertOperationLog(Constants.SUCCESS, OperationLogEnum.SYSTEM_LOGIN, loginUser, MessageUtils.message("i18n_login_success"));
+ //将登录成功用户存入缓存
+ redisService.setCacheObject(loginUser.getUserType().getKey() + loginUser.getUsername(), loginUser);
+ break;
+ case MINI_USER:
+ //将登录成功用户存入缓存
+ redisService.setCacheObject(loginUser.getUserType().getKey() + loginUser.getUsername(), loginUser);
+ break;
+ default:
+ }
+
+ }
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/service/impl/AuthorityPermissionService.java b/Cpop-Core/src/main/java/com/cpop/core/service/impl/AuthorityPermissionService.java
new file mode 100644
index 0000000..6e69716
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/service/impl/AuthorityPermissionService.java
@@ -0,0 +1,39 @@
+package com.cpop.core.service.impl;
+
+import com.cpop.common.constant.Constants;
+import com.cpop.common.utils.StringUtils;
+import com.cpop.core.base.entity.LoginUser;
+import com.cpop.core.utils.SecurityUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.Set;
+
+/**
+ * @author: DB
+ * @date: 2022-10-20 15:15
+ * @Description:
+ * AuthorityPermissionService:自定义access表达式,鉴权验证。
+ * 可以将自定义的access添加到配置类中:http.anyRequest.access(aps.hasPermission(xxx,xxx));
+ * 也可以直接使用注解@PreAuthorize:@PreAuthorize(aps.hasPermission(xxx));
+ */
+@Service("aps")
+public class AuthorityPermissionService {
+
+ public boolean hasPermission(String permissions) {
+ if (StringUtils.isEmpty(permissions)) {
+ return false;
+ }
+ // 用户信息对象
+ LoginUser loginUser = SecurityUtils.getInstance().getLoginUser();
+ if (loginUser == null || CollectionUtils.isEmpty(loginUser.getPermissions())) {
+ return false;
+ }
+ Set authorities = loginUser.getPermissions();
+ return StringUtils.isNotEmpty(permissions) && hasPermissions(authorities, permissions);
+ }
+
+ private boolean hasPermissions(Set authorities, String permission) {
+ return authorities.contains(Constants.ALL_PERMISSION) || authorities.contains(permission.trim());
+ }
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/service/impl/OamStaffDetailsServiceImpl.java b/Cpop-Core/src/main/java/com/cpop/core/service/impl/OamStaffDetailsServiceImpl.java
new file mode 100644
index 0000000..169cbfd
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/service/impl/OamStaffDetailsServiceImpl.java
@@ -0,0 +1,113 @@
+package com.cpop.core.service.impl;
+
+import com.cpop.common.constant.Constants;
+import com.cpop.common.utils.bean.BeanUtils;
+import com.cpop.core.base.entity.LoginUser;
+import com.cpop.core.base.entity.loginInfo.OamStaffLoginInfo;
+import com.cpop.core.base.enums.UserType;
+import com.cpop.core.base.table.SysUser;
+import com.cpop.core.service.CoreService;
+import com.cpop.core.utils.MessageUtils;
+import com.mybatisflex.core.row.DbChain;
+import com.mybatisflex.core.row.Row;
+import com.mybatisflex.core.row.RowUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * @author DB
+ * @createTime 2023/09/11 10:56
+ * @description oam员工登陆信息
+ */
+@Service("oamStaffDetailsService")
+public class OamStaffDetailsServiceImpl implements UserDetailsService {
+
+ @Autowired
+ private CoreService coreService;
+
+ /**
+ * @descriptions 加载员工信息
+ * @author DB
+ * @date 2023/09/11 10:57
+ * @param username 用户名
+ * @return org.springframework.security.core.userdetails.UserDetails
+ */
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ //统一获取系统用户信息
+ SysUser sysUser = coreService.getSysUser(username, UserType.OAM_USER);
+ if (sysUser == null) {
+ throw new UsernameNotFoundException(MessageUtils.message("i18n_alert_accountOrPwdError"));
+ }
+ //构建登陆员工信息
+ OamStaffLoginInfo staffLoginInfo = BeanUtils.mapToClass(sysUser, OamStaffLoginInfo.class);
+ staffLoginInfo.setUserId(sysUser.getId());
+ //员工
+ if (!staffLoginInfo.getUserName().equals(Constants.SUPER_ADMIN)) {
+ Row row = DbChain.table("pp_oam_staff")
+ .select()
+ .where("user_id = ?", staffLoginInfo.getUserId())
+ .one();
+ staffLoginInfo.setDeptId(row.getString("deptId"));
+ staffLoginInfo.setRoleId(row.getString("roleId"));
+ staffLoginInfo.setName(row.getString("name"));
+ staffLoginInfo.setStaffType(row.getInt("staffType"));
+ staffLoginInfo.setId(row.getString("id"));
+ }
+ //获取权限
+ Set permissionSet = new HashSet<>();
+ if (Constants.SUPER_ADMIN.equals(username)) {
+ permissionSet.add(Constants.ALL_PERMISSION);
+ staffLoginInfo.setName(Constants.SUPER_ADMIN);
+ }
+ else {
+ //查询员工信息
+ List list = DbChain.table("oam_menu")
+ .select("pom.permission")
+ .from("pp_oam_menu").as("pom")
+ .leftJoin("pp_oam_role_menu").as("porm").on("porm.menu_id = pom.id")
+ .where("pom.type in (1,2)")
+ .and("porm.role_id = ?", staffLoginInfo.getRoleId())
+ .and("pom.permission is not null")
+ .list();
+ if (list.isEmpty()) {
+ permissionSet = new HashSet<>();
+ } else {
+ List permissions = RowUtil.toEntityList(list, Permission.class);
+ permissionSet = permissions.stream().map(Permission::getPermission).collect(Collectors.toSet());
+ }
+ }
+ LoginUser loginUser = new LoginUser(sysUser.getId(), staffLoginInfo, permissionSet);
+ loginUser.setUserName(username)
+ .setLoginTime(System.currentTimeMillis())
+ .setStatus(sysUser.getStatus());
+ return loginUser;
+ }
+
+ /**
+ * 内部自用类
+ */
+ public class Permission {
+
+ /**
+ * 权限
+ */
+ private String permission;
+
+ public String getPermission() {
+ return permission;
+ }
+
+ public void setPermission(String permission) {
+ this.permission = permission;
+ }
+ }
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/utils/JwtUtils.java b/Cpop-Core/src/main/java/com/cpop/core/utils/JwtUtils.java
new file mode 100644
index 0000000..93c00c1
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/utils/JwtUtils.java
@@ -0,0 +1,90 @@
+package com.cpop.core.utils;
+
+import com.cpop.common.constant.Constants;
+import com.cpop.core.base.enums.UserType;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+
+/**
+ * @author: DB
+ * @date: 2022-09-25 16:14
+ * @Description: jwt核心工具类
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "cpop.jwt")
+public class JwtUtils {
+
+ /**
+ * 过期时间
+ */
+ private Long expire;
+ /**
+ * 密钥
+ */
+ private String secret;
+ /**
+ * 请求头
+ */
+ private String header;
+
+ /**
+ * 白名单
+ */
+ private String whiteList;
+
+ /**
+ * @author LOST.yuan
+ * @description 生成JWT
+ * @date 16:15 2022/9/25
+ * @param username 用户名
+ * @return {@link String}
+ **/
+ public String generateToken(String username, UserType userType) {
+ Date nowDate = new Date();
+ Date expireDate = new Date(nowDate.getTime() + 1000 * expire);
+ return Jwts.builder()
+ .setHeaderParam(Constants.USER_TYPE, userType)
+ .setSubject(username)
+ .setIssuedAt(nowDate)
+ // 7天过期
+ .setExpiration(expireDate)
+ .signWith(SignatureAlgorithm.HS512, secret)
+ .compact();
+ }
+
+ /**
+ * @author LOST.yuan
+ * @description 解析JWT
+ * @date 16:15 2022/9/25
+ * @param jwt
+ * @return {@link Claims}
+ **/
+ public Jws getClaimsByToken(String jwt) {
+ try {
+ return Jwts.parser()
+ .setSigningKey(secret)
+ .parseClaimsJws(jwt);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * @author LOST.yuan
+ * @description 判断JWT是否过期
+ * @date 16:15 2022/9/25
+ * @param claims jwt令牌
+ * @return {@link boolean}
+ **/
+ public boolean isTokenExpired(Claims claims) {
+ return claims.getExpiration().before(new Date());
+ }
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/utils/PasswordEncoder.java b/Cpop-Core/src/main/java/com/cpop/core/utils/PasswordEncoder.java
new file mode 100644
index 0000000..d36394f
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/utils/PasswordEncoder.java
@@ -0,0 +1,38 @@
+package com.cpop.core.utils;
+
+import lombok.NoArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.crypto.bcrypt.BCrypt;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+
+/**
+ * @author: DB
+ * @date: 2022-09-25 17:01
+ * @Description:
+ */
+@NoArgsConstructor
+public class PasswordEncoder extends BCryptPasswordEncoder {
+
+ @Autowired
+ private RsaUtils rsaUtils;
+
+ @Override
+ public boolean matches(CharSequence rawPassword, String encodedPassword) {
+ // 接收到的前端的密码
+ String pwd = rawPassword.toString();
+ //针对+变空格进行处理
+ pwd = pwd.replace(" ", "+");
+ // 进行rsa解密
+ try {
+ pwd = rsaUtils.decrypt(pwd);
+ } catch (Exception e) {
+ throw new BadCredentialsException(e.getMessage());
+ }
+ if (encodedPassword != null && encodedPassword.length() != 0) {
+ return BCrypt.checkpw(pwd, encodedPassword);
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/Cpop-Core/src/main/java/com/cpop/core/utils/RsaUtils.java b/Cpop-Core/src/main/java/com/cpop/core/utils/RsaUtils.java
new file mode 100644
index 0000000..e73e662
--- /dev/null
+++ b/Cpop-Core/src/main/java/com/cpop/core/utils/RsaUtils.java
@@ -0,0 +1,203 @@
+package com.cpop.core.utils;
+
+import com.cpop.core.base.exception.UtilException;
+import com.cpop.core.config.GenerateKeyPairConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import java.io.File;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.*;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author DB
+ * @Description: Rsa相关工具类
+ * @create 2023-08-04 23:09
+ */
+@Component
+public class RsaUtils {
+
+ @Autowired
+ private GenerateKeyPairConfig keyPairConfig;
+
+ // region 私有方法
+
+ /**
+ * 生成密钥对
+ * @return 返回map集合,其中包含publicKey与privateKey
+ * @throws NoSuchAlgorithmException
+ */
+ private Map generateKeyPair() throws NoSuchAlgorithmException {
+ //RSA算法要求有一个可信任的随机数源
+ SecureRandom secureRandom = new SecureRandom();
+ //为RSA算法创建一个KeyPairGenerator对象
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(keyPairConfig.getAlgorithm());
+ //利用上面的随机数据源初始化这个KeyPairGenerator对象
+ keyPairGenerator.initialize(keyPairConfig.getKeySize(), secureRandom);
+ //生成密匙对
+ KeyPair keyPair = keyPairGenerator.generateKeyPair();
+ //得到公钥
+ Key publicKey = keyPair.getPublic();
+ //得到私钥
+ Key privateKey = keyPair.getPrivate();
+ Map keyPairMap = new HashMap<>();
+ keyPairMap.put("publicKey", publicKey);
+ keyPairMap.put("privateKey", privateKey);
+ return keyPairMap;
+ }
+
+ /**
+ * 获取文件中获取密钥对象
+ * @param fileName 文件名
+ * @return 密钥对象
+ */
+ private Key getKeyFromFile(String fileName) {
+ Key key = null;
+ ObjectInputStream ois = null;
+ try {
+ ois = new ObjectInputStream(Files.newInputStream(Paths.get(fileName)));
+ key = (Key) ois.readObject();
+ } catch (IOException | ClassNotFoundException e) {
+ throw new UtilException(e.getMessage());
+ } finally {
+ try {
+ assert ois != null;
+ ois.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return key;
+ }
+
+ /**
+ * 将密钥对生成到文件中
+ */
+ private void generateKeyPairToFiles() {
+ ObjectOutputStream oosPublicKey = null;
+ ObjectOutputStream oosPrivateKey = null;
+ try {
+ Map keyPairMap = generateKeyPair();
+ Key publicKey = keyPairMap.get("publicKey");
+ Key privateKey = keyPairMap.get("privateKey");
+ oosPublicKey = new ObjectOutputStream(Files.newOutputStream(Paths.get(keyPairConfig.getPublicKeyFile())));
+ oosPrivateKey = new ObjectOutputStream(Files.newOutputStream(Paths.get(keyPairConfig.getPrivateKeyFile())));
+ oosPublicKey.writeObject(publicKey);
+ oosPrivateKey.writeObject(privateKey);
+ } catch (NoSuchAlgorithmException | IOException e) {
+ throw new UtilException(e.getMessage());
+ } finally {
+ try {
+ //清空缓存,关闭文件输出流
+ assert oosPublicKey != null;
+ oosPublicKey.close();
+ assert oosPrivateKey != null;
+ oosPrivateKey.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * 初始化密钥对文件
+ * @return 返回密钥对信息【publicKey(公钥字符串)、、privateKey(私钥字符串)】
+ */
+ public Map initKeyPair() {
+ Map keyPairMap = new HashMap<>();
+ File publicFile = new File(keyPairConfig.getPublicKeyFile());
+ File privateFile = new File(keyPairConfig.getPrivateKeyFile());
+ //判断是否存在公钥和私钥文件
+ if (!publicFile.exists() || !privateFile.exists()) {
+ generateKeyPairToFiles();
+ }
+ ObjectInputStream oisPublic = null;
+ ObjectInputStream oisPrivate = null;
+ Key publicKey = null;
+ Key privateKey = null;
+ try {
+ oisPublic = new ObjectInputStream(Files.newInputStream(Paths.get(keyPairConfig.getPublicKeyFile())));
+ oisPrivate = new ObjectInputStream(Files.newInputStream(Paths.get(keyPairConfig.getPrivateKeyFile())));
+ publicKey = (Key) oisPublic.readObject();
+ privateKey = (Key) oisPrivate.readObject();
+ byte[] publicKeyBytes = publicKey.getEncoded();
+ byte[] privateKeyBytes = privateKey.getEncoded();
+ String publicKeyBase64 = Base64.getEncoder().encodeToString(publicKeyBytes);
+ String privateKeyBase64 = Base64.getEncoder().encodeToString(privateKeyBytes);
+ //公钥字符串
+ keyPairMap.put("publicKey", publicKeyBase64);
+ //私钥字符串
+ keyPairMap.put("privateKey", privateKeyBase64);
+ } catch (IOException | ClassNotFoundException e) {
+ throw new UtilException(e.getMessage());
+ } finally {
+ try {
+ assert oisPrivate != null;
+ oisPrivate.close();
+ oisPublic.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return keyPairMap;
+ }
+
+ /**
+ * 加密方法
+ * @param source 源数据
+ * @return 加密后的字符串
+ */
+ public String encrypt(String source) {
+ Key publicKey = getKeyFromFile(keyPairConfig.getPublicKeyFile());
+ Base64.Encoder encoder = Base64.getEncoder();
+ String encryptSource = null;
+ try {
+ //得到Cipher对象来实现对源数据的RSA加密
+ Cipher cipher = Cipher.getInstance(keyPairConfig.getAlgorithm());
+ cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+ byte[] bytes = source.getBytes();
+ //执行加密操作
+ encryptSource = encoder.encodeToString(cipher.doFinal(bytes));
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException |
+ BadPaddingException e) {
+ throw new UtilException(e.getMessage());
+ }
+ return encryptSource;
+ }
+
+ /**
+ * 解密方法
+ * @param source 密文
+ * @return 解密后的字符串
+ */
+ public String decrypt(String source) {
+ Key privateKey = getKeyFromFile(keyPairConfig.getPrivateKeyFile());
+ Base64.Decoder decoder = Base64.getDecoder();
+ String decryptSource = null;
+ try {
+ //得到Cipher对象对已用公钥加密的数据进行RSA解密
+ Cipher cipher = Cipher.getInstance(keyPairConfig.getAlgorithm());
+ cipher.init(Cipher.DECRYPT_MODE, privateKey);
+ //执行解密操作
+ byte[] bytes = decoder.decode(source);
+ decryptSource = new String(cipher.doFinal(bytes), StandardCharsets.UTF_8);
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException |
+ IllegalBlockSizeException | BadPaddingException e) {
+ throw new UtilException(e.getMessage());
+ }
+ return decryptSource;
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index 9ba1fdf..66f4228 100644
--- a/pom.xml
+++ b/pom.xml
@@ -30,6 +30,8 @@
2.0.38
1.6.4
3.3.2
+ 1.21
+ 0.9.1
@@ -58,12 +60,24 @@
fastjson
${fastjson.version}
+
+
+ io.jsonwebtoken
+ jjwt
+ ${jjwt.version}
+
com.cpop
Cpop-Common
${cpop.version}
+
+
+ com.cpop
+ Cpop-Core
+ ${cpop.version}
+
com.mybatis-flex
@@ -81,6 +95,12 @@
easyexcel
${easyexcel.version}
+
+
+ org.apache.commons
+ commons-compress
+ ${commons-compress.version}
+