SpringSecurity默认是没有图片验证码功能的,假如我们需要在登录界面添加一个图片验证码的功能,我们可以在UsernamePasswordAuthenticationFilter过滤器之前写一个图片验证码过滤器,图片验证码过滤器的功能:首先判断请求地址是否需要图片验证码,如果需要就判断图片验证码是否正确,如果验证码正确则继续往下执行,否则抛出验证码错误异常;如果不需要就直接往下执行。
图片验证码
使用kaptcha插件来生成图片验证码,导入如下依赖
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
kaptcha的一些配置
@Component
public class KaptchaConfig {
@Bean
public DefaultKaptcha getDefaultKaptcha(){
com.google.code.kaptcha.impl.DefaultKaptcha defaultKaptcha = new com.google.code.kaptcha.impl.DefaultKaptcha();
Properties properties = new Properties();
properties.setProperty("kaptcha.border", "yes"); // 是否有边框
properties.setProperty("kaptcha.border.color", "105,179,90"); // 验证码边框颜色
// properties.setProperty("kaptcha.textproducer.char.string", "ABCDEFG23456789"); // 验证码,不设置默认也存在
properties.setProperty("kaptcha.noise.color", "red"); // 干扰线的颜色
properties.setProperty("kaptcha.textproducer.font.color", "blue"); // 字体颜色
properties.setProperty("kaptcha.image.width", "110");
properties.setProperty("kaptcha.image.height", "40");
properties.setProperty("kaptcha.textproducer.font.size", "30");
properties.setProperty("kaptcha.session.key", "code");
properties.setProperty("kaptcha.textproducer.char.length", "4");
properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
Config config = new Config(properties);
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}
kaptcha使用
DefaultKaptcha defaultKaptcha = new DefaultKaptcha ();
//生产验证码字符串
String code = defaultKaptcha.createText();
//使用生产的验证码字符串返回一个BufferedImage对象
BufferedImage image = defaultKaptcha.createImage(code);
省略图片验证码的生成过程,主要有两个步骤:
1. 使用DefaultKaptcha 类生成BufferedImage 对象,将BufferedImage封装到ImageCode类中,ImageCode主要有三个属性:
String code(验证码字符串)、LocalDateTime expireTime(过期时间)、BufferedImage image(图片)
2. 将生成的ImageCode存入Session中
图片验证码过滤器
为了在某些地址使用图片验证码,我们需要写一个过滤器。
@Component
public class ImageCodeFilter extends OncePerRequestFilter implements InitializingBean {
@Autowired
private AuthenticationFailureHandler authenticationFailureHandler;
private Set<String> urls = new HashSet<>();
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public void afterPropertiesSet() throws ServletException {
super.afterPropertiesSet();
// 这里可以设置,哪些地址是需要图片验证码进行验证的
urls.add("/authentication/form"); // 登录地址
}
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
boolean action = false;
// 判断请求地址是否需要图片验证码
for (String url : urls) {
if (antPathMatcher.match(url, httpServletRequest.getRequestURI())) {
action = true;
break;
}
}
if (action) {
try {
// 验证验证码是否正确
validate(httpServletRequest);
} catch (ImageCodeException e) {
// 验证码错误则抛出异常
authenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);
return;
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
private void validate(HttpServletRequest request) {
ImageCode imageCodeSession = (ImageCode)request.getSession().getAttribute(ValidateCodeProcessor.SESSION_KEY_PREFIX + "IMAGE");
String imageCodeRequest = request.getParameter("imageCode");
if (imageCodeRequest == null || imageCodeRequest.isEmpty()) {
throw new ImageCodeException("图片验证码不能为空");
}
if (imageCodeSession == null) {
throw new ImageCodeException("验证码不存在");
}
if (imageCodeSession.isExpired()) {
request.getSession().removeAttribute(ValidateCodeProcessor.SESSION_KEY_PREFIX + "IMAGE");
throw new ImageCodeException("验证码已过期");
}
if(!imageCodeRequest.equalsIgnoreCase(imageCodeSession.getCode())) {
throw new ImageCodeException("验证码错误");
}
request.getSession().removeAttribute(ValidateCodeProcessor.SESSION_KEY_PREFIX + "IMAGE");
}
}
配置过滤器
将ImageCodeFilter过滤器设置在UsernamePasswordAuthenticationFilter之前
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserService myUserService;
@Autowired
private MyAuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private MyAuthenticationFailHandler authenticationFailHandler;
@Autowired
private ImageCodeFilter imageCodeFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(imageCodeFilter, UsernamePasswordAuthenticationFilter.class) // 将ImageCodeFilter过滤器设置在UsernamePasswordAuthenticationFilter之前
.authorizeRequests()
.antMatchers("/authentication/*","/login/*","/code/*") // 不需要登录就可以访问
.permitAll()
.antMatchers("/user/**").hasAnyRole("USER") // 需要具有ROLE_USER角色才能访问
.antMatchers("/admin/**").hasAnyRole("ADMIN") // 需要具有ROLE_ADMIN角色才能访问
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/authentication/login") // 访问需要登录才能访问的页面,如果未登录,会跳转到该地址来
.loginProcessingUrl("/authentication/form")
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailHandler)
;
}
// 密码加密方式
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
// 重写方法,自定义用户
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.inMemoryAuthentication().withUser("lzc").password(new BCryptPasswordEncoder().encode("123456")).roles("ADMIN","USER");
// auth.inMemoryAuthentication().withUser("zhangsan").password(new BCryptPasswordEncoder().encode("123456")).roles("USER");
auth.userDetailsService(myUserService); // 注入MyUserService,这样SpringSecurity会调用里面的loadUserByUsername(String s)
}
}
登录页面
<form th:action="@{/authentication/form}" method="post">
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control" id="username" name="username" placeholder="Enter username">
</div>
<div class="form-group">
<label for="Password">Password:</label>
<input type="password" class="form-control" id="Password" name="password" placeholder="Enter password">
</div>
<div class="form-group">
<label for="imageCode">imageCode:</label>
<input type="text" class="form-control" id="imageCode" name="imageCode" placeholder="Enter imageCode">
<img src="/code/image">
</div>
<div class="form-group" th:if="${param.error}">
<p th:if="${session.SPRING_SECURITY_LAST_EXCEPTION}">
<p th:text="${session.SPRING_SECURITY_LAST_EXCEPTION.message}"></p>
</p>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
代码地址 https://github.com/923226145/SpringSecurity/tree/master/chapter4