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 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} +