All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.yizzuide.milkomeda.crust.CrustConfigurerAdapter Maven / Gradle / Ivy

/*
 * Copyright (c) 2021 yizzuide All rights Reserved.
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package com.github.yizzuide.milkomeda.crust;

import com.github.yizzuide.milkomeda.hydrogen.uniform.ResultVO;
import com.github.yizzuide.milkomeda.hydrogen.uniform.UniformHandler;
import com.github.yizzuide.milkomeda.hydrogen.uniform.UniformResult;
import com.github.yizzuide.milkomeda.universe.context.ApplicationContextHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.AuthenticationException;
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.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.util.CollectionUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

/**
 * CrustConfigurerAdapter
 * Spring Security配置器适配器
 *
 * @see org.springframework.security.web.session.SessionManagementFilter
 * @see org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer
 * @author yizzuide
 * @since 1.14.0
 * @version 3.11.2
 * 
* Create at 2019/11/11 18:25 */ public class CrustConfigurerAdapter extends WebSecurityConfigurerAdapter { @Autowired private CrustProperties props; @Autowired(required = false) private BCryptPasswordEncoder passwordEncoder; @Autowired private ApplicationContextHolder applicationContextHolder; @Override public void configure(AuthenticationManagerBuilder auth) { // 使用继承自DaoAuthenticationProvider CrustAuthenticationProvider authenticationProvider = new CrustAuthenticationProvider(props, passwordEncoder); // 扩展配置(UserDetailsService、PasswordEncoder) configureProvider(authenticationProvider); // 添加自定义身份验证组件 auth.authenticationProvider(authenticationProvider); } @Override protected void configure(HttpSecurity http) throws Exception { List allowURLs = new ArrayList<>(props.getPermitURLs()); // 登录 allowURLs.add(props.getLoginUrl()); // 额外添加的排除项 if (!CollectionUtils.isEmpty(props.getAdditionPermitUrls())) { allowURLs.addAll(props.getAdditionPermitUrls()); } // 标记匿名访问 // Find URL method map Map handlerMethodMap = applicationContextHolder.getApplicationContext() .getBean(RequestMappingHandlerMapping.class).getHandlerMethods(); Set anonUrls = new HashSet<>(); for (Map.Entry infoEntry : handlerMethodMap.entrySet()) { HandlerMethod handlerMethod = infoEntry.getValue(); // Has `CrustAnon` annotation on Method? CrustAnon crustAnon = handlerMethod.getMethodAnnotation(CrustAnon.class); if (null != crustAnon && null != infoEntry.getKey().getPatternsCondition()) { anonUrls.addAll(infoEntry.getKey().getPatternsCondition().getPatterns()); } } if (!CollectionUtils.isEmpty(anonUrls)) { allowURLs.addAll(anonUrls); } String[] permitAllMapping = allowURLs.toArray(new String[0]); // 通用失败处理器 DefaultFailureHandler failureHandler = new DefaultFailureHandler(this); http.csrf() .disable() .sessionManagement().sessionCreationPolicy(props.isStateless() ? SessionCreationPolicy.STATELESS : SessionCreationPolicy.IF_REQUIRED).and() .formLogin().disable() // 支持跨域,从CorsConfigurationSource中取跨域配置 .cors() .and() // 禁用iframe跨域 .headers() .frameOptions() .disable() .and() .authorizeRequests() // 跨域预检请求 .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 忽略的请求 .antMatchers(permitAllMapping).permitAll() // 其他所有请求需要身份认证 .anyRequest().authenticated(); // 如果是无状态方式 if (props.isStateless()) { allowURLs.add(props.getLoginUrl()); // 应用Token认证配置器,忽略需要匿名的请求 http.apply(new CrustAuthenticationConfigurer<>(() -> failureHandler)) .permissiveRequestUrls(allowURLs.toArray(new String[0])) .and() .logout() .logoutUrl(props.getLogoutUrl()) .addLogoutHandler((req, res, auth) -> CrustContext.invalidate()) .logoutSuccessHandler((req, res, auth) -> { ResultVO source = UniformResult.ok(null); UniformHandler.matchStatusToWrite(res, source.toMap()); }); } else { // 自定义session方式登录 http.httpBasic().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint(props.getLoginUrl())) .and() .sessionManagement() .sessionFixation().changeSessionId() .sessionAuthenticationErrorUrl(props.getLoginUrl()) .sessionAuthenticationFailureHandler(failureHandler).and() .logout() .logoutUrl(props.getLogoutUrl()) .addLogoutHandler((req, res, auth) -> CrustContext.invalidate()) .logoutSuccessUrl(props.getLoginUrl()) .invalidateHttpSession(true); } // 认证用户无权限访问处理 http.exceptionHandling().accessDeniedHandler(failureHandler) // 匿名用户无权限访问处理 .authenticationEntryPoint(failureHandler); // add others http configure additionalConfigure(http, props.isStateless()); } /** * 配置Web资源,资源根路径需要配置静态资源映射
* @param web WebSecurity */ @Override public void configure(WebSecurity web) { // 放开静态资源的限制 if (!CollectionUtils.isEmpty(props.getAllowStaticUrls())) { web.ignoring().antMatchers(HttpMethod.GET, props.getAllowStaticUrls().toArray(new String[0])); } } /** * Custom http configure. * @param http HttpSecurity * @param stateless true for session type, false for token type */ protected void additionalConfigure(HttpSecurity http, boolean stateless) { } /** * 自定义配置数据源提供及PasswordEncoder * @param provider DaoAuthenticationProvider * @see DaoAuthenticationProvider#setPasswordEncoder(PasswordEncoder) */ protected void configureProvider(DaoAuthenticationProvider provider) { provider.setHideUserNotFoundExceptions(false); provider.setUserDetailsService(userDetailsService()); } /** * Custom response for auth or access failure handler. * @param isAuth true if is auth type * @param request http request * @param response http response * @param exception AuthenticationException (auth type) | AccessDeniedException (access type) * @throws IOException if an input or output exception occurred * @since 3.14.0 */ protected void doFailure(boolean isAuth, HttpServletRequest request, HttpServletResponse response, RuntimeException exception) throws IOException { response.setStatus(HttpStatus.OK.value()); ResultVO source = UniformResult.error(props.getAuthFailCode(), exception.getMessage()); UniformHandler.matchStatusToWrite(response, source.toMap()); } @Bean(name = BeanIds.AUTHENTICATION_MANAGER) @Override public AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } @Bean protected CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Collections.singletonList("*")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "HEAD", "OPTION")); configuration.setAllowedHeaders(Collections.singletonList("*")); configuration.addExposedHeader(props.getRefreshTokenName()); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } static class DefaultFailureHandler implements AuthenticationFailureHandler, AccessDeniedHandler, AuthenticationEntryPoint { private final CrustConfigurerAdapter configurerAdapter; public DefaultFailureHandler(CrustConfigurerAdapter configurerAdapter) { this.configurerAdapter = configurerAdapter; } // 认证失败处理器 @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { configurerAdapter.doFailure(true, request, response, exception); } // 认证用户无权限访问拒绝处理器 @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { configurerAdapter.doFailure(false, request, response, accessDeniedException); } // 匿名用户无权限访问拒绝处理器 @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { configurerAdapter.doFailure(false, request, response, authException); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy