diff --git a/src/main/java/com/zsc/edu/dify/framework/security/CustomAccessDeniedHandler.java b/src/main/java/com/zsc/edu/dify/framework/security/CustomAccessDeniedHandler.java index 267cc64..92da44e 100644 --- a/src/main/java/com/zsc/edu/dify/framework/security/CustomAccessDeniedHandler.java +++ b/src/main/java/com/zsc/edu/dify/framework/security/CustomAccessDeniedHandler.java @@ -8,12 +8,14 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.AllArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authorization.AuthorizationDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.csrf.MissingCsrfTokenException; import org.springframework.stereotype.Component; import java.io.IOException; import java.time.LocalDateTime; +import java.util.Map; /** * @author harry_yao @@ -27,7 +29,7 @@ public class CustomAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex) throws IOException, ServletException { - ex.printStackTrace(); +// ex.printStackTrace(); response.setContentType("application/json;charset=utf-8"); ExceptionResult result; if (ex instanceof MissingCsrfTokenException) { @@ -36,6 +38,11 @@ public class CustomAccessDeniedHandler implements AccessDeniedHandler { response.setStatus(HttpStatus.UNAUTHORIZED.value()); result = new ExceptionResult("凭证已过期,请重新登录", HttpStatus.UNAUTHORIZED.value(), LocalDateTime.now()); + } else if (ex instanceof AuthorizationDeniedException) { + // 403 + response.setStatus(HttpStatus.FORBIDDEN.value()); + result = new ExceptionResult("当前账号已在其他设备登录,请先退出再尝试登录", HttpStatus.FORBIDDEN.value(), + LocalDateTime.now()); } else { // 403 response.setStatus(HttpStatus.FORBIDDEN.value()); diff --git a/src/main/java/com/zsc/edu/dify/framework/security/CustomSessionInformationExpiredStrategy.java b/src/main/java/com/zsc/edu/dify/framework/security/CustomSessionInformationExpiredStrategy.java index 0dc4b20..4c59a9e 100644 --- a/src/main/java/com/zsc/edu/dify/framework/security/CustomSessionInformationExpiredStrategy.java +++ b/src/main/java/com/zsc/edu/dify/framework/security/CustomSessionInformationExpiredStrategy.java @@ -24,7 +24,7 @@ public class CustomSessionInformationExpiredStrategy implements SessionInformati response.setContentType("application/json;charset=utf-8"); ObjectMapper objectMapper = new ObjectMapper(); response.getWriter().print(objectMapper.writeValueAsString(Map.of( - "msg", "会话已过期(有可能是您同时登录了太多的太多的客户端)", + "msg", "会话已过期(有可能是您同时登录了太多的客户端)", "code", HttpStatus.UNAUTHORIZED.value(), "timestamp", LocalDateTime.now() ))); diff --git a/src/main/java/com/zsc/edu/dify/framework/security/SecurityBeanConfig.java b/src/main/java/com/zsc/edu/dify/framework/security/SecurityBeanConfig.java deleted file mode 100644 index 9a19c9a..0000000 --- a/src/main/java/com/zsc/edu/dify/framework/security/SecurityBeanConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.zsc.edu.dify.framework.security; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.core.session.SessionRegistry; -import org.springframework.security.core.session.SessionRegistryImpl; -import org.springframework.security.crypto.factory.PasswordEncoderFactories; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.session.HttpSessionEventPublisher; - -/** - * @author harry_yao - */ -@Configuration -public class SecurityBeanConfig { - @Bean - public PasswordEncoder passwordEncoder() { - return PasswordEncoderFactories.createDelegatingPasswordEncoder(); - } - - @Bean - public SessionRegistry sessionRegistry() { - return new SessionRegistryImpl(); - } - - @Bean - public HttpSessionEventPublisher httpSessionEventPublisher() { - return new HttpSessionEventPublisher(); - } -} diff --git a/src/main/java/com/zsc/edu/dify/framework/security/SpringSecurityConfig.java b/src/main/java/com/zsc/edu/dify/framework/security/SpringSecurityConfig.java index 202872e..9f4dccb 100644 --- a/src/main/java/com/zsc/edu/dify/framework/security/SpringSecurityConfig.java +++ b/src/main/java/com/zsc/edu/dify/framework/security/SpringSecurityConfig.java @@ -12,11 +12,15 @@ import org.springframework.security.config.annotation.method.configuration.Enabl import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.core.session.SessionRegistry; +import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; +import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy; import org.springframework.security.web.context.HttpSessionSecurityContextRepository; import org.springframework.security.web.session.HttpSessionEventPublisher; @@ -35,31 +39,51 @@ public class SpringSecurityConfig { private final CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler; private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; private final CustomAccessDeniedHandler customAccessDeniedHandler; - private final SessionRegistry sessionRegistry; - private final SecurityBeanConfig securityBeanConfig; +// private final SessionRegistry sessionRegistry; private final CustomSessionInformationExpiredStrategy customSessionInformationExpiredStrategy; @Resource private final DataSource dataSource; -// @Bean -// public BCryptPasswordEncoder bCryptPasswordEncoder() { -// return new BCryptPasswordEncoder(); -// }; + @Bean + public HttpSessionSecurityContextRepository httpSessionSecurityContextRepository() { + return new HttpSessionSecurityContextRepository(); + } + + @Bean + public ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy() { + ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry()); + concurrentSessionControlAuthenticationStrategy.setMaximumSessions(1); + return concurrentSessionControlAuthenticationStrategy; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } + + @Bean + public SessionRegistry sessionRegistry() { + return new SessionRegistryImpl(); + } + + @Bean + public HttpSessionEventPublisher httpSessionEventPublisher() { + return new HttpSessionEventPublisher(); + } @Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl(); tokenRepository.setDataSource(dataSource); return tokenRepository; - } @Bean AuthenticationManager authenticationManager() { DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); daoAuthenticationProvider.setUserDetailsService(userDetailsService); - daoAuthenticationProvider.setPasswordEncoder(securityBeanConfig.passwordEncoder()); + daoAuthenticationProvider.setPasswordEncoder(passwordEncoder()); return new ProviderManager(daoAuthenticationProvider); } @@ -71,7 +95,8 @@ public class SpringSecurityConfig { filter.setFilterProcessesUrl("/api/rest/user/login"); filter.setAuthenticationManager(authenticationManager()); // 将登录后的请求信息保存到Session中,不然会报null - filter.setSecurityContextRepository(new HttpSessionSecurityContextRepository()); +// filter.setSessionAuthenticationStrategy(concurrentSessionControlAuthenticationStrategy()); +// filter.setSecurityContextRepository(httpSessionSecurityContextRepository()); return filter; } @@ -86,43 +111,6 @@ public class SpringSecurityConfig { .requestMatchers("/v1/**").authenticated() .requestMatchers("/api/**").authenticated() ) - // 不用注解,直接通过判断路径实现动态访问权限 -// .requestMatchers("/api/**").access((authentication, object) -> { -// //表示请求的 URL 地址和数据库的地址是否匹配上了 -// boolean isMatch = false; -// //获取当前请求的 URL 地址 -// String requestURI = object.getRequest().getRequestURI(); -// List menuWithRole = menuService.getMenuWithRole(); -// for (MenuWithRoleVO m : menuWithRole) { -// AntPathMatcher antPathMatcher = new AntPathMatcher(); -// if (antPathMatcher.match(m.getUrl(), requestURI)) { -// isMatch = true; -// //说明找到了请求的地址了 -// //这就是当前请求需要的角色 -// List roles = m.getRoles(); -// //获取当前登录用户的角色 -// Collection authorities = authentication.get().getAuthorities(); -// for (GrantedAuthority authority : authorities) { -// for (Role role : roles) { -// if (authority.getAuthority().equals(role.getName())) { -// //说明当前登录用户具备当前请求所需要的角色 -// return new AuthorizationDecision(true); -// } -// } -// } -// } -// } -// if (!isMatch) { -// //说明请求的 URL 地址和数据库的地址没有匹配上,对于这种请求,统一只要登录就能访问 -// if (authentication.get() instanceof AnonymousAuthenticationToken) { -// return new AuthorizationDecision(false); -// } else { -// //说明用户已经认证了 -// return new AuthorizationDecision(true); -// } -// } -// return new AuthorizationDecision(false); -// })) .addFilterAt(jsonAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) .formLogin(form -> form .loginPage("/user/login") @@ -140,11 +128,13 @@ public class SpringSecurityConfig { .rememberMe(rememberMe -> rememberMe .userDetailsService(userDetailsService) .tokenRepository(persistentTokenRepository())) - .csrf(csrf -> csrf.ignoringRequestMatchers("v1/**","/api/internal/**", "/api/rest/user/logout","/api/rest/user/register")) + .csrf(csrf -> + csrf.ignoringRequestMatchers("v1/**","/api/internal/**", "/api/rest/user/logout","/api/rest/user/register")) .sessionManagement(session -> session .maximumSessions(1) - .sessionRegistry(sessionRegistry) - .expiredSessionStrategy(customSessionInformationExpiredStrategy)) - .build(); + .maxSessionsPreventsLogin(true) + .sessionRegistry(sessionRegistry()) +// .expiredSessionStrategy(customSessionInformationExpiredStrategy) + ).build(); } } diff --git a/src/main/java/com/zsc/edu/dify/framework/security/UserDetailsImpl.java b/src/main/java/com/zsc/edu/dify/framework/security/UserDetailsImpl.java index 6b38d42..13c9d57 100644 --- a/src/main/java/com/zsc/edu/dify/framework/security/UserDetailsImpl.java +++ b/src/main/java/com/zsc/edu/dify/framework/security/UserDetailsImpl.java @@ -12,6 +12,7 @@ import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -98,4 +99,34 @@ public class UserDetailsImpl implements UserDetails { return enableState; } + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + UserDetailsImpl that = (UserDetailsImpl) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id, username, password, enableState, name, dept, role, roles, authorities, permissions, dataScopeDeptIds, deptId, createId); + } + + @Override + public String toString() { + return "UserDetailsImpl{" + + "id=" + id + + ", username='" + username + '\'' + + ", password='" + password + '\'' + + ", enableState=" + enableState + + ", name='" + name + '\'' + + ", dept=" + dept + + ", role=" + role + + ", roles=" + roles + + ", authorities=" + authorities + + ", permissions=" + permissions + + ", dataScopeDeptIds=" + dataScopeDeptIds + + ", deptId=" + deptId + + ", createId=" + createId + + '}'; + } } diff --git a/src/main/java/com/zsc/edu/dify/modules/dify/controller/SpiderController.java b/src/main/java/com/zsc/edu/dify/modules/dify/controller/SpiderController.java index d7319b5..51bafed 100644 --- a/src/main/java/com/zsc/edu/dify/modules/dify/controller/SpiderController.java +++ b/src/main/java/com/zsc/edu/dify/modules/dify/controller/SpiderController.java @@ -19,7 +19,7 @@ import java.util.Collections; public class SpiderController { @Resource - private ObjectMapper objectMapper; + private ObjectMapper objectMapper; private static final String SPIDER_URL = "http://47.112.173.8:6806/api/v1"; private static final String API_KEY = "77c068fd-d5b6-4c33-97d8-db5511a09b26";