package com.iplatform.security.config; import com.iplatform.base.UserCacheProvider; import com.iplatform.base.UserLoginCache; import com.iplatform.base.cache.MenuCacheProvider; import com.iplatform.base.captcha.JigsawCaptchaProvider; import com.iplatform.base.captcha.NoneCaptchaProvider; import com.iplatform.base.captcha.ThirdPartyCaptchaProvider; import com.iplatform.base.service.UserServiceImpl; import com.iplatform.base.util.UserUtils; import com.iplatform.core.PlatformConfiguration; import com.iplatform.model.po.S_user_core; import com.iplatform.security.DefaultAuthenticationFailureHandler; import com.iplatform.security.DefaultAuthenticationProvider; import com.iplatform.security.DefaultLogoutSuccessHandler; import com.iplatform.security.DefaultResourceLoaderProvider; import com.iplatform.security.DefaultUserDetailsService; import com.iplatform.security.FailedAuthenticationEntryPoint; import com.iplatform.security.JwtAuthenticationTokenFilter; import com.iplatform.security.callback.EncryptPasswordLoginCallback; import com.iplatform.security.callback.MobilePassCaptchaLoginCallback; import com.iplatform.security.callback.NoneCaptchaLoginCallback; import com.iplatform.security.callback.SmsCodeLoginCallback; import com.iplatform.security.callback.ThirdPartyLoginCallback; import com.iplatform.security.callback.WechatLoginCallback; import com.iplatform.security.event.RoleSecurityUpdateListener; import com.iplatform.security.util.SecurityConfigUtils; import com.walker.cache.CacheProvider; import com.walker.infrastructure.utils.StringUtils; import com.walker.web.CaptchaProvider; import com.walker.web.CaptchaResult; import com.walker.web.TokenGenerator; import com.walker.web.UserOnlineProvider; import com.walker.web.UserPrincipal; import com.walker.web.security.DefaultAccessDecisionManager; import com.walker.web.security.DefaultAccessDeniedHandler; import com.walker.web.security.DefaultSecurityMetadataSource; import com.walker.web.security.ResourceLoadProvider; import com.walker.web.token.JwtTokenGenerator; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.ExceptionHandlingDsl; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutFilter; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; import java.util.List; @Configuration public class WebSecurityConfig extends PlatformConfiguration { private MenuCacheProvider menuCacheProvider; private UserServiceImpl userService; private UserOnlineProvider userOnlineProvider; private UserCacheProvider userCacheProvider; private UserLoginCache userLoginCache; @Autowired public WebSecurityConfig(MenuCacheProvider menuCacheProvider , UserServiceImpl userService, UserOnlineProvider userOnlineProvider, UserCacheProvider userCacheProvider , UserLoginCache userLoginCache){ this.menuCacheProvider = menuCacheProvider; this.userService = userService; this.userOnlineProvider = userOnlineProvider; this.userCacheProvider = userCacheProvider; // 2023-07-11 this.userLoginCache = userLoginCache; } @Bean public SecurityProperties securityProperties(){ return new SecurityProperties(); } /** * HttpSecurity:忽略 antMatchers 中使用的端点的身份验证,其他安全功能将生效。

* WebSecurity:直接忽略也不会进行 CSRF xss等攻击保护。 * @param http * @return * @throws Exception */ /** * HttpSecurity:忽略 antMatchers 中使用的端点的身份验证,其他安全功能将生效。

* WebSecurity:直接忽略也不会进行 CSRF xss等攻击保护。 * @param http * @return * @throws Exception */ @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // 缓存 securityProperties 的结果,避免重复调用 SecurityProperties securityProperties = this.securityProperties(); DefaultUserDetailsService userDetailsService = userDetailsService(securityProperties, this.userCacheProvider); http.userDetailsService(userDetailsService); // CSRF禁用,因为不使用session // 注意:禁用CSRF需确保所有接口已通过其他方式保护 http.csrf(csrf -> csrf.disable()); // 禁用frameOptions以支持iframe嵌套 // 替换弃用的 headers() 方法 http.headers(headers -> headers.frameOptions(frameOptions -> frameOptions.disable())); // 禁用默认登录和HTTP Basic认证 http.formLogin(formLogin -> formLogin.disable()); // 异常处理配置 http.exceptionHandling(exceptionHandling -> exceptionHandling .authenticationEntryPoint(failedAuthenticationEntryPoint()) .accessDeniedHandler(this.accessDeniedHandler())); // 基于token,所以不需要session http.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); // 登出配置 http.logout(logout -> logout .logoutUrl("/logout") .logoutSuccessHandler(this.logoutSuccessHandler()) .permitAll()); // 配置匿名访问权限 configureAnonymousAccess(http, securityProperties); // 配置自定义认证提供者 http.authenticationProvider(this.authenticationProvider(userDetailsService, securityProperties)); // 所有请求都需要认证 http.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests.anyRequest().authenticated()); // 添加自定义动态拦截器 http.addFilterBefore(securityInterceptor(), FilterSecurityInterceptor.class); // 添加JWT认证过滤器 http.addFilterBefore(jwtAuthenticationTokenFilter(userDetailsService), UsernamePasswordAuthenticationFilter.class); // 配置跨域过滤器 configureCorsFilter(http, securityProperties); return http.build(); } /** * 配置匿名访问权限 */ private void configureAnonymousAccess(HttpSecurity http, SecurityProperties securityProperties) throws Exception { List anonymousList = securityProperties.getAnonymousList(); if (!CollectionUtils.isEmpty(anonymousList)) { http.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests .requestMatchers(anonymousList.toArray(new String[0])).permitAll()); } } /** * 配置跨域过滤器 */ private void configureCorsFilter(HttpSecurity http, SecurityProperties securityProperties) throws Exception { if (securityProperties.isCorsEnabled()) { CorsFilter corsFilter = this.corsFilter().getFilter(); if (corsFilter != null) { http.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class) .addFilterBefore(corsFilter, LogoutFilter.class); logger.info("跨域过滤器已启用"); } else { logger.warn("跨域过滤器未正确初始化"); } } else { logger.info("跨域过滤器未启用"); } } /** * 获取AuthenticationManager(认证管理器),登录时认证使用 * @param authenticationConfiguration * @return * @throws Exception */ @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean public DefaultUserDetailsService userDetailsService(SecurityProperties securityProperties, UserCacheProvider userCacheProvider){ DefaultUserDetailsService userDetailsService = new DefaultUserDetailsService(); userDetailsService.setUserService(this.userService); // userDetailsService.setPasswordEncoder(passwordEncoder()); userDetailsService.setSecurityProperties(securityProperties); userDetailsService.setMenuCacheProvider(this.menuCacheProvider); System.out.println("create UserDetailsService = " + userDetailsService); // 2023-05-07,这里把超级管理员初始化到缓存 UserPrincipal userPrincipal = UserUtils.createSupervisor(securityProperties.getSupervisorPassword()); userCacheProvider.putUser(userPrincipal.getUserInfo()); return userDetailsService; } @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } /** * 匿名用户无法访问异常返回 * @return */ @Bean public AuthenticationEntryPoint failedAuthenticationEntryPoint(){ return new FailedAuthenticationEntryPoint(); } /** * 已认证用户拒绝访问资源。 * @return */ @Bean public AccessDeniedHandler accessDeniedHandler(){ return new DefaultAccessDeniedHandler(); } @Bean public AuthenticationFailureHandler authenticationFailureHandler(){ return new DefaultAuthenticationFailureHandler(); } @Bean public LogoutSuccessHandler logoutSuccessHandler(){ DefaultLogoutSuccessHandler logoutSuccessHandler = new DefaultLogoutSuccessHandler(); logoutSuccessHandler.setUserOnlineProvider(this.userOnlineProvider); logoutSuccessHandler.setTokenGenerator(this.tokenGenerator()); logoutSuccessHandler.setUserLoginCache(this.userLoginCache); return logoutSuccessHandler; } @Bean public AccessDecisionManager accessDecisionManager(){ return new DefaultAccessDecisionManager(); } @Bean public DefaultSecurityMetadataSource securityMetadataSource(ResourceLoadProvider resourceLoadProvider){ // DefaultResourceLoaderProvider resourceLoaderProvider = new DefaultResourceLoaderProvider(); // resourceLoaderProvider.setMenuCacheProvider(this.menuCacheProvider); // resourceLoaderProvider.setPermitAccessUrls(this.securityProperties().getPermitList()); // resourceLoaderProvider.setAnonymousUrlList(this.securityProperties().getAnonymousList()); // resourceLoaderProvider.loadResource(); DefaultSecurityMetadataSource securityMetadataSource = new DefaultSecurityMetadataSource(); securityMetadataSource.setResourceLoaderProvider(resourceLoadProvider); return securityMetadataSource; } @Bean public FilterSecurityInterceptor securityInterceptor(){ // DefaultSecurityInterceptor securityInterceptor = new DefaultSecurityInterceptor(); FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor(); securityInterceptor.setSecurityMetadataSource(securityMetadataSource(this.resourceLoadProvider())); securityInterceptor.setAccessDecisionManager(accessDecisionManager()); logger.info("创建:FilterSecurityInterceptor"); return securityInterceptor; } /** * 定义token过滤器 * @return */ @Bean public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(DefaultUserDetailsService userDetailsService){ JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter = new JwtAuthenticationTokenFilter(); jwtAuthenticationTokenFilter.setTokenGenerator(tokenGenerator()); jwtAuthenticationTokenFilter.setUserOnlineProvider(this.userOnlineProvider); jwtAuthenticationTokenFilter.setDefaultUserDetailsService(userDetailsService); jwtAuthenticationTokenFilter.setSecurityProperties(this.securityProperties()); return jwtAuthenticationTokenFilter; } @Bean public TokenGenerator tokenGenerator(){ return new JwtTokenGenerator(); } /** * 微信登录回调实现。 * @param passwordEncoder * @param tokenGenerator * @return * @date 2023-07-27 */ @Bean public WechatLoginCallback wechatLoginCallback(PasswordEncoder passwordEncoder, TokenGenerator tokenGenerator){ WechatLoginCallback callback = new WechatLoginCallback(); callback.setTokenGenerator(tokenGenerator); callback.setPasswordEncoder(passwordEncoder); callback.setCaptchaProvider(new NoneCaptchaProvider()); return callback; } /** * 第三方的对接登录回调实现。(为预算一体化对接使用,更多需要抽象) * @param passwordEncoder * @param tokenGenerator * @return * @date 2023-07-03 */ @Bean public ThirdPartyLoginCallback thirdPartyLoginCallback(PasswordEncoder passwordEncoder, TokenGenerator tokenGenerator){ ThirdPartyLoginCallback callback = new ThirdPartyLoginCallback(); callback.setTokenGenerator(tokenGenerator); callback.setPasswordEncoder(passwordEncoder); callback.setCaptchaProvider(new ThirdPartyCaptchaProvider()); return callback; } /** * 移动端使用:用户名、密码(AES) + 普通验证码登录方式。 * @param passwordEncoder * @param tokenGenerator * @param securityProperties * @param smsCaptchaProvider * @param imageCaptchaProvider * @param jigsawCaptchaProvider * @return * @date 2023-12-28 */ @Bean public MobilePassCaptchaLoginCallback mobilePassCaptchaLoginCallback(PasswordEncoder passwordEncoder , TokenGenerator tokenGenerator, SecurityProperties securityProperties , CaptchaProvider smsCaptchaProvider , CaptchaProvider imageCaptchaProvider , JigsawCaptchaProvider jigsawCaptchaProvider){ MobilePassCaptchaLoginCallback loginCallback = new MobilePassCaptchaLoginCallback(); loginCallback.setTokenGenerator(tokenGenerator); loginCallback.setPasswordEncoder(passwordEncoder); // 配置组装验证码提供者,2023-12-28 CaptchaProvider captchaProvider = SecurityConfigUtils .findCaptchaProvider(securityProperties.getLoginCaptchaUserPass() , smsCaptchaProvider, imageCaptchaProvider, jigsawCaptchaProvider); loginCallback.setCaptchaProvider(captchaProvider); return loginCallback; } /** * 账号、密码(加密方式)登录的回调实现,不包含:用户验证码。

适合在移动端使用

* @param passwordEncoder * @param tokenGenerator * @return * @date 2023-03-20 */ @Bean public NoneCaptchaLoginCallback noneCaptchaPasswordLoginCallback(PasswordEncoder passwordEncoder , TokenGenerator tokenGenerator){ NoneCaptchaLoginCallback encryptPasswordLoginCallback = new NoneCaptchaLoginCallback(); encryptPasswordLoginCallback.setTokenGenerator(tokenGenerator); encryptPasswordLoginCallback.setPasswordEncoder(passwordEncoder); encryptPasswordLoginCallback.setCaptchaProvider(new NoneCaptchaProvider()); return encryptPasswordLoginCallback; } /** * 用户名、密码(明文)登录方式回调实现,包含:验证码组件,适合PC端使用。 * @param tokenGenerator * @param passwordEncoder * @return * @date 2023-01-26 */ @Bean public EncryptPasswordLoginCallback captchaPasswordLoginCallback(TokenGenerator tokenGenerator , PasswordEncoder passwordEncoder, SecurityProperties securityProperties , CaptchaProvider smsCaptchaProvider , CaptchaProvider imageCaptchaProvider , JigsawCaptchaProvider jigsawCaptchaProvider){ // SimplePasswordLoginCallback webLoginCallback = new SimplePasswordLoginCallback(); EncryptPasswordLoginCallback webLoginCallback = new EncryptPasswordLoginCallback(); webLoginCallback.setTokenGenerator(tokenGenerator); webLoginCallback.setUserOnlineProvider(this.userOnlineProvider); webLoginCallback.setUserService(this.userService); webLoginCallback.setPasswordEncoder(passwordEncoder); // 配置组装验证码提供者,2023-03-14 CaptchaProvider captchaProvider = SecurityConfigUtils .findCaptchaProvider(securityProperties.getLoginCaptchaUserPass() , smsCaptchaProvider, imageCaptchaProvider, jigsawCaptchaProvider); webLoginCallback.setCaptchaProvider(captchaProvider); return webLoginCallback; } /** * 短信验证码登录方式回调实现。 * @param tokenGenerator * @param captchaCacheProvider * @return * @date 2023-01-26 */ @Bean public SmsCodeLoginCallback smsCodeLoginCallback(TokenGenerator tokenGenerator , CacheProvider captchaCacheProvider, SecurityProperties securityProperties , CaptchaProvider smsCaptchaProvider , CaptchaProvider imageCaptchaProvider , JigsawCaptchaProvider jigsawCaptchaProvider){ SmsCodeLoginCallback smsCodeLoginCallback = new SmsCodeLoginCallback(); smsCodeLoginCallback.setTokenGenerator(tokenGenerator); smsCodeLoginCallback.setUserOnlineProvider(this.userOnlineProvider); smsCodeLoginCallback.setUserService(this.userService); smsCodeLoginCallback.setCaptchaCacheProvider(captchaCacheProvider); // 配置组装验证码提供者,2023-03-14 CaptchaProvider captchaProvider = SecurityConfigUtils .findCaptchaProvider(securityProperties.getLoginCaptchaSmsCode() , smsCaptchaProvider, imageCaptchaProvider, jigsawCaptchaProvider); smsCodeLoginCallback.setCaptchaProvider(captchaProvider); return smsCodeLoginCallback; } /** * 解决同源跨域问题 * @return */ @Bean public FilterRegistrationBean corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); if(!this.securityProperties().isCorsEnabled()){ return new FilterRegistrationBean<>(new CorsFilter(source)); } // 对接口配置跨域设置 source.registerCorsConfiguration("/**", buildConfig()); System.out.println("配置跨域过滤器,this.securityProperties().isCorsEnabled() = true"); //有多个filter时此处设置改CorsFilter的优先执行顺序 FilterRegistrationBean bean = new FilterRegistrationBean<>(new CorsFilter(source)); bean.setOrder(Ordered.HIGHEST_PRECEDENCE); return bean; } private CorsConfiguration buildConfig() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOrigin("*"); corsConfiguration.addAllowedHeader("*"); corsConfiguration.addAllowedMethod("*"); corsConfiguration.addExposedHeader("*"); return corsConfiguration; } /** * 配置自定义认证提供者,自己实现密码认证细节,否则spring会默认密码比较,导致手机短信验证码作为密码比较失效。 * @param userDetailsService * @return * @date 2023-01-28 */ @Bean public DefaultAuthenticationProvider authenticationProvider(UserDetailsService userDetailsService , SecurityProperties securityProperties){ DefaultAuthenticationProvider authenticationProvider = new DefaultAuthenticationProvider(); authenticationProvider.setUserDetailsService(userDetailsService); System.out.println("isAllowPcUserAccessApp = " + securityProperties.isAllowPcUserAccessApp()); authenticationProvider.setAllowPcUserAccessApp(securityProperties.isAllowPcUserAccessApp()); authenticationProvider.setHideUserNotFoundExceptions(false); // 必须设置,否则不会抛出该异常,2023-06-28 return authenticationProvider; } /** * 把资源提供者独立出来,可以复用。 * @return * @date 2023-05-07 */ @Bean public ResourceLoadProvider resourceLoadProvider(){ DefaultResourceLoaderProvider resourceLoaderProvider = new DefaultResourceLoaderProvider(); resourceLoaderProvider.setMenuCacheProvider(this.menuCacheProvider); resourceLoaderProvider.setPermitAccessUrls(this.securityProperties().getPermitList()); resourceLoaderProvider.setAnonymousUrlList(this.securityProperties().getAnonymousList()); resourceLoaderProvider.loadResource(); return resourceLoaderProvider; } /** * 角色权限变更后,处理通知,并重新加载资源信息。 * @param resourceLoadProvider * @return * @date 2023-05-07 */ @Bean public RoleSecurityUpdateListener roleSecurityUpdateListener(ResourceLoadProvider resourceLoadProvider){ RoleSecurityUpdateListener listener = new RoleSecurityUpdateListener(); listener.setResourceLoaderProvider(resourceLoadProvider); return listener; } }