|
|
@@ -0,0 +1,238 @@
|
|
|
+package com.shkpr.iot.common.auth;
|
|
|
+
|
|
|
+import com.shkpr.iot.common.core.domain.base.Result;
|
|
|
+import com.shkpr.iot.common.core.util.ResponseUtil;
|
|
|
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
|
|
+import org.springframework.context.MessageSource;
|
|
|
+import org.springframework.context.annotation.Bean;
|
|
|
+import org.springframework.context.annotation.Configuration;
|
|
|
+import org.springframework.context.support.ReloadableResourceBundleMessageSource;
|
|
|
+import org.springframework.http.HttpStatus;
|
|
|
+import org.springframework.security.authentication.AuthenticationManager;
|
|
|
+import org.springframework.security.authentication.ProviderManager;
|
|
|
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
|
|
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
|
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
|
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
|
|
+import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
|
|
|
+import org.springframework.security.config.http.SessionCreationPolicy;
|
|
|
+import org.springframework.security.core.Authentication;
|
|
|
+import org.springframework.security.core.AuthenticationException;
|
|
|
+import org.springframework.security.core.context.SecurityContextHolder;
|
|
|
+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.AnonymousAuthenticationFilter;
|
|
|
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
|
|
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|
|
+import org.springframework.security.web.util.matcher.RequestMatcher;
|
|
|
+import org.springframework.web.cors.CorsConfiguration;
|
|
|
+import org.springframework.web.cors.CorsConfigurationSource;
|
|
|
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
|
|
+
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
+import java.io.IOException;
|
|
|
+import java.util.Arrays;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Security配置
|
|
|
+ *
|
|
|
+ * @author 欧阳劲驰
|
|
|
+ * @since 0.0.1
|
|
|
+ */
|
|
|
+@EnableWebSecurity
|
|
|
+@EnableMethodSecurity
|
|
|
+@Configuration
|
|
|
+@EnableConfigurationProperties(AuthenticationProperties.class)
|
|
|
+public class SecurityConfig {
|
|
|
+ final
|
|
|
+ AuthenticationProperties properties;
|
|
|
+ final
|
|
|
+ CaptchaService captchaService;
|
|
|
+ final
|
|
|
+ TokenService tokenService;
|
|
|
+ final
|
|
|
+ UserDetailsService userDetailsService;
|
|
|
+
|
|
|
+ public SecurityConfig(AuthenticationProperties properties, TokenService tokenService, CaptchaService captchaService
|
|
|
+ , UserDetailsService userDetailsService) {
|
|
|
+ this.properties = properties;
|
|
|
+ this.captchaService = captchaService;
|
|
|
+ this.tokenService = tokenService;
|
|
|
+ this.userDetailsService = userDetailsService;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 国际化配置
|
|
|
+ *
|
|
|
+ * @return 消息策略
|
|
|
+ */
|
|
|
+ @Bean
|
|
|
+ MessageSource messageSource() {
|
|
|
+ ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
|
|
|
+ messageSource.addBasenames("classpath:org/springframework/security/messages_zh_CN");
|
|
|
+ return messageSource;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 密码编码器配置
|
|
|
+ * <p>目前是委托spring处理密码编码器</p>
|
|
|
+ *
|
|
|
+ * @return 密码编码器
|
|
|
+ */
|
|
|
+ @Bean
|
|
|
+ PasswordEncoder passwordEncoder() {
|
|
|
+ return PasswordEncoderFactories.createDelegatingPasswordEncoder();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 跨域配置
|
|
|
+ *
|
|
|
+ * @return 跨域配置
|
|
|
+ */
|
|
|
+ private CorsConfigurationSource corsConfigurationSource() {
|
|
|
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
|
|
+ CorsConfiguration corsConfiguration = new CorsConfiguration();
|
|
|
+ //跨域方法
|
|
|
+ corsConfiguration.addAllowedMethod("GET");
|
|
|
+ corsConfiguration.addAllowedMethod("POST");
|
|
|
+ //公开返回头,便于刷新token
|
|
|
+ corsConfiguration.addExposedHeader(properties.getTokenName());
|
|
|
+ //同源配置
|
|
|
+ corsConfiguration.addAllowedOrigin("*");
|
|
|
+ //header配置
|
|
|
+ corsConfiguration.addAllowedHeader("*");
|
|
|
+ //跨域路径
|
|
|
+ source.registerCorsConfiguration("/**", corsConfiguration);
|
|
|
+ return source;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 认证管理器配置
|
|
|
+ * <p>使用的提供器管理,后续Provider都可以修改该方法</p>
|
|
|
+ *
|
|
|
+ * @return 认证管理器
|
|
|
+ */
|
|
|
+ public AuthenticationManager authenticationManager() {
|
|
|
+ return new ProviderManager(Arrays.asList(
|
|
|
+ new PasswordAuthenticationProvider(userDetailsService),
|
|
|
+ new PhoneAuthenticationProvider(userDetailsService)
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 密码认证过滤器配置
|
|
|
+ */
|
|
|
+ PasswordAuthenticationFilter passwordAuthenticationFilter() {
|
|
|
+ PasswordAuthenticationFilter passwordAuthenticationFilter = new PasswordAuthenticationFilter(authenticationManager());
|
|
|
+ //认证成功
|
|
|
+ passwordAuthenticationFilter.setAuthenticationSuccessHandler(this::authSuccessHandler);
|
|
|
+ //认证失败
|
|
|
+ passwordAuthenticationFilter.setAuthenticationFailureHandler(this::authFailureHandler);
|
|
|
+ return passwordAuthenticationFilter;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 手机认证过滤器配置
|
|
|
+ */
|
|
|
+ PhoneAuthenticationFilter phoneAuthenticationFilter() {
|
|
|
+ PhoneAuthenticationFilter phoneAuthenticationFilter = new PhoneAuthenticationFilter(authenticationManager());
|
|
|
+ //认证成功
|
|
|
+ phoneAuthenticationFilter.setAuthenticationSuccessHandler(this::authSuccessHandler);
|
|
|
+ //认证失败
|
|
|
+ phoneAuthenticationFilter.setAuthenticationFailureHandler(this::authFailureHandler);
|
|
|
+ return phoneAuthenticationFilter;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Security过滤链配置
|
|
|
+ *
|
|
|
+ * @param http http
|
|
|
+ * @return Security过滤链
|
|
|
+ * @throws Exception exception
|
|
|
+ */
|
|
|
+ @Bean
|
|
|
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|
|
+ http
|
|
|
+ //过滤器配置
|
|
|
+ .addFilterBefore(new CaptchaFilter(properties, captchaService), UsernamePasswordAuthenticationFilter.class)
|
|
|
+ .addFilterAt(phoneAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
|
|
|
+ .addFilterAt(passwordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
|
|
|
+ .addFilterBefore(new TokenFilter(properties, tokenService), AnonymousAuthenticationFilter.class)
|
|
|
+ //认证配置
|
|
|
+ .authenticationProvider(new PasswordAuthenticationProvider(userDetailsService))
|
|
|
+ .authenticationProvider(new PhoneAuthenticationProvider(userDetailsService))
|
|
|
+ //权限配置
|
|
|
+ .authorizeHttpRequests(authorize -> authorize
|
|
|
+ .requestMatchers(properties.getPublicUrl().stream()
|
|
|
+ .map(AntPathRequestMatcher::new)
|
|
|
+ .toArray(RequestMatcher[]::new)).permitAll()
|
|
|
+ .anyRequest().authenticated()
|
|
|
+ )
|
|
|
+ //跨域防伪配置
|
|
|
+ .csrf(AbstractHttpConfigurer::disable)
|
|
|
+ //跨域配置
|
|
|
+ .cors(cors -> cors
|
|
|
+ .configurationSource(corsConfigurationSource()))
|
|
|
+ //session认证配置
|
|
|
+ .sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer
|
|
|
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
|
|
+ //header配置
|
|
|
+ .headers(header -> header
|
|
|
+ .frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
|
|
|
+ //认证配置
|
|
|
+ .formLogin(AbstractHttpConfigurer::disable)
|
|
|
+ //登出处理器
|
|
|
+ .logout(logout -> logout
|
|
|
+ .logoutUrl(AuthenticationMetadata.LOGOUT_URI)
|
|
|
+ .clearAuthentication(true)
|
|
|
+ .addLogoutHandler((request, response, authentication) -> {
|
|
|
+
|
|
|
+ })
|
|
|
+ .logoutSuccessHandler((request, response, authentication) ->
|
|
|
+ ResponseUtil.writeJson(response, Result.okMsg("退出成功"))
|
|
|
+ ).permitAll()
|
|
|
+ )
|
|
|
+ //异常处理器
|
|
|
+ .exceptionHandling(exception -> exception
|
|
|
+ //访问拒绝
|
|
|
+ .accessDeniedHandler((request, response, accessDeniedException) ->
|
|
|
+ ResponseUtil.writeJson(response, new Result<>(HttpStatus.FORBIDDEN.value(),
|
|
|
+ accessDeniedException.getMessage())))
|
|
|
+ //未认证
|
|
|
+ .authenticationEntryPoint((request, response, authenticationException) ->
|
|
|
+ ResponseUtil.writeJson(response, new Result<>(HttpStatus.UNAUTHORIZED.value(),
|
|
|
+ authenticationException.getMessage())))
|
|
|
+ );
|
|
|
+ return http.build();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 认证成功处理器
|
|
|
+ *
|
|
|
+ * @param request request
|
|
|
+ * @param response response
|
|
|
+ * @param authentication 认证信息
|
|
|
+ * @throws IOException io异常
|
|
|
+ */
|
|
|
+ private void authSuccessHandler(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
|
|
|
+ SecurityContextHolder.getContext().setAuthentication(authentication);
|
|
|
+ //生成token
|
|
|
+ String token = tokenService.generateToken(authentication);
|
|
|
+ ResponseUtil.writeJson(response, Result.ok("认证成功", new LoginVo(token)));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 认证失败处理器
|
|
|
+ *
|
|
|
+ * @param request request
|
|
|
+ * @param response response
|
|
|
+ * @throws IOException io异常
|
|
|
+ */
|
|
|
+ private void authFailureHandler(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
|
|
|
+ ResponseUtil.writeJson(response, new Result<>(HttpStatus.FORBIDDEN.value(), exception.getMessage()));
|
|
|
+ }
|
|
|
+}
|