思路
後端生成驗證碼保存在Session中(Redis也可以),當前端輸入驗證碼進行登錄時,在校驗用戶名密碼之前校驗驗證碼是否正確,不正確就拋出異常,由失敗處理器進行處理
實現
使用Kaptcha
進行驗證碼以及圖片的生成,先引入依賴
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
在WebSecurityConfig
文件中注入我們的驗證碼生成器
@Bean
// 配置驗證碼工具
public Producer captcha(){
Properties properties = new Properties();
properties.setProperty("kaptcha.image.width","150");
properties.setProperty("kaptcha.image.height","50");
// 字符集
properties.setProperty("kaptcha.textproducer.char.string","0123456789");
// 字符長度
properties.setProperty("kaptcha.textproducer.char.length","4");
Config config = new Config(properties);
// 使用默認圖形驗證碼
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
接下來我們創建一個Controller用於生成驗證碼圖片,生成驗證碼並將其放入Session中
@Controller
public class KaptchaController {
@Autowired
private Producer captcha;
@GetMapping("/captcha.jpg")
public void getCaptcha(HttpServletRequest request,
HttpServletResponse response){
response.setContentType("image/jpeg");
String text = captcha.createText();
// 將生成的驗證碼放入session中
request.getSession().setAttribute("captcha",text);
BufferedImage bi = captcha.createImage(text);
ServletOutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
ImageIO.write(bi,"jpg",outputStream);
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(outputStream != null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
記得在配置文件中對生成驗證碼圖片的路徑放行
httpp.authorizeRequests()
.antMatchers("/css/**", "/img/**", "/js/**", "/bootstrap/**","/captcha.jpg").permitAll()
修改前端代碼使用img
標籤獲取驗證碼圖片,並添加用於輸入驗證碼的輸入框
<form action="/login" method="POST">
<div class="form-group">
<label for="username">User-Name</label>
<input id="username" class="form-control" name="username" value="" required autofocus>
</div>
<div class="form-group">
<label for="password">Password
<a href="forgot.html" class="float-right">
Forgot Password?
</a>
</label>
<input id="password" type="password" class="form-control" name="password" required
data-eye>
</div>
<div class="form-group">
<label for="captcha">驗證碼
</label>
<input id="captcha" type="text" class="form-control" name="captcha" required>
<img src="/captcha.jpg" alt="captcha" height="50px" width="150px">
</div>
<div class="form-group">
<label>
<input type="checkbox" name="remember"> Remember Me
</label>
</div>
<div class="form-group no-margin">
<button type="submit" class="btn btn-primary btn-block">
Login
</button>
</div>
<div class="margin-top20 text-center">
Don't have an account? <a href="register.html">Create One</a>
</div>
</form>
在我們配置的入參爲HttpSecurity
的configure中,查看http
的方法可以看到有addFilterAfter
,addFilterBefore
,addFilter
,addFilterAt
用於添加過濾器,其實SpringSecurity的原理就是過濾器鏈,之前我們一系列的操作就是基於過濾器鏈完成的,接下來我們創建自己的攔截器用於驗證驗證驗證碼是否正確,事項Filter
接口即可,這裏實現的是OncePerRequestFilter
,它可以保證一次請求只會經過一次該過濾器
public class VerificationCodeFilter extends OncePerRequestFilter {
private AuthenticationFailureHandler failureHandler = new MyFailureHandler();
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
if(!"/login".equals(httpServletRequest.getRequestURI())){
// 非登陸請求就不驗證驗證碼
filterChain.doFilter(httpServletRequest, httpServletResponse);
}else {
try{
verificationCode(httpServletRequest);
filterChain.doFilter(httpServletRequest, httpServletResponse);
}catch (VerificationCodeException e){
failureHandler.onAuthenticationFailure(httpServletRequest,httpServletResponse,e);
}
}
}
private void verificationCode(HttpServletRequest httpServletRequest) throws VerificationCodeException {
String requestCode = httpServletRequest.getParameter("captcha");
HttpSession session = httpServletRequest.getSession();
String vertificationCode = (String) session.getAttribute("captcha");
// 不論校驗成功還是失敗,要保證session的驗證碼被刪除
session.removeAttribute("captcha");
if(StringUtils.isEmpty(requestCode) || StringUtils.isEmpty(vertificationCode)
|| !requestCode.equals(vertificationCode)){
throw new VerificationCodeException();
}
}
}
這裏我將之前的失敗處理器單獨的放在了一個類裏面,沒有像之前使用內部類了。還創建了一個自定義的異常,用於驗證碼校驗失敗
public class VerificationCodeException extends AuthenticationException {
public VerificationCodeException() {
super("驗證碼校驗失敗");
}
}
再在配置文件中將我們的攔截器添加進去即可
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 開啓註解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MySuccessHandler successHandler;
@Autowired
private MyFailureHandler failureHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/css/**", "/img/**", "/js/**", "/bootstrap/**","/captcha.jpg").permitAll()
.antMatchers("/app/api/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/myLogin.html")
.loginProcessingUrl("/login")
.successHandler(successHandler)
.failureHandler(failureHandler)
.authenticationDetailsSource(myWebAuthenticationDetailsSource)
.permitAll()
// 使登錄頁不受限
.and()
.csrf().disable()
// 在驗證用戶名密碼之前驗證驗證碼信息
.addFilterBefore(new VerificationCodeFilter(),
UsernamePasswordAuthenticationFilter.class);
}
@Autowired
private DataSource dataSource;
@Autowired
private MyUserDetailService userDetailService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
// 配置驗證碼工具
public Producer captcha(){
Properties properties = new Properties();
properties.setProperty("kaptcha.image.width","150");
properties.setProperty("kaptcha.image.height","50");
// 字符集
properties.setProperty("kaptcha.textproducer.char.string","0123456789");
// 字符長度
properties.setProperty("kaptcha.textproducer.char.length","4");
Config config = new Config(properties);
// 使用默認圖形驗證碼
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
接下來訪問登錄頁測試即可