1.实现验证码的基本参数可配置
修改application.yml
文件
imooc-security/imooc-security-demo/src/main/resources/application.yml
# 配置自定义登录页面
imooc:
security:
code:
image:
width: 70
height: 30
length: 4
expire: 120
创建ImageCodeProperties
配置文件
com.imooc.security.core.properties.ImageCodeProperties
package com.imooc.security.core.properties;
/**
* @Author:LovingLiu
* @Description:
* @Date:Created in 2019-12-23
*/
public class ImageCodeProperties {
private int width = 67;
private int height = 23;
private int length = 4;
private int expire = 60;
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getExpire() {
return expire;
}
public void setExpire(int expire) {
this.expire = expire;
}
}
创建ValidateCodeProperties
配置文件
com.imooc.security.core.properties.ValidateCodeProperties
package com.imooc.security.core.properties;
/**
* @Author:LovingLiu
* @Description:
* @Date:Created in 2019-12-23
*/
public class ValidateCodeProperties {
private ImageCodeProperties image = new ImageCodeProperties();
public ImageCodeProperties getImage() {
return image;
}
public void setImage(ImageCodeProperties image) {
this.image = image;
}
}
修改SecurityProperties
配置文件
com.imooc.security.core.properties.SecurityProperties
package com.imooc.security.core.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @Author:LovingLiu
* @Description:
* @Date:Created in 2019-12-22
*/
@ConfigurationProperties(prefix = "imooc.security")
public class SecurityProperties {
private BrowserProperties browser = new BrowserProperties();
private ValidateCodeProperties code = new ValidateCodeProperties();
public BrowserProperties getBrowser() {
return browser;
}
public void setBrowser(BrowserProperties browser) {
this.browser = browser;
}
public ValidateCodeProperties getCode() {
return code;
}
public void setCode(ValidateCodeProperties code) {
this.code = code;
}
}
修改ValidateCodeController
com.imooc.security.core.validate.code.ValidateCodeController
package com.imooc.security.core.validate.code;
import com.imooc.security.core.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;
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;
/**
* @Author:LovingLiu
* @Description:
* @Date:Created in 2019-12-22
*/
@RestController
public class ValidateCodeController {
public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
// session 操作
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
// 引入验证码的配置信息
@Autowired
private SecurityProperties securityProperties;
@GetMapping("/code/image")
public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 1.生成验证码
ImageCode imageCode = createImageCode(request);
// 2.将随机数存放在Session中
sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode);
// 3.将图片写回响应
ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream());
}
private ImageCode createImageCode(HttpServletRequest request){
/**
* 为应用级别的配置
* 1.先从请求中获取参数
* 2.若是没有再去配置文件中读取
* ServletRequestUtils HTTP请求的使用
*
*/
int width = ServletRequestUtils.getIntParameter(request,"width",securityProperties.getCode().getImage().getWidth());
int height = ServletRequestUtils.getIntParameter(request,"height",securityProperties.getCode().getImage().getHeight());
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
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);
}
String sRand = "";
for (int i = 0; i < securityProperties.getCode().getImage().getLength(); i++) {
String rand = String.valueOf(random.nextInt(10));
sRand += 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, securityProperties.getCode().getImage().getExpire());
}
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);
}
}
2.实现拦截参数可配
修改application.yml
文件
# 配置自定义登录页面
imooc:
security:
code:
image:
url: /user,/user/*
修改配置文件ImageCodeProperties
com.imooc.security.core.properties.ImageCodeProperties
package com.imooc.security.core.properties;
/**
* @Author:LovingLiu
* @Description:
* @Date:Created in 2019-12-23
*/
public class ImageCodeProperties {
private int width = 67;
private int height = 23;
private int length = 4;
private int expire = 60;
private String url = ""; // 拦截的请求URL
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getExpire() {
return expire;
}
public void setExpire(int expire) {
this.expire = expire;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
修改ValidateCodeFilter
过滤器
com.imooc.security.core.validate.code.ValidateCodeFilter
修改验证url的逻辑
package com.imooc.security.core.validate.code;
import com.imooc.security.core.properties.SecurityProperties;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
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;
import java.util.HashSet;
import java.util.Set;
/**
* @Author:LovingLiu
* @Description:
* 自定义验证码的过滤器,根据请求判断是哪一种验证
* OncePerRequestFilter保证每次请求只被调用一次
* InitializingBean接口的实现方法afterPropertiesSet()会在bean在初始化后自动调用且在init-method之前调用
* @Date:Created in 2019-12-22
*/
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {
private Logger log = LoggerFactory.getLogger(getClass());
private AuthenticationFailureHandler authenticationFailureHandler;
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
private Set<String> urls = new HashSet<>(); // 可配置的可拦截的请求URL
private SecurityProperties securityProperties; // 由Set方法传入
private AntPathMatcher antPathMatcher = new AntPathMatcher(); // 工具类
@Override
public void afterPropertiesSet() throws ServletException {
super.afterPropertiesSet();
String[] configUrls = StringUtils.split(securityProperties.getCode().getImage().getUrl(),",");
for (String config: configUrls) {
urls.add(config);
log.info("【afterPropertiesSet自动调用】=> {}",config);
}
urls.add("/authentication/form");
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
boolean action = false;
for (String url: urls) {
if(antPathMatcher.match(url,request.getRequestURI())){
action = true;
}
}
if(action){
try{
valicate(new ServletWebRequest(request));
}catch (ValidateCodeException e){
authenticationFailureHandler.onAuthenticationFailure(request,response,e);
return;// 直接返回
}
}
//继续执行沿着Filter链执行
filterChain.doFilter(request, response);
}
private void valicate(ServletWebRequest request) throws ServletRequestBindingException {
ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request,ValidateCodeController.SESSION_KEY);
// 获取请求参数
String codeInRequest;
try {
codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(),
"imageCode");
} catch (ServletRequestBindingException e) {
throw new ValidateCodeException("获取验证码的值失败");
}
if (StringUtils.isBlank(codeInRequest)) {
throw new ValidateCodeException("验证码的值不能为空");
}
if (codeInSession == null) {
throw new ValidateCodeException("验证码不存在");
}
if (codeInSession.isExpried()) {
sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
throw new ValidateCodeException("验证码已过期");
}
if (!StringUtils.equals(codeInSession.getCode(), codeInRequest)) {
throw new ValidateCodeException("验证码不匹配");
}
sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
}
public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
this.authenticationFailureHandler = authenticationFailureHandler;
}
public SecurityProperties getSecurityProperties() {
return securityProperties;
}
public void setSecurityProperties(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
}
修改配置文件BrowserSecurityConfig
com.imooc.security.browser.BrowserSecurityConfig
注入urls
属性和securityProperties
package com.imooc.security.browser;
import com.imooc.security.core.properties.SecurityProperties;
import com.imooc.security.core.validate.code.ValidateCodeFilter;
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.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @Author:LovingLiu
* @Description: Security安全配置的适配器
* @Date:Created in 2019-12-18
*/
@Configuration
@EnableWebSecurity
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SecurityProperties securityProperties;
@Autowired
private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler imoocAuthenticationFailureHandler;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(); // 推荐使用 BCrypt 的加密的形式
}
@Override
protected void configure(HttpSecurity http) throws Exception {
ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
validateCodeFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);
validateCodeFilter.setSecurityProperties(securityProperties);
validateCodeFilter.afterPropertiesSet(); // 设置配置的属性
http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class). // 添加自定义过滤器
formLogin(). // 声明验证方式为表单登录
loginPage("/authentication/require"). // 自定义登录界面
loginProcessingUrl("/authentication/form"). // 指定接收username/password的请求路径
successHandler(imoocAuthenticationSuccessHandler). // 使用自定义成功处理器
failureHandler(imoocAuthenticationFailureHandler). // 使用自定义失败处理器
and(). //
authorizeRequests(). // 对请求进行授权
antMatchers("/authentication/require","/authentication/form","/code/image",securityProperties.getBrowser().getLoginPage()).permitAll().// 配置不进行认证请求
anyRequest(). // 任意请求
authenticated(). // 都需要身份认证
and().
csrf().disable(); // 关闭跨站请求防护
}
}
InitializingBean
接口的使用
1、Spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现afterPropertiesSet方法,或者在配置文件中通过init-method指定,两种方式可以同时使用。
2、实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率要高一点,但是init-method方式消除了对spring的依赖。
3、如果调用afterPropertiesSet方法时出错,则不调用init-method指定的方法。
3.验证码生成逻辑可配置
非常重要,这里将会体现出一个增量编程
,即:在不改变当前代码所拥有的功能下,进行功能的递增。
创建接口ValidateCodeGenerator
com.imooc.security.core.validate.code.ValidateCodeGenerator
定义生成验证码图片的方法
package com.imooc.security.core.validate.code;
import javax.servlet.http.HttpServletRequest;
/**
* @Author:LovingLiu
* @Description: 自定义生成验证码的逻辑
* @Date:Created in 2019-12-23
*/
public interface ValidateCodeGenerator {
ImageCode createImageCode(HttpServletRequest request);
}
创建验证码生成器的默认实现ImageCodeGenerator
com.imooc.security.core.validate.code.ImageCodeGenerator
实际上就是将原来生成验证码的代码抽离出来
package com.imooc.security.core.validate.code;
import com.imooc.security.core.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.ServletRequestUtils;
import javax.servlet.http.HttpServletRequest;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
/**
* @Author:LovingLiu
* @Description: 自定义生成验证码的逻辑(默认实现)
* 注意: 这里不写@Component 与后面的 @ConditionalOnMissingBean 相辅相成
* @Date:Created in 2019-12-23
*/
public class ImageCodeGenerator implements ValidateCodeGenerator {
// 引入验证码的配置信息
@Autowired
private SecurityProperties securityProperties;
@Override
public ImageCode createImageCode(HttpServletRequest request){
/**
* 为应用级别的配置
* 1.先从请求中获取参数
* 2.若是没有再去配置文件中读取
* ServletRequestUtils HTTP请求的使用
*
*/
int width = ServletRequestUtils.getIntParameter(request,"width",securityProperties.getCode().getImage().getWidth());
int height = ServletRequestUtils.getIntParameter(request,"height",securityProperties.getCode().getImage().getHeight());
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
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);
}
String sRand = "";
for (int i = 0; i < securityProperties.getCode().getImage().getLength(); i++) {
String rand = String.valueOf(random.nextInt(10));
sRand += 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, securityProperties.getCode().getImage().getExpire());
}
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);
}
public SecurityProperties getSecurityProperties() {
return securityProperties;
}
public void setSecurityProperties(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
}
新增ValidateCodeBeanConfig
配置类
package com.imooc.security.core.validate.code;
import com.imooc.security.core.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author:LovingLiu
* @Description: 生成验证码逻辑的相关配置
* @Date:Created in 2019-12-23
*/
@Configuration
public class ValidateCodeBeanConfig {
@Autowired
private SecurityProperties securityProperties;
/**
* @ConditionalOnMissingBean注解作用在@bean定义上
* 可以给该注解传入参数例如@ConditionOnMissingBean(name = "example")
* 这个表示如果name为“example”的bean存在,这该注解修饰的代码块不执行
* @return
*/
@Bean
@ConditionalOnMissingBean(name = "imageCodeGenerator")
public ValidateCodeGenerator imageCodeGenerator(){
ImageCodeGenerator codeGenerator = new ImageCodeGenerator();
codeGenerator.setSecurityProperties(securityProperties);
return codeGenerator;
}
}
修改ValidateCodeController
获取验证码逻辑
com.imooc.security.core.validate.code.ValidateCodeController
package com.imooc.security.core.validate.code;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Author:LovingLiu
* @Description: 获取验证码图片的Controller
* @Date:Created in 2019-12-22
*/
@RestController
public class ValidateCodeController {
public static final String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
// session 操作
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
// 生成验证码逻辑可配置 改造
@Autowired
private ValidateCodeGenerator validateCodeGenerator;
@GetMapping("/code/image")
public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 1.生成验证码
ImageCode imageCode = validateCodeGenerator.createImageCode(request);
// 2.将随机数存放在Session中
sessionStrategy.setAttribute(new ServletWebRequest(request),SESSION_KEY,imageCode);
// 3.将图片写回响应
ImageIO.write(imageCode.getImage(),"JPEG",response.getOutputStream());
}
}
床架自定义生成验证码的逻辑DemoImageCodeGenerator
com.imooc.code.DemoImageCodeGenerator
package com.imooc.code;
import com.imooc.security.core.validate.code.ImageCode;
import com.imooc.security.core.validate.code.ValidateCodeGenerator;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.ServletRequestUtils;
import javax.servlet.http.HttpServletRequest;
import java.awt.image.BufferedImage;
/**
* @Author:LovingLiu
* @Description: 自定义生成验证码的逻辑
* @Component("imageCodeGenerator") 覆盖默认的实现
* @Date:Created in 2019-12-23
*/
@Component("imageCodeGenerator")
public class DemoImageCodeGenerator implements ValidateCodeGenerator {
@Override
public ImageCode createImageCode(HttpServletRequest request) {
System.out.println("更高级的图形验证码的生成代码");
int width = ServletRequestUtils.getIntParameter(request,"width",150);
int height = ServletRequestUtils.getIntParameter(request,"height",100);
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
return new ImageCode(image, "1111", 60);
}
}
网友评论