我们正在将我们的springboot应用程序迁移到springboot 3.x。在最新版本的springboot中,我看到spring-security升级到了版本6。
在这个版本中,WebSecurityConfigurerAdapter
被删除。
我有一个在spring-boot 2上编写的SamlConfiguation类。x.我必须定义SAMLWebSSOHoKProcessingFilter
和SAMLProcessingFilter
bean。
这些bean使用WebSecurityConfigurerAdapter
类中的authenticationManager()
方法。
如何使用Spring Security 6定义它们?我把它改成
@Bean
public AuthenticationManager authenticationManagerBean(AuthenticationConfiguration authConfiguration) throws Exception {
return authConfiguration.getAuthenticationManager();
}
但是当我启动应用程序调用任何API时,我得到错误10:20:55.995 ERROR org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/cms-service/webapi].[dispatcherServlet].log() @175 - Servlet.service() for servlet [dispatcherServlet] in context with path [/cms-service/webapi] threw exception [Filter execution threw an exception] with root cause java.lang.AbstractMethodError: Receiver class org.springframework.security.saml.metadata.MetadataGeneratorFilter does not define or inherit an implementation of the resolved method 'abstract void doFilter(jakarta.servlet.ServletRequest, jakarta.servlet.ServletResponse, jakarta.servlet.FilterChain)' of interface jakarta.servlet.Filter. at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.wrapFilter(ObservationFilterChainDecorator.java:240) at org.springframework.security.web.ObservationFilterChainDecorator$ObservationFilter.doFilter(ObservationFilterChainDecorator.java:227) at org.springframework.security.web.ObservationFilterChainDecorator$VirtualFilterChain.doFilter(ObservationFilterChainDecorator.java:137) at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
这是我完整的SAMLSecurityConfig
@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = {SAMLUserDetailsServiceImpl.class, Environment.class, SAMLConfig.class})
@Order(2)
public class SAMLSecurityConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(SAMLSecurityConfig.class);
private static final String LOGIN = "/login";
private final SAMLUserDetailsServiceImpl samlUserDetailsServiceImpl;
private final Environment environment;
private Timer backgroundTaskTimer;
private MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager;
private ApplicationEventPublisher applicationEventPublisher;
private AuthenticationConfiguration authenticationConfiguration;
@Autowired
public SAMLSecurityConfig(SAMLUserDetailsServiceImpl samlUserDetailsServiceImpl,
Environment environment, ApplicationEventPublisher applicationEventPublisher, AuthenticationConfiguration authenticationConfiguration) {
this.samlUserDetailsServiceImpl = samlUserDetailsServiceImpl;
this.environment = environment;
this.applicationEventPublisher = applicationEventPublisher;
this.authenticationConfiguration = authenticationConfiguration;
}
@Autowired
public SAMLConfig samlConfig;
public SAMLConfig config() {
samlConfig.setEntity();
return samlConfig;
}
// Initialization of OpenSAML library
@Bean
public static SAMLBootstrap samlBootstrap() {
return new CustomSAMLBootStrap();
}
@PostConstruct
public void init() {
this.backgroundTaskTimer = new Timer(true);
this.multiThreadedHttpConnectionManager = new MultiThreadedHttpConnectionManager();
}
@PreDestroy
public void destroy() {
this.backgroundTaskTimer.purge();
this.backgroundTaskTimer.cancel();
this.multiThreadedHttpConnectionManager.shutdown();
}
// Initialization of the velocity engine
@Bean
public VelocityEngine velocityEngine() {
VelocityEngine engine = new VelocityEngine();
Properties props = new Properties();
props.put("runtime.log.logsystem.class", "org.apache.velocity.runtime.log.SimpleLog4JLogSystem");
props.put("runtime.log.logsystem.log4j.category", "velocity");
props.put("runtime.log.logsystem.log4j.logger", "velocity");
props.put("UTF-8", "UTF-8");
props.put("resource.loader", "classpath");
props.put("classpath.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
engine.init(props);
return engine;
}
// XML parser pool needed for OpenSAML parsing
@Bean(initMethod = "initialize")
public StaticBasicParserPool parserPool() {
return new StaticBasicParserPool();
}
@Bean(name = "parserPoolHolder")
public ParserPoolHolder parserPoolHolder() {
return new ParserPoolHolder();
}
// Bindings, encoders and decoders used for creating and parsing messages
@Bean
public HttpClient httpClient() {
return new HttpClient(this.multiThreadedHttpConnectionManager);
}
// SAML Authentication Provider responsible for validating of received SAML
// messages
@Bean
public SAMLAuthenticationProvider samlAuthenticationProvider() {
SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider();
samlAuthenticationProvider.setUserDetails(samlUserDetailsServiceImpl);
samlAuthenticationProvider.setForcePrincipalAsString(false);
return samlAuthenticationProvider;
}
// Load Balancer Context Provider
@Bean
public SAMLContextProviderLB contextProvider() {
SAMLContextProviderLB samlContextProviderLB = new SAMLContextProviderLB();
samlContextProviderLB.setServerName(config().getSp().getServerName());
samlContextProviderLB.setIncludeServerPortInRequestURL(false);
samlContextProviderLB.setServerPort(config().getLb().getServerPort());
samlContextProviderLB.setScheme(config().getLb().getScheme());
samlContextProviderLB.setContextPath("/cms-service/webapi");
return samlContextProviderLB;
}
// Logger for SAML messages and events
@Bean
public SAMLDefaultLogger samlLogger() {
return new SAMLDefaultLogger();
}
// SAML 2.0 WebSSO Assertion Consumer
@Bean
public WebSSOProfileConsumer webSSOprofileConsumer() {
return new WebSSOProfileConsumerImpl();
}
// SAML 2.0 WebSSO Assertion Consumer
@Bean
public WebSSOProfileConsumer hokWebSSOprofileConsumer() {
WebSSOProfileConsumerImpl webSSOProfileConsumer = new WebSSOProfileConsumerImpl();
webSSOProfileConsumer.setResponseSkew(180);
webSSOProfileConsumer.setMaxAuthenticationAge(180);
return webSSOProfileConsumer;
}
// SAML 2.0 Holder-of-Key WebSSO Assertion Consumer
@Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOAssertionConsumer() {
return new WebSSOProfileConsumerHoKImpl();
}
// SAML 2.0 Web SSO profile
@Bean
public WebSSOProfile webSSOprofile() {
return new WebSSOProfileImpl();
}
// SAML 2.0 Holder-of-Key Web SSO profile
@Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() {
return new WebSSOProfileConsumerHoKImpl();
}
// SAML 2.0 ECP profile
@Bean
public WebSSOProfileECPImpl ecpProfile() {
return new WebSSOProfileECPImpl();
}
@Bean
public SingleLogoutProfile logoutProfile() {
return new SingleLogoutProfileImpl();
}
/*
* Central storage of cryptographic keys
*
* If your project does not require any keys you can replace the code below with:
* return new EmptyKeyManager();
*/
@Bean
public KeyManager keyManager() {
KeyStore keyStore = null;
try (FileInputStream fileInputStream = new FileInputStream(config().getKeyStore().getResource().getFile())) {
keyStore = KeyStore.getInstance("JKS");
keyStore.load(fileInputStream, config().getKeyStore().getPass().toCharArray());
} catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
LOGGER.debug("Error reading keystore file", e);
}
Map<String, String> passwords = new HashMap<>();
passwords.put(config().getKeyStore().getAlias(), config().getKeyStore().getPass());
assert keyStore != null;
return new JKSKeyManager(keyStore, passwords, config().getKeyStore().getAlias());
}
@Bean
public WebSSOProfileOptions defaultWebSSOProfileOptions() {
WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
webSSOProfileOptions.setIncludeScoping(false);
return webSSOProfileOptions;
}
// Entry point to initialize authentication, default values taken from
// properties file
@Bean
public SAMLEntryPoint samlEntryPoint() {
SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();
samlEntryPoint.setWebSSOprofile(webSSOprofile());
samlEntryPoint.setDefaultProfileOptions(defaultWebSSOProfileOptions());
httpClient().getState().clear();
return samlEntryPoint;
}
// Setup advanced info about metadata
@Bean
public ExtendedMetadata extendedMetadata() {
ExtendedMetadata extendedMetadata = new ExtendedMetadata();
extendedMetadata.setIdpDiscoveryEnabled(true);
extendedMetadata.setSignMetadata(false);
extendedMetadata.setEcpEnabled(true);
extendedMetadata.setSslHostnameVerification(ALLOW_ALL.toString());
return extendedMetadata;
}
// Filter automatically generates default SP metadata
@Bean
public MetadataGenerator metadataGenerator() {
MetadataGenerator metadataGenerator = new MetadataGenerator();
metadataGenerator.setEntityBaseURL(config().getSp().getEntityBaseURL());
metadataGenerator.setEntityId(config().getSp().getEntityId());
metadataGenerator.setExtendedMetadata(extendedMetadata());
metadataGenerator.setIncludeDiscoveryExtension(true);
metadataGenerator.setKeyManager(keyManager());
return metadataGenerator;
}
// The filter is waiting for connections on URL suffixed with filterSuffix
// and presents SP metadata there
@Bean
public MetadataDisplayFilter metadataDisplayFilter() {
return new MetadataDisplayFilter();
}
// Handler deciding where to redirect user after successful login
@Bean
public CustomAuthenticationSuccessHandler successRedirectHandler() {
CustomAuthenticationSuccessHandler successRedirectHandler =
new CustomAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl(config().getSp().getEntityBaseURL());
return successRedirectHandler;
}
// Handler deciding where to redirect user after failed login
@Bean
public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() {
SimpleUrlAuthenticationFailureHandler failureHandler =
new CustomAuthenticationFailureHandler();
failureHandler.setUseForward(false);
failureHandler.setDefaultFailureUrl(config().getSp().getEntityBaseURL());
return failureHandler;
}
@Bean
public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter() throws Exception {
SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter = new SAMLWebSSOHoKProcessingFilter();
samlWebSSOHoKProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());
samlWebSSOHoKProcessingFilter.setAuthenticationManager(authenticationManager());
samlWebSSOHoKProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
return samlWebSSOHoKProcessingFilter;
}
// Processing filter for WebSSO profile messages
@Bean
public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception {
SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();
samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager());
samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());
samlWebSSOProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
httpClient().getState().clear();
return samlWebSSOProcessingFilter;
}
@Bean
public MetadataGeneratorFilter metadataGeneratorFilter() {
return new MetadataGeneratorFilter(metadataGenerator());
}
// Handler for successful logout
@Bean
public SimpleUrlLogoutSuccessHandler successLogoutHandler() {
SimpleUrlLogoutSuccessHandler successLogoutHandler = new CustomSimpleUrlLogoutSuccessHandler();
successLogoutHandler.setDefaultTargetUrl(config().getSp().getEntityBaseURL());
return successLogoutHandler;
}
// Logout handler terminating local session
@Bean
public SecurityContextLogoutHandler logoutHandler() {
SecurityContextLogoutHandler logoutHandler =
new SecurityContextLogoutHandler();
logoutHandler.setInvalidateHttpSession(true);
logoutHandler.setClearAuthentication(true);
return logoutHandler;
}
// Filter processing incoming logout messages
// First argument determines URL user will be redirected to after successful
// global logout
@Bean
public SAMLLogoutProcessingFilter samlLogoutProcessingFilter() {
return new SAMLLogoutProcessingFilter(successLogoutHandler(),
logoutHandler());
}
// Overrides default logout processing filter with the one processing SAML
// messages
@Bean
public SAMLLogoutFilter samlLogoutFilter() {
return new SAMLLogoutFilter(successLogoutHandler(),
new LogoutHandler[]{logoutHandler()},
new LogoutHandler[]{logoutHandler()});
}
// Bindings
private ArtifactResolutionProfile artifactResolutionProfile() {
final ArtifactResolutionProfileImpl artifactResolutionProfile =
new ArtifactResolutionProfileImpl(httpClient());
artifactResolutionProfile.setProcessor(new SAMLProcessorImpl(soapBinding()));
return artifactResolutionProfile;
}
@Bean
public HTTPArtifactBinding artifactBinding(ParserPool parserPool, VelocityEngine velocityEngine) {
return new HTTPArtifactBinding(parserPool, velocityEngine, artifactResolutionProfile());
}
@Bean
public HTTPSOAP11Binding soapBinding() {
return new HTTPSOAP11Binding(parserPool());
}
@Bean
public HTTPPostBinding httpPostBinding() {
return new HTTPPostBinding(parserPool(), velocityEngine());
}
@Bean
public HTTPRedirectDeflateBinding httpRedirectDeflateBinding() {
return new HTTPRedirectDeflateBinding(parserPool());
}
@Bean
public HTTPSOAP11Binding httpSOAP11Binding() {
return new HTTPSOAP11Binding(parserPool());
}
@Bean
public HTTPPAOS11Binding httpPAOS11Binding() {
return new HTTPPAOS11Binding(parserPool());
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
// Processor
@Bean
public SAMLProcessorImpl processor() {
Collection<SAMLBinding> bindings = new ArrayList<>();
bindings.add(httpRedirectDeflateBinding());
bindings.add(httpPostBinding());
bindings.add(artifactBinding(parserPool(), velocityEngine()));
bindings.add(httpSOAP11Binding());
bindings.add(httpPAOS11Binding());
return new SAMLProcessorImpl(bindings);
}
/**
* Define the security filter chain in order to support SSO Auth by using SAML 2.0
*
* @return Filter chain proxy
*/
@Bean
public FilterChainProxy samlFilter() throws Exception {
List<SecurityFilterChain> chains = new ArrayList<>();
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"),
samlEntryPoint()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"),
samlLogoutFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/metadata/**"),
metadataDisplayFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"),
samlWebSSOProcessingFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSOHoK/**"),
samlWebSSOHoKProcessingFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"),
samlLogoutProcessingFilter()));
return new FilterChainProxy(chains);
}
/**
* Returns the authentication manager currently used by Spring.
* It represents a bean definition with the aim allow wiring from
* other classes performing the Inversion of Control (IoC).
*
*/
@Bean
public AuthenticationManager authenticationManagerBean(AuthenticationConfiguration authConfiguration) throws Exception {
return authConfiguration.getAuthenticationManager();
}
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
/**
* Defines the web based security configuration.
*
* @param http It allows configuring web based security for specific http requests.
*
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.httpBasic(httpBasic-> httpBasic.authenticationEntryPoint(samlEntryPoint()));
/*http
.httpBasic()
.authenticationEntryPoint(samlEntryPoint());*/
/*http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringAntMatchers("/saml/**");*/
http.csrf(csrf->csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringRequestMatchers("/saml/**"));
http
.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
.addFilterAfter(samlFilter(), BasicAuthenticationFilter.class);
/*http
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/saml/logout"))
.deleteCookies("JSESSIONID")
.logoutSuccessUrl(config().getSp().getEntityBaseURL() + LOGIN);*/
http.logout(httpSecurityLogoutConfigurer -> httpSecurityLogoutConfigurer.logoutRequestMatcher(new AntPathRequestMatcher("/saml/logout"))
.deleteCookies("JSESSIONID")
.logoutSuccessUrl(config().getSp().getEntityBaseURL() + LOGIN));
/*http
.headers()
.frameOptions()
.disable();*/
http.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable));
/*http
.sessionManagement()
.sessionAuthenticationErrorUrl(config().getSp().getEntityBaseURL() + LOGIN)
.invalidSessionUrl(config().getSp().getEntityBaseURL() + LOGIN);*/
http.sessionManagement(sessionManagement -> sessionManagement.sessionAuthenticationErrorUrl(config().getSp().getEntityBaseURL() + LOGIN)
.invalidSessionUrl(config().getSp().getEntityBaseURL() + LOGIN));
/*http
.sessionManagement()
.maximumSessions(1)
.expiredUrl(config().getSp().getEntityBaseURL() + LOGIN);*/
http.sessionManagement(sessionManagement -> sessionManagement.maximumSessions(1)
.expiredUrl(config().getSp().getEntityBaseURL() + LOGIN));
/*http
.sessionManagement()
.sessionFixation()
.newSession();*/
http.sessionManagement(sessionManagement ->
sessionManagement.sessionFixation().newSession());
/*http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.NEVER);*/
http.sessionManagement(sessionManagement ->sessionManagement.sessionCreationPolicy(SessionCreationPolicy.NEVER));
permitEndpoints(http);
http.authenticationProvider(samlAuthenticationProvider());
return http.build();
}
/**
* Sets a custom authentication provider.
*
* @param auth SecurityBuilder used to create an AuthenticationManager.
*/
/*@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(samlAuthenticationProvider());
}*/
private void permitEndpoints(HttpSecurity http) throws Exception {
if (isSamlDisabled()) {
http
.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests.requestMatchers("/**").permitAll()
.anyRequest().authenticated());
// .requestMatchers("/**").permitAll()
//.anyRequest().authenticated();
} else {
http
.authorizeHttpRequests(authorizeHttpRequests ->
authorizeHttpRequests.requestMatchers("/saml/**").permitAll()
.requestMatchers("/error").permitAll().anyRequest().permitAll());
/*
.requestMatchers("/saml/**").permitAll()
.requestMatchers("/error").permitAll()
.anyRequest().permitAll();*/
}
}
private boolean isSamlDisabled() {
return Arrays.stream(environment.getActiveProfiles()).anyMatch(
env -> (env.equalsIgnoreCase("samlDisabled")));
}
public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
logger.debug("in onAuthenticationSuccess");
Cookie[] cookies = request.getCookies();
if(cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("XSRF-TOKEN")) {
cookie.setValue("");
cookie.setMaxAge(0);
cookie.setPath("/");
response.addCookie(cookie);
}
}
}
if(config().getSp().getEntityBaseURL().equalsIgnoreCase(this.getDefaultTargetUrl())) {
URLBuilder builder = new URLBuilder(request.getRequestURL().toString());
builder.setPath("/");
builder.setFragment("/login?SSO="+ UserPolicy.AUTH_TYPE_SAML);
builder.setPort(CmsUtil.getWebServerPort());
this.setDefaultTargetUrl(builder.buildURL());
}
request.changeSessionId();
super.onAuthenticationSuccess(request, response, authentication);
}
}
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
protected String defaultFailureUrl = null;
public String getDefaultFailureUrl() {
return defaultFailureUrl;
}
@Override
public void setDefaultFailureUrl(String defaultFailureUrl) {
super.setDefaultFailureUrl(defaultFailureUrl);
this.defaultFailureUrl = defaultFailureUrl;
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
logger.debug("in onAuthenticationFailure");
if(config().getSp().getEntityBaseURL().equalsIgnoreCase(this.getDefaultFailureUrl())) {
URLBuilder builder = new URLBuilder(request.getRequestURL().toString());
builder.setPath("/");
builder.setFragment("/login?SSO="+ UserPolicy.AUTH_TYPE_SAML);
builder.setPort(CmsUtil.getWebServerPort());
this.setDefaultFailureUrl(builder.buildURL());
}
super.onAuthenticationFailure(request, response, exception);
}
}
public class CustomSimpleUrlLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
if(config().getSp().getEntityBaseURL().equalsIgnoreCase(this.getDefaultTargetUrl())) {
URLBuilder builder = new URLBuilder(request.getRequestURL().toString());
builder.setPath("/");
builder.setFragment("/login");
builder.setPort(CmsUtil.getWebServerPort());
this.setDefaultTargetUrl(builder.buildURL());
}
super.onLogoutSuccess(request, response, authentication);
}
}
@Bean
@Qualifier("idp-keycloak")
public ExtendedMetadataDelegate keycloakExtendedMetadataProvider(Environment env)
throws MetadataProviderException {
String idpKeycloakMetadataURL = env.getRequiredProperty("keycloak.auth-server-url") + "/protocol/saml/descriptor";
HTTPMetadataProvider httpMetadataProvider = new HTTPMetadataProvider(
this.backgroundTaskTimer, httpClient(), idpKeycloakMetadataURL);
httpMetadataProvider.setParserPool(parserPool());
ExtendedMetadataDelegate extendedMetadataDelegate =
new ExtendedMetadataDelegate(httpMetadataProvider, extendedMetadata());
extendedMetadataDelegate.setMetadataTrustCheck(true);
extendedMetadataDelegate.setMetadataRequireSignature(false);
backgroundTaskTimer.purge();
return extendedMetadataDelegate;
}
@Bean
@Qualifier("metadata")
public CachingMetadataManager metadata(List<MetadataProvider> providers) throws MetadataProviderException {
providers.remove(0);
return new CachingMetadataManager(providers);
}
}
请指示。
1条答案
按热度按时间qf9go6mv1#
您是否尝试在安全配置类中声明
authenticationManager
Bean
?大概是这样的:
UPDATE:如果无法通过方法param完成,则使用上面提供的代码即可。