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