Spring Security 实战干货:从零手写一个验证码登录 (3)

定制好验证码凭据和验证码认证管理器后我们就可以定义验证码认证过滤器了。修改一下UsernamePasswordAuthenticationFilter就能满足需求:

package cn.felord.spring.security.captcha; import org.springframework.lang.Nullable; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class CaptchaAuthenticationFilter extends AbstractAuthenticationProcessingFilter { public static final String SPRING_SECURITY_FORM_PHONE_KEY = "phone"; public static final String SPRING_SECURITY_FORM_CAPTCHA_KEY = "captcha"; public CaptchaAuthenticationFilter() { super(new AntPathRequestMatcher("/clogin", "POST")); } public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (!request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } String phone = obtainPhone(request); String captcha = obtainCaptcha(request); if (phone == null) { phone = ""; } if (captcha == null) { captcha = ""; } phone = phone.trim(); CaptchaAuthenticationToken authRequest = new CaptchaAuthenticationToken( phone, captcha); // Allow subclasses to set the "details" property setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); } @Nullable protected String obtainCaptcha(HttpServletRequest request) { return request.getParameter(SPRING_SECURITY_FORM_CAPTCHA_KEY); } @Nullable protected String obtainPhone(HttpServletRequest request) { return request.getParameter(SPRING_SECURITY_FORM_PHONE_KEY); } protected void setDetails(HttpServletRequest request, CaptchaAuthenticationToken authRequest) { authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); } }

这里我们指定了拦截验证码登陆的请求为:

POST /clogin?phone=手机号&captcha=验证码 HTTP/1.1 Host: localhost:8082

接下来就是配置了。

3.4 配置

我把所有的验证码认证的相关配置集中了起来,并加上了注释。

package cn.felord.spring.security.captcha; import cn.hutool.core.util.RandomUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.util.StringUtils; import java.util.Collections; import java.util.Objects; /** * 验证码认证配置. * * @author felord.cn * @since 13 :23 */ @Slf4j @Configuration public class CaptchaAuthenticationConfiguration { private static final String SMS_CAPTCHA_CACHE = "captcha"; /** * spring cache 管理验证码的生命周期. * * @return the captcha cache storage */ @Bean CaptchaCacheStorage captchaCacheStorage() { return new CaptchaCacheStorage() { @CachePut(cacheNames = SMS_CAPTCHA_CACHE, key = "#phone") @Override public String put(String phone) { return RandomUtil.randomNumbers(5); } @Cacheable(cacheNames = SMS_CAPTCHA_CACHE, key = "#phone") @Override public String get(String phone) { return null; } @CacheEvict(cacheNames = SMS_CAPTCHA_CACHE, key = "#phone") @Override public void expire(String phone) { } }; } /** * 验证码服务. * 两个功能: 发送和校验. * * @param captchaCacheStorage the captcha cache storage * @return the captcha service */ @Bean public CaptchaService captchaService(CaptchaCacheStorage captchaCacheStorage) { return new CaptchaService() { @Override public boolean sendCaptcha(String phone) { String existed = captchaCacheStorage.get(phone); if (StringUtils.hasText(existed)) { // 节约成本的话如果缓存存在可用的验证码 不再发新的验证码 log.warn("captcha code 【 {} 】 is available now", existed); return false; } // 生成验证码并放入缓存 String captchaCode = captchaCacheStorage.put(phone); log.info("captcha: {}", captchaCode); //todo 这里自行完善调用第三方短信服务 return true; } @Override public boolean verifyCaptcha(String phone, String code) { String cacheCode = captchaCacheStorage.get(phone); if (Objects.equals(cacheCode, code)) { // 验证通过手动过期 captchaCacheStorage.expire(phone); return true; } return false; } }; } /** * 自行实现根据手机号查询可用的用户,这里简单举例. * 注意该接口可能出现多态。所以***加上注解@Qualifier * * @return the user details service */ @Bean @Qualifier("captchaUserDetailsService") public UserDetailsService captchaUserDetailsService() { // 验证码登陆后密码无意义了但是需要填充一下 return username -> User.withUsername(username).password("TEMP") //todo 这里权限 你需要自己注入 .authorities(AuthorityUtils.createAuthorityList("ROLE_ADMIN", "ROLE_APP")).build(); } /** * 验证码认证器. * * @param captchaService the captcha service * @param userDetailsService the user details service * @return the captcha authentication provider */ @Bean public CaptchaAuthenticationProvider captchaAuthenticationProvider(CaptchaService captchaService, @Qualifier("captchaUserDetailsService") UserDetailsService userDetailsService) { return new CaptchaAuthenticationProvider(userDetailsService, captchaService); } /** * 验证码认证过滤器. * * @param authenticationSuccessHandler the authentication success handler * @param authenticationFailureHandler the authentication failure handler * @param captchaAuthenticationProvider the captcha authentication provider * @return the captcha authentication filter */ @Bean public CaptchaAuthenticationFilter captchaAuthenticationFilter(AuthenticationSuccessHandler authenticationSuccessHandler, AuthenticationFailureHandler authenticationFailureHandler, CaptchaAuthenticationProvider captchaAuthenticationProvider) { CaptchaAuthenticationFilter captchaAuthenticationFilter = new CaptchaAuthenticationFilter(); // 配置 authenticationManager ProviderManager providerManager = new ProviderManager(Collections.singletonList(captchaAuthenticationProvider)); captchaAuthenticationFilter.setAuthenticationManager(providerManager); // 成功处理器 captchaAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler); // 失败处理器 captchaAuthenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler); return captchaAuthenticationFilter; } }

然而这并没有完,你需要将CaptchaAuthenticationFilter配置到整个Spring Security的过滤器链中,这种看了胖哥教程的同学应该非常熟悉了。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgjdwf.html