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.security.SystemLogMan; 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.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.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; SystemLogMan.getInstance().checkMan(); // 2023-07-11 this.userLoginCache = userLoginCache; } @Bean public SecurityProperties securityProperties(){ return new SecurityProperties(); } /** * HttpSecurity:忽略 antMatchers 中使用的端点的身份验证,其他安全功能将生效。

* WebSecurity:直接忽略也不会进行 CSRF xss等攻击保护。 * @param http * @return * @throws Exception */ @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { DefaultUserDetailsService userDetailsService = userDetailsService(this.securityProperties(), this.userCacheProvider); http.userDetailsService(userDetailsService); // CSRF禁用,因为不使用session http.csrf().disable(); // ??? http.headers().frameOptions().disable(); // 登录行为由自己实现,参考 AuthController#login http.formLogin().disable().httpBasic().disable(); // 匿名资源访问权限,返回无权限提示接口 http.exceptionHandling().authenticationEntryPoint(failedAuthenticationEntryPoint()) // 已认证用户无权限访问配置 .accessDeniedHandler(this.accessDeniedHandler()) .and() // 基于token,所以不需要session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // http.formLogin().loginProcessingUrl("/login") // .failureHandler(this.authenticationFailureHandler()); // 注意:这里不能配置上面的登录,否则就不会执行自己实现的/login方法。2022-11-11 http.logout().logoutUrl("/logout").logoutSuccessHandler(this.logoutSuccessHandler()).permitAll(); // 匿名访问集合,2022-11-07 List anonymousList = this.securityProperties().getAnonymousList(); if(!StringUtils.isEmptyList(anonymousList)){ http.authorizeHttpRequests().antMatchers(anonymousList.toArray(new String[]{})).permitAll(); } // http.authorizeHttpRequests().antMatchers("/login", "/register", "/captchaImage", "/test/**").permitAll(); // http.authorizeHttpRequests().antMatchers("/static/**", "/test/**").permitAll(); // http.authorizeHttpRequests().antMatchers("/security/**").hasAuthority("query_user"); // 2023-03-21 注释掉,调试activiti7时发现和下面重复, // http.addFilterBefore(securityInterceptor(), FilterSecurityInterceptor.class); /*http.authorizeHttpRequests().withObjectPostProcessor(new ObjectPostProcessor(){ @Override public O postProcess(O object) { object.setAccessDecisionManager(accessDecisionManager());//决策管理器 object.setSecurityMetadataSource(securityMetadataSource());//安全元数据源 return object; } });*/ // 2023-01-28 配置自定义认证提供者(密码验证用) http.authenticationProvider(this.authenticationProvider(userDetailsService, securityProperties())); // 所有请求都需要认证 http.authorizeHttpRequests().anyRequest().authenticated(); // 使用自定义动态拦截器,拦截所有权限请求,2022-11-02 http.addFilterBefore(securityInterceptor(), FilterSecurityInterceptor.class); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // token拦截过滤器,2022-11-02 // 必须在这里添加拦截,不能放在'FilterSecurityInterceptor'之后,因为如果放在之后,那么就无法获得用户信息,从而无法 // 获得用户所具有的权限角色集合:roleIdList。2022-11-14(2) http.addFilterBefore(jwtAuthenticationTokenFilter(userDetailsService), UsernamePasswordAuthenticationFilter.class); // http.addFilterBefore(jwtAuthenticationTokenFilter(), DefaultAuthenticationFilter.class); // 尝试让jwt在URL权限之后才拦截, 2022-11-14(1) // 注意:以上 UsernamePasswordAuthenticationFilter 需要去掉才能生效 // http.addFilterAfter(jwtAuthenticationTokenFilter(), FilterSecurityInterceptor.class); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if(this.securityProperties().isCorsEnabled()){ // 解决跨域过滤器,2022-11-06 http.addFilterBefore(this.corsFilter().getFilter(), JwtAuthenticationTokenFilter.class); // 未知?2022-11-11 http.addFilterBefore(this.corsFilter().getFilter(), LogoutFilter.class); } else { System.out.println("不添加跨域过滤器: "); } return http.build(); } /** * 获取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; } }