项目验证码简单实现
首先在项目中加入依赖
<!-- kaptcha -->
<!-- Kaptcha验证码组件 -->
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
然后配置验证码生成类的信息
package com.dongmu.config;
import java.util.Properties;
import com.google.code.kaptcha.Producer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
//验证码配置一般都是在web.xml中配置 springboot中 将 xml 的形势转化成代码实现
@Configuration
public class KaptchaConfig {
@Bean
public Producer kaptchaProducer() {
Properties properties = new Properties();
properties.setProperty("kaptcha.image.width", "100");
properties.setProperty("kaptcha.image.height", "40");
// 设置字体大小
properties.setProperty("kaptcha.textproducer.font.size", "32");
// 设置字体颜色
properties.setProperty("kaptcha.textproducer.font.color", "black");
properties.setProperty("kaptcha.textproducer.char.string", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
// 截取4个字符
properties.setProperty("kaptcha.textproducer.char.length", "4");
DefaultKaptcha kaptcha = new DefaultKaptcha();
Config config = new Config(properties);
kaptcha.setConfig(config);
return kaptcha;
}
}
编写Controller类返回验证码,这里注意将验证码的请求放行,不然无法获取。
@Autowired
Producer kaptchaProduer;
@RequestMapping("/vc.jpg")
public void getImage(HttpServletResponse response, HttpSession session){
// 生成验证码
String text = kaptchaProduer.createText();
// 生成图片
BufferedImage image = kaptchaProduer.createImage(text);
// 将验证码存入Session
session.setAttribute("kaptcha",text);
//将图片输出给浏览器
try {
response.setContentType("image/png");
OutputStream os = response.getOutputStream();
ImageIO.write(image,"png",os);
} catch (IOException e) {
System.out.println("响应验证码失败"+e.getMessage());
}
}
然后在html中写
<body>
<form th:action="@{/login}" method="post">
用户名:<input type="text" name="username"><br>
密码:<input type="text" name="password"><br>
验证码:<input type="text" name="kaptcha">
<img th:src="@{/vc.jpg}" alt="">
<br>
<input type="submit" name="登录">
</form>
</body>
有了验证码之后我们就需要验证码正确之后才能进行用户名密码的验证,这时候我们需要改变验证的逻辑,自定义验证过滤器
package com.dongmu.filter;
import lombok.Data;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Data
public class KaptchaFilter extends UsernamePasswordAuthenticationFilter {
private static final String FROM_KAPTCHA = "kaptcha";
private String kaptchaName = FROM_KAPTCHA;
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String attribute = (String) request.getSession().getAttribute(FROM_KAPTCHA);
if (attribute!=null&&attribute.equalsIgnoreCase(request.getParameter(getKaptchaName()))){
return super.attemptAuthentication(request, response);
}else {
throw new AuthenticationException("验证码错误,请重新输入!") {
@Override
public String getMessage() {
return super.getMessage();
}
};
}
}
}
替换掉原来的UsernamePasswordAuthenticationFilter
,因此在WebSecurityConfiguration
中编写
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final MyUserDetailService myUserDetailService;
public WebSecurityConfiguration(MyUserDetailService myUserDetailService) {
this.myUserDetailService = myUserDetailService;
}
//这里自定义的AuthenticationManager会把工厂默认的覆盖掉
// 这样即使上面创建了UserDetailService的bean也不会自动注入进去
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
// builder.userDetailsService(userDetailsService());
builder.userDetailsService(myUserDetailService);
System.out.println("自定义的AuthenticationManager:"+builder);
}
//这个覆盖的作用是,上面我们自己定义的AuthenticationManager不会暴漏在spring工厂中,只能内部使用,无法注入
//到其他的组件当中,如果我们想要注入到其他的组件当中,就需要覆盖父类中的这个方法。
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
@Bean
protected MyAuthenticationHandler myAuthenticationHandler(){
return new MyAuthenticationHandler();
}
@Bean
@DependsOn("myAuthenticationHandler")
public KaptchaFilter kaptchaFilter() throws Exception {
//自定义UsernamePasswordAuthenticationFilter之后这些属性要重新设置
KaptchaFilter kaptchaFilter = new KaptchaFilter();
kaptchaFilter.setFilterProcessesUrl("/login");
kaptchaFilter.setUsernameParameter("username");
kaptchaFilter.setPasswordParameter("password");
kaptchaFilter.setAuthenticationManager(authenticationManager());
kaptchaFilter.setAuthenticationSuccessHandler(myAuthenticationHandler());
kaptchaFilter.setAuthenticationFailureHandler(myAuthenticationHandler());
return kaptchaFilter;
}
@Override
@DependsOn("myAuthenticationHandler")
protected void configure(HttpSecurity http) throws Exception {
//ss里面要求放行的资源要写在任何请求的前面
http.authorizeRequests()//开启请求的权限管理
.mvcMatchers("/public/**").permitAll()
.mvcMatchers("/login.html").permitAll()
.mvcMatchers("/vc.jpg").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login.html")//表单验证的方式,同时指定默认的登录界面
//一旦自定义登录界面必须指定登录url
.loginProcessingUrl("/login")
// .usernameParameter("uname")
// .passwordParameter("pwd")
// .successForwardUrl("")//默认验证成功之后的跳转,这个是请求转发, 登录成功之后直接跳转到这个指定的地址,原来的地址不跳转了。
// .defaultSuccessUrl("")//这个也是成功之后的跳转路径,默认是请求重定向。 登录成功之后会记住原来访问的路径
// .successHandler(new MyAuthenticationSuccessHandler())//前后端分离的处理方案
// .failureForwardUrl("/login.html")//登录失败之后的请求转发页面
.failureUrl("/login.html")//登录失败之后的重定向页面
.failureHandler(myAuthenticationHandler())
.and()
.logout()
// .logoutUrl("/logout")//指定注销登录的接口
.logoutRequestMatcher(new OrRequestMatcher(
//指定注销登录的接口和方式
new AntPathRequestMatcher("/aa","GET"),
new AntPathRequestMatcher("/bb","POST")
))
.invalidateHttpSession(true)//是否让当前的session失效
.clearAuthentication(true)//清除认证标记
// .logoutSuccessUrl("")//可以指定退出登录之后跳转的路径
.logoutSuccessHandler(myAuthenticationHandler())
.and()
.csrf().disable();
http.addFilterAt(kaptchaFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
然后启动项目测试,只有验证码正确的时候才能访问成功。
当然,项目中验证码的实现逻辑肯定不是这样子简单,而是要记录是哪个用户的验证码,而且一般是存放在redis里面设置过期时间。