SpringSecurity重写DaoAuthenticationProvider问题

问题描述

MyDaoAuthenticationProvider重写DaoAuthenticationProvider中additionalAuthenticationChecks方法后,自定义验证密码错误后,抛出BadCredentialsException异常,代码会继续执行,会执行DaoAuthenticationProvider中的additionalAuthenticationChecks发放,导致自定义抛出的BadCredentialsException的异常无用。

public class MyDaoAuthenticationProvider extends DaoAuthenticationProvider {

    private static final Logger log = LoggerFactory.getLogger(MyDaoAuthenticationProvider.class);

    @SneakyThrows
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            logger.debug("Authentication failed: no credentials provided");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }

        String presentedPassword = authentication.getCredentials().toString();

        if (!iscLoginJsoupReq(userDetails.getUsername(), presentedPassword)) {
            this.logger.debug("Failed to authenticate since password does not match stored value");
            throw new BadCredentialsException(this.messages
                    .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        }
    }
}

问题发现

在debug中,发现ProviderManager类中authenticate方法中,出现以下代码片段:

for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
						provider.getClass().getSimpleName(), ++currentPosition, size));
			}
			try {
				result = provider.authenticate(authentication);
				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException ex) {
				prepareException(ex, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw ex;
			}
			catch (AuthenticationException ex) {
				lastException = ex;
			}
		}

在getProviders()方法中,会获取到两个AuthenticationProvider,一个是自定义的MyDaoAuthenticationProvider,一个是默认的DaoAuthenticationProvider,在执行DaoAuthenticationProvider中的additionalAuthenticationChecks方法时,就会验证密码成功。

问题解决

在继承WebSecurityConfigurerAdapter的配置类中,在以下方法中:

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        ApplicationContext applicationContext = getApplicationContext();
        ConfigurableEnvironment environment = (ConfigurableEnvironment) applicationContext.getEnvironment();
        String name = environment.getProperty("spring.profiles.active");
        if (ACTIVE.equals(name)) {
            auth.authenticationProvider(myProvider());
        } else {
            auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
        }
    }
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());

这句代码会生成一个DaoAuthenticationProvider在容器中供使用,且容器中定义了几个Provider,在getProviders()方法中就会获取到几个,并循环执行。所以在使用自定义的DaoAuthenticationProvider是,需要注释auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());此代码片段。在不使用自定义的Provider时,需放开此代码片段。

遗留问题:重写DaoAuthenticationProvider,抛出的BadCredentialsException为什么会被屏蔽

自己的理解

DaoAuthenticationProvider和MyDaoAuthenticationProvider其实是功能一模一样的两个列,除了重写的additionalAuthenticationChecks()的实现类不一样,在执行DaoAuthenticationProvider中的additionalAuthenticationChecks时,会得到正确的result进行返回,在代码中,只要得到验证通过,得到正确的result,就会进行返回,代码如下:

@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		AuthenticationException parentException = null;
		Authentication result = null;
		Authentication parentResult = null;
		int currentPosition = 0;
		int size = this.providers.size();
		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
						provider.getClass().getSimpleName(), ++currentPosition, size));
			}
			try {
				result = provider.authenticate(authentication);
				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException ex) {
				prepareException(ex, authentication);
				// SEC-546: Avoid polling additional providers if auth failure is due to
				// invalid account status
				throw ex;
			}
			catch (AuthenticationException ex) {
				lastException = ex;
			}
		}
		if (result == null && this.parent != null) {
			// Allow the parent to try.
			try {
				parentResult = this.parent.authenticate(authentication);
				result = parentResult;
			}
			catch (ProviderNotFoundException ex) {
				// ignore as we will throw below if no other exception occurred prior to
				// calling parent and the parent
				// may throw ProviderNotFound even though a provider in the child already
				// handled the request
			}
			catch (AuthenticationException ex) {
				parentException = ex;
				lastException = ex;
			}
		}
		if (result != null) {
			if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
				// Authentication is complete. Remove credentials and other secret data
				// from authentication
				((CredentialsContainer) result).eraseCredentials();
			}
			// If the parent AuthenticationManager was attempted and successful then it
			// will publish an AuthenticationSuccessEvent
			// This check prevents a duplicate AuthenticationSuccessEvent if the parent
			// AuthenticationManager already published it
			if (parentResult == null) {
				this.eventPublisher.publishAuthenticationSuccess(result);
			}

			return result;
		}

		// Parent was null, or didn't authenticate (or throw an exception).
		if (lastException == null) {
			lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",
					new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));
		}
		// If the parent AuthenticationManager was attempted and failed then it will
		// publish an AbstractAuthenticationFailureEvent
		// This check prevents a duplicate AbstractAuthenticationFailureEvent if the
		// parent AuthenticationManager already published it
		if (parentException == null) {
			prepareException(lastException, authentication);
		}
		throw lastException;
	}

在执行DaoAuthenticationProvider中的result = provider.authenticate(authentication);时,会得到验证通过的result,在下面代码会返回result:

	if (result != null) {
			if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
				// Authentication is complete. Remove credentials and other secret data
				// from authentication
				((CredentialsContainer) result).eraseCredentials();
			}
			// If the parent AuthenticationManager was attempted and successful then it
			// will publish an AuthenticationSuccessEvent
			// This check prevents a duplicate AuthenticationSuccessEvent if the parent
			// AuthenticationManager already published it
			if (parentResult == null) {
				this.eventPublisher.publishAuthenticationSuccess(result);
			}

			return result;
		}

所以不会再代码最后一行,执行抛出throw lastException;这一行代码。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
可以通过实现`CookieSerializer`接口来重写Spring Security中的cookie。具体步骤如下: 1. 创建一个类并实现`CookieSerializer`接口。 2. 在实现的`serializeCookie()`方法中,可以自定义cookie的name、value、domain、path、max-age等属性。 3. 在Spring Security配置中使用`cookieSerializer()`方法来指定自定义的`CookieSerializer`实现类。 下面是一个简单的示例代码,供您参考: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public CookieSerializer cookieSerializer() { return new MyCookieSerializer(); } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .formLogin() .and() .logout() .logoutSuccessUrl("/") .and() .rememberMe() .key("remember-me-key") .rememberMeCookieName("my-remember-me-cookie") .rememberMeCookieDomain("example.com") .rememberMeCookiePath("/my-app") .tokenValiditySeconds(86400); } } public class MyCookieSerializer implements CookieSerializer { private final DefaultCookieSerializer delegate = new DefaultCookieSerializer(); @Override public String readCookieValue(HttpServletRequest request, HttpServletResponse response) { return delegate.readCookieValue(request, response); } @Override public void writeCookieValue(CookieValue cookieValue) { delegate.writeCookieValue(cookieValue); } @Override public void setDomainName(String domainName) { delegate.setDomainName(domainName); } @Override public void setCookieName(String cookieName) { delegate.setCookieName(cookieName); } @Override public void setCookiePath(String cookiePath) { delegate.setCookiePath(cookiePath); } @Override public void setCookieMaxAge(Integer cookieMaxAge) { delegate.setCookieMaxAge(cookieMaxAge); } @Override public void setUseSecureCookie(boolean useSecureCookie) { delegate.setUseSecureCookie(useSecureCookie); } @Override public void setUseHttpOnlyCookie(boolean useHttpOnlyCookie) { delegate.setUseHttpOnlyCookie(useHttpOnlyCookie); } } ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值