图形验证码
图形验证码一般是防止恶意,人眼看起来都费劲,何况是机器。不少网站为了防止用户利用机器人自动注册、登录、灌水,都采用了验证码技术。所谓验证码,就是将一串随机产生的数字或符号,生成一幅图片, 图片里加上一些干扰, 也有目前需要手动滑动的图形验证码. 这种可以有专门去做的第三方平台. 比如极验(https://www.geetest.com/), 那么本次课程讲解主要针对图形验证码.
spring security添加验证码大致可以分为三个步骤:
1. 根据随机数生成验证码图片;
2. 将验证码图片显示到登录页面;
3. 认证流程中加入验证码校验。
Spring Security的认证校验是由UsernamePasswordAuthenticationFilter过滤器完成的,所以我们的验证码校验逻辑应该在这个过滤器之前。验证码通过后才能到后续的操作. 流程如下:
代码实现:
验证码生成类
package com.po.controller; import com.po.domain.ImageCode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.imageio.ImageIO; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.awt.*; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.Random; import java.util.concurrent.TimeUnit; /** * 处理生成验证码的请求 */ @RestController @RequestMapping("/code") public class ValidateCodeController { public final static String REDIS_KEY_IMAGE_CODE = "REDIS_KEY_IMAGE_CODE"; public final static int expireIn = 60; // 验证码有效时间 60s //使用sessionStrategy将生成的验证码对象存储到Session中,并通过IO流将生成的图片输出到登录页面上。 @Autowired public StringRedisTemplate stringRedisTemplate; @RequestMapping("/image") public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException { //获取访问IP String remoteAddr = request.getRemoteAddr(); //生成验证码对象 ImageCode imageCode = createImageCode(); //生成的验证码对象存储到redis中 KEY为REDIS_KEY_IMAGE_CODE+IP地址 stringRedisTemplate.boundValueOps(REDIS_KEY_IMAGE_CODE + "-" + remoteAddr) .set(imageCode.getCode(), expireIn, TimeUnit.SECONDS); //通过IO流将生成的图片输出到登录页面上 ImageIO.write(imageCode.getImage(), "jpeg", response.getOutputStream()); } /** * 用于生成验证码对象 * * @return */ private ImageCode createImageCode() { int width = 100; // 验证码图片宽度 int height = 36; // 验证码图片长度 int length = 4; // 验证码位数 //创建一个带缓冲区图像对象 BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); //获得在图像上绘图的Graphics对象 Graphics g = image.getGraphics(); Random random = new Random(); //设置颜色、并随机绘制直线 g.setColor(getRandColor(200, 250)); g.fillRect(0, 0, width, height); g.setFont(new Font("Times New Roman", Font.ITALIC, 20)); g.setColor(getRandColor(160, 200)); for (int i = 0; i < 155; i++) { int x = random.nextInt(width); int y = random.nextInt(height); int xl = random.nextInt(12); int yl = random.nextInt(12); g.drawLine(x, y, x + xl, y + yl); } //生成随机数 并绘制 StringBuilder sRand = new StringBuilder(); for (int i = 0; i < length; i++) { String rand = String.valueOf(random.nextInt(10)); sRand.append(rand); g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110))); g.drawString(rand, 13 * i + 6, 16); } g.dispose(); return new ImageCode(image, sRand.toString()); } /** * 获取随机演示 * * @param fc * @param bc * @return */ private Color getRandColor(int fc, int bc) { Random random = new Random(); if (fc > 255) { fc = 255; } if (bc > 255) { bc = 255; } int r = fc + random.nextInt(bc - fc); int g = fc + random.nextInt(bc - fc); int b = fc + random.nextInt(bc - fc); return new Color(r, g, b); } }
自定义验证码过滤器ValidateCodeFilter
package com.po.filter; import com.po.controller.ValidateCodeController; import com.po.exception.ValidateCodeException; import com.po.service.impl.MyAuthenticationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 自定义验证码过滤器,OncePerRequestFilter 一次请求只会经过一次过滤器 */ @Service public class ValidateCodeFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 判断是否是登录请求 if (request.getRequestURI().equals("/login") && request.getMethod().equalsIgnoreCase("post")) { String imageCode = request.getParameter("imageCode"); System.out.println(imageCode); // 具体的验证流程 try { validate(request, imageCode); } catch (ValidateCodeException e) { myAuthenticationService.onAuthenticationFailure(request, response, e); return; } } // 如果不是登录请求,直接放行 filterChain.doFilter(request, response); } @Autowired StringRedisTemplate stringRedisTemplate; @Autowired MyAuthenticationService myAuthenticationService; private void validate(HttpServletRequest request, String imageCode) { // 从redis中获取验证码 String redisKey = ValidateCodeController.REDIS_KEY_IMAGE_CODE + "-" + request.getRemoteAddr(); String redisImageCode = stringRedisTemplate.boundValueOps(redisKey).get(); // 验证码的判断 if (!StringUtils.hasText(redisImageCode)) { throw new ValidateCodeException("验证码的值不能为空"); } if (redisImageCode == null) { throw new ValidateCodeException("验证码已过期"); } if (!redisImageCode.equals(imageCode)) { throw new ValidateCodeException("验证码不正确"); } // 从redis中删除验证码 stringRedisTemplate.delete(redisKey); } }
自定义验证码异常类
package com.po.exception; import org.springframework.security.core.AuthenticationException; public class ValidateCodeException extends AuthenticationException { public ValidateCodeException(String msg) { super(msg); } }
security配置类(里面添加我们自定义过滤器的顺序)
package com.po.config; import com.po.filter.ValidateCodeFilter; import com.po.service.impl.MyAuthenticationService; import com.po.service.impl.MyUserDetailsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import javax.sql.DataSource; @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired private MyUserDetailsService myUserDetailsService; @Autowired ValidateCodeFilter validateCodeFilter; /** * http请求方法 * * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { /** http.httpBasic() //开启httpBasic认证 .and().authorizeRequests().anyRequest().authenticated(); //所有请求都需要认证之后访问 */ // 将验证码过滤器添加在UsernamePasswordAuthenticationFilter过滤器的前面 http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class); /* http.formLogin().loginPage("/login.html")//开启表单认证 // .and().authorizeRequests() //放行登录页面 // .anyRequest().authenticated(); // .and().authorizeRequests().antMatchers("/login.html").permitAll() //放行登录页面 .and().authorizeRequests().antMatchers("/toLoginPage").permitAll() //放行登录页面 .anyRequest().authenticated();*/ http.formLogin() //开启表单认证 .loginPage("/toLoginPage") // 自定义登陆页面 .loginProcessingUrl("/login") //表单提交路径 .usernameParameter("username").passwordParameter("password") //自定义input额name值和password .successForwardUrl("/") //登录成功之后跳转的路径 .successHandler(myAuthenticationService) // 登录成功处理 .failureHandler(myAuthenticationService) //登录失败处理 .and().logout().logoutUrl("/logout") //退出 .logoutSuccessHandler(myAuthenticationService) //退出后处理 .and().authorizeRequests().antMatchers("/toLoginPage").permitAll() //放行登录页面 .anyRequest().authenticated() .and().rememberMe() //开启记住我功能 .tokenValiditySeconds(1209600) //token失效时间,默认失效时间是两周 .rememberMeParameter("remember-me") // 自定义表单name值 .tokenRepository(getPersistentTokenRepository()) //设置PersistentTokenRepository .and().headers().frameOptions().sameOrigin() //加载同源域名下iframe页面 .and().csrf().disable();//关闭csrf防护 } @Override public void configure(WebSecurity web) throws Exception { //解决静态资源被拦截的问题 web.ignoring().antMatchers("/css/**","/images/**","/js/**","/code/**"); } /** *身份安全管理器 * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserDetailsService); } @Autowired DataSource dataSource; /** * 负责token与数据库之间的操作 * @return */ @Bean public PersistentTokenRepository getPersistentTokenRepository(){ JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl(); tokenRepository.setDataSource(dataSource); //设置数据源 tokenRepository.setCreateTableOnStartup(false); //启动时帮助我们自动创建一张表,第一次启动设置为true,第二次启动程序的时候设置false或者注释掉; return tokenRepository; } @Autowired private MyAuthenticationService myAuthenticationService; }
标签:int,random,验证码,springframework,org,import,图形 From: https://www.cnblogs.com/popopopopo/p/17221167.html