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.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;
|
// 2023-07-11
|
this.userLoginCache = userLoginCache;
|
}
|
|
@Bean
|
public SecurityProperties securityProperties(){
|
return new SecurityProperties();
|
}
|
|
/**
|
* HttpSecurity:忽略 antMatchers 中使用的端点的身份验证,其他安全功能将生效。<br></br>
|
* 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<String> 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<FilterSecurityInterceptor>(){
|
@Override
|
public <O extends FilterSecurityInterceptor> 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<S_user_core> 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<CaptchaResult> smsCaptchaProvider
|
, CaptchaProvider<CaptchaResult> imageCaptchaProvider
|
, JigsawCaptchaProvider jigsawCaptchaProvider){
|
MobilePassCaptchaLoginCallback loginCallback = new MobilePassCaptchaLoginCallback();
|
loginCallback.setTokenGenerator(tokenGenerator);
|
loginCallback.setPasswordEncoder(passwordEncoder);
|
// 配置组装验证码提供者,2023-12-28
|
CaptchaProvider<CaptchaResult> captchaProvider = SecurityConfigUtils
|
.findCaptchaProvider(securityProperties.getLoginCaptchaUserPass()
|
, smsCaptchaProvider, imageCaptchaProvider, jigsawCaptchaProvider);
|
loginCallback.setCaptchaProvider(captchaProvider);
|
return loginCallback;
|
}
|
|
/**
|
* 账号、密码(加密方式)登录的回调实现,不包含:用户验证码。<p>适合在移动端使用</p>
|
* @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<CaptchaResult> smsCaptchaProvider
|
, CaptchaProvider<CaptchaResult> 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<CaptchaResult> 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<String> captchaCacheProvider, SecurityProperties securityProperties
|
, CaptchaProvider<CaptchaResult> smsCaptchaProvider
|
, CaptchaProvider<CaptchaResult> 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<CaptchaResult> captchaProvider = SecurityConfigUtils
|
.findCaptchaProvider(securityProperties.getLoginCaptchaSmsCode()
|
, smsCaptchaProvider, imageCaptchaProvider, jigsawCaptchaProvider);
|
smsCodeLoginCallback.setCaptchaProvider(captchaProvider);
|
return smsCodeLoginCallback;
|
}
|
|
/**
|
* 解决同源跨域问题
|
* @return
|
*/
|
@Bean
|
public FilterRegistrationBean<CorsFilter> 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<CorsFilter> 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;
|
}
|
}
|