关于springboot:SpringSecurity登录优雅集成图形验证码

3次阅读

共计 5294 个字符,预计需要花费 14 分钟才能阅读完成。

前言

在 SpringSecurity 的默认登录反对组件 formLogin 中没有提供图形验证码的反对,目前大多数的计划都是通过新增 Filter 来实现。filter 的形式能够实现性能,然而没有优雅的解决,须要从新保护一套和登录相干的 url,例如:loginProccessUrl,loginFailUrl,loginSuccessUrl,从软件设计角度来讲性能没有内聚。
上面为大家介绍一种优雅的解决方案。

解决思路

先获取验证码

判断图形验证码先要获取到验证码,在 UsernamePasswordAuthenticationToken(UPAT)中没有字段来存储验证码,重写 UPAT 老本太高。能够从 details 字段中动手,将验证码放在 details 中。

判断验证码是否正确

UPAT 的认证是在 DaoAuthenticationProvider 中实现的,如果须要判断验证码间接批改是老本比拟大的形式,能够新增 AuthenticationProvider 来对验证码新增验证。

输入验证码

惯例超过能够通过 Controller 来输入,然而验证码的治理须要对立,避免各种 sessionKey 乱飞。

代码实现

新增验证码容器:CaptchaAuthenticationDetails

public class CaptchaAuthenticationDetails extends WebAuthenticationDetails {
    private final String DEFAULT_CAPTCHA_PARAMETER_NAME = "captcha";
 private String captchaParameter = DEFAULT_CAPTCHA_PARAMETER_NAME;
/**
 * 用户提交的验证码
 */
private String committedCaptcha;
/**
 * 预设的验证码
 */
private String presetCaptcha;
 private final WebAuthenticationDetails webAuthenticationDetails;
 public CaptchaAuthenticationDetails(HttpServletRequest request) {super(request);
 this.committedCaptcha = request.getParameter(captchaParameter);
 this.webAuthenticationDetails = new WebAuthenticationDetails(request);
 }
    public boolean isCaptchaMatch() {if (this.presetCaptcha == null || this.committedCaptcha == null) {return false;}
        return this.presetCaptcha.equalsIgnoreCase(committedCaptcha);
 }
 getter ...
 setter ...
 }

这个类次要是用于保留验证码

验证码获取:CaptchaAuthenticationDetailsSource

public class CaptchaAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, CaptchaAuthenticationDetails> {
    private final CaptchaRepository<HttpServletRequest> captchaRepository;
 public CaptchaAuthenticationDetailsSource(CaptchaRepository<HttpServletRequest> captchaRepository) {this.captchaRepository = captchaRepository;}
    @Override
 public CaptchaAuthenticationDetails buildDetails(HttpServletRequest httpServletRequest) {CaptchaAuthenticationDetails captchaAuthenticationDetails = new CaptchaAuthenticationDetails(httpServletRequest);
 captchaAuthenticationDetails.setPresetCaptcha(captchaRepository.load(httpServletRequest));
 return captchaAuthenticationDetails;
 }
}

依据提交的参数构建 CaptchaAuthenticationDetails,用户提交的验证码 (committedCaptcha) 从 request 中获取,预设的验证码 (presetCaptcha) 从验证码仓库 (CaptchaRepostory) 获取

验证码仓库实现 SessionCaptchaRepository

public class SessionCaptchaRepository implements CaptchaRepository<HttpServletRequest> {
    private static final String CAPTCHA_SESSION_KEY = "captcha";
 /**
 * the key of captcha in session attributes */ private String captchaSessionKey = CAPTCHA_SESSION_KEY;
 @Override
 public String load(HttpServletRequest request) {return (String) request.getSession().getAttribute(captchaSessionKey);
 }
    @Override
 public void save(HttpServletRequest request, String captcha) {request.getSession().setAttribute(captchaSessionKey, captcha);
 }
    /**
 * @return sessionKey */ public String getCaptchaSessionKey() {return captchaSessionKey;}
    /**
 * @param captchaSessionKey sessionKey
 */ public void setCaptchaSessionKey(String captchaSessionKey) {this.captchaSessionKey = captchaSessionKey;}
}

这个验证码仓库是基于 Session 的,如果想要基于 Redis 只有实现 CaptchaRepository 即可。

对验证码进行认证 CaptchaAuthenticationProvider

public class CaptchaAuthenticationProvider implements AuthenticationProvider {private final Logger log = LoggerFactory.getLogger(this.getClass());
 @Override
 public Authentication authenticate(Authentication authentication) throws AuthenticationException {UsernamePasswordAuthenticationToken authenticationToken = (UsernamePasswordAuthenticationToken) authentication;
 CaptchaAuthenticationDetails details = (CaptchaAuthenticationDetails) authenticationToken.getDetails();
 if (!details.isCaptchaMatch()) {
            // 验证码不匹配抛出异样,退出认证
 if (log.isDebugEnabled()) {log.debug("认证失败:验证码不匹配");
 }
            throw new CaptchaIncorrectException("验证码谬误");
 }
        // 替换 details
 authenticationToken.setDetails(details.getWebAuthenticationDetails());
 // 返回空交给下一个 provider 进行认证
 return null;
 }
    @Override
 public boolean supports(Class<?> aClass) {return UsernamePasswordAuthenticationToken.class.isAssignableFrom(aClass);
 }
}

SpringSecurity 配置

@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Bean
 public CaptchaRepository<HttpServletRequest> sessionCaptchaRepository() {return new SessionCaptchaRepository();
 }
    @Override
 protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/login")
                .loginPage("/login.html")
                .authenticationDetailsSource(new CaptchaAuthenticationDetailsSource(sessionCaptchaRepository()))
                .failureUrl("/login.html?error=true")
                .defaultSuccessUrl("/index.html")
                .and()
                .authenticationProvider(new CaptchaAuthenticationProvider())
                .csrf()
                .disable();}
    @Override
 public void configure(WebSecurity web) {web.ignoring()
                .mvcMatchers("/captcha", "/login.html");
 }
}

将 CaptchaAuthenticationProvider 退出到认证链条中,重新配置 authenticationDetailsSource

提供图形验证码接口

@Controller
public class CaptchaController {
    private final CaptchaRepository<HttpServletRequest> captchaRepository;
 @Autowired
 public CaptchaController(CaptchaRepository<HttpServletRequest> captchaRepository) {this.captchaRepository = captchaRepository;}
    @RequestMapping("/captcha")
    public void captcha(HttpServletRequest request, HttpServletResponse response) throws IOException {RandomCaptcha randomCaptcha = new RandomCaptcha(4);
 captchaRepository.save(request, randomCaptcha.getValue());
 CaptchaImage captchaImage = new DefaultCaptchaImage(200, 60, randomCaptcha.getValue());
 captchaImage.write(response.getOutputStream());
 }
}

将生成的随机验证码 (RandomCaptcha) 保留到验证码仓库 (CaptchaRepository) 中,并将验证码图片 (CaptchaImage) 输入到客户端。
至此整个图形验证码认证的全流程曾经完结。

代码仓库

https://gitee.com/leecho/spri…

正文完
 0