要定义AuthenticationManager只需要定义其实现ProviderManager。而ProviderManager又需要依赖AuthenticationProvider。所以我们要实现一个专门处理CaptchaAuthenticationToken的AuthenticationProvider。AuthenticationProvider的流程是:
从CaptchaAuthenticationToken拿到手机号、验证码。
利用手机号从数据库查询用户信息,并判断用户是否是有效用户,实际上就是实现UserDetailsService接口
验证码校验。
校验成功则封装授信的凭据。
校验失败抛出认证异常。
根据这个流程实现如下:
package cn.felord.spring.security.captcha; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.util.Assert; import java.util.Collection; import java.util.Objects; /** * 验证码认证器. * @author felord.cn */ @Slf4j public class CaptchaAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware { private final GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); private final UserDetailsService userDetailsService; private final CaptchaService captchaService; private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); /** * Instantiates a new Captcha authentication provider. * * @param userDetailsService the user details service * @param captchaService the captcha service */ public CaptchaAuthenticationProvider(UserDetailsService userDetailsService, CaptchaService captchaService) { this.userDetailsService = userDetailsService; this.captchaService = captchaService; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(CaptchaAuthenticationToken.class, authentication, () -> messages.getMessage( "CaptchaAuthenticationProvider.onlySupports", "Only CaptchaAuthenticationToken is supported")); CaptchaAuthenticationToken unAuthenticationToken = (CaptchaAuthenticationToken) authentication; String phone = unAuthenticationToken.getName(); String rawCode = (String) unAuthenticationToken.getCredentials(); UserDetails userDetails = userDetailsService.loadUserByUsername(phone); // 此处省略对UserDetails 的可用性 是否过期 是否锁定 是否失效的检验 建议根据实际情况添加 或者在 UserDetailsService 的实现中处理 if (Objects.isNull(userDetails)) { throw new BadCredentialsException("Bad credentials"); } // 验证码校验 if (captchaService.verifyCaptcha(phone, rawCode)) { return createSuccessAuthentication(authentication, userDetails); } else { throw new BadCredentialsException("captcha is not matched"); } } @Override public boolean supports(Class<?> authentication) { return CaptchaAuthenticationToken.class.isAssignableFrom(authentication); } @Override public void afterPropertiesSet() throws Exception { Assert.notNull(userDetailsService, "userDetailsService must not be null"); Assert.notNull(captchaService, "captchaService must not be null"); } @Override public void setMessageSource(MessageSource messageSource) { this.messages = new MessageSourceAccessor(messageSource); } /** * 认证成功将非授信凭据转为授信凭据. * 封装用户信息 角色信息。 * * @param authentication the authentication * @param user the user * @return the authentication */ protected Authentication createSuccessAuthentication(Authentication authentication, UserDetails user) { Collection<? extends GrantedAuthority> authorities = authoritiesMapper.mapAuthorities(user.getAuthorities()); CaptchaAuthenticationToken authenticationToken = new CaptchaAuthenticationToken(user, null, authorities); authenticationToken.setDetails(authentication.getDetails()); return authenticationToken; } }然后就可以组装ProviderManager了:
ProviderManager providerManager = new ProviderManager(Collections.singletonList(captchaAuthenticationProvider));经过3.1和3.2的准备,我们的准备工作就完成了。
3.3 验证码认证过滤器