<code> @Data
public class ImageCode {
private BufferedImage image;
private String code;
private LocalDateTime expireTime;
public ImageCode() {
}
public ImageCode(BufferedImage image, String code, int expireIn) {
this.image = image;
this.code = code;
this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
}
public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) {
this.image = image;
this.code = code;
this.expireTime = expireTime;
}
public boolean isExpire() {
return LocalDateTime.now().isAfter(expireTime);
}
}/<code>
生成图像
<code> /***
**图像生成
*/
private ImageCode createImageCode() {
int width = 100; // 验证码图片宽度
int height = 36; // 验证码图片长度
int length = 4; // 验证码位数
int expireIn = 120; // 验证码有效时间 120s
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);
}
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(), expireIn);
}
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);
}/<code>
接口:
<code>@GetMapping("/code/image")
public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
ImageCode imageCode = createImageCode();
//存放到redis或session中
ImageIO.write(imageCode.getImage(), "jpeg", response.getOutputStream());
}/<code>
界面获取
<code>
/<code>
校验验证码
<code>/**
* @program: security_root
* @description: 验证码校验类
* @author: xs-shuai.com
* @create: 2020-02-27 14:19
**/
@Configuration
public class ValidateCodeFilter extends OncePerRequestFilter {
/****
* 失败处理
*/
@Autowired
private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
//注入redis 或session
/***
* 处理验证码校验是否正确
* @param httpServletRequest
* @param httpServletResponse
* @param filterChain
*/
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
if (StringUtils.equalsIgnoreCase("/login", httpServletRequest.getRequestURI())
&& StringUtils.equalsIgnoreCase(httpServletRequest.getMethod(), "post")) {
try {
validateCode(new ServletWebRequest(httpServletRequest));
} catch (ValidateCodeException e) {
myAuthenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, e);
return;
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
/**
**校验验证码 抛出异常 验证码错误 或验证码为空
*/
private void validateCode(ServletWebRequest servletWebRequest) throws ServletRequestBindingException {
// ImageCode codeInSession = 获取到请求时放入的验证码
String codeInRequest = ServletRequestUtils.getStringParameter(servletWebRequest.getRequest(), "imageCode");
if (StringUtils.isBlank(codeInRequest)) {
throw new ValidateCodeException("验证码不能为空!");
}
if (codeInSession == null) {
throw new ValidateCodeException("验证码不存在!");
}
if (codeInSession.isExpire()) {
//认证成功删除保存的验证码对象
throw new ValidateCodeException("验证码已过期!");
}
if (!StringUtils.equalsIgnoreCase(codeInSession.getCode(), codeInRequest)) {
throw new ValidateCodeException("验证码不正确!");
}
sessionStrategy.removeAttribute(servletWebRequest, ValidateController.SESSION_KEY_IMAGE_CODE);
}
}
/<code>
修改配置
<code>@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
//认证成功的handler 跳转首页或提示页面后后跳转
@Autowired
private MyAuthenticationSuccessHandler authenticationSuccessHandler;
//认证失败的handler 提示失败信息
@Autowired
private MyAuthenticationFailureHandler authenticationFailureHandler;
//验证码验证的filter
@Autowired
private ValidateCodeFilter validateCodeFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
//密码验证前加入
http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin() // 表单登录
// http.httpBasic() // HTTP Basic
.loginPage("/login.html") // 登录跳转 URL
.loginProcessingUrl("/login") // 处理表单登录 URL
.successHandler(authenticationSuccessHandler) // 处理登录成功
.failureHandler(authenticationFailureHandler) // 处理登录失败
.and()
.authorizeRequests() // 授权配置
.antMatchers("/authentication/require","/code/image", "/login.html").permitAll() // 登录跳转 URL 无需认证
.anyRequest() // 所有请求
.authenticated() // 都需要认证
.and().csrf().disable();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
/<code>
记住我
配置数据源
<code>//数据源配置
spring:
datasource:
url: jdbc:mysql://192.168.0.109:3307/security_learn?useUnicode=yes&characterEncoding=UTF-8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
/<code>
持久化到数据源 创建表结构
<code>CREATE TABLE persistent_logins (
username VARCHAR (64) NOT NULL,
series VARCHAR (64) PRIMARY KEY,
token VARCHAR (64) NOT NULL,
last_used TIMESTAMP NOT NULL
)/<code>
表单实现 注意:name一定是remeber-me
<code> 记住我/<code>
配置类
<code>@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyAuthenticationSucessHandler authenticationSucessHandler;
@Autowired
private MyAuthenticationFailureHandler authenticationFailureHandler;
@Autowired
private ValidateCodeFilter validateCodeFilter;
@Autowired
private DataSource dataSource;
@Autowired
private UserDetailService userDetailService;
/**
PersistentTokenRepository为一个接口类,
这里我们用的是数据库持久化,所以实例用的是PersistentTokenRepository的实现类JdbcTokenRepositoryImpl。
JdbcTokenRepositoryImpl需要指定数据源,所以我们将配置好的数据源对象DataSource
注入进来并配置到JdbcTokenRepositoryImpl的dataSource属性中。
createTableOnStartup属性用于是否启动项目时创建保存token信息的数据表,这里设置为false,我们自己手动创建
*/
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
jdbcTokenRepository.setCreateTableOnStartup(false);
return jdbcTokenRepository;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//密码验证前加入
http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin() // 表单登录
// http.httpBasic() // HTTP Basic
.loginPage("/login.html") // 登录跳转 URL
.loginProcessingUrl("/login") // 处理表单登录 URL
.successHandler(authenticationSucessHandler) // 处理登录成功
.failureHandler(authenticationFailureHandler) // 处理登录失败
.and()
//>>>>>>>>记住我的配置<<<<
.rememberMe()
.tokenRepository(persistentTokenRepository()) // 配置 token 持久化仓库
.tokenValiditySeconds(3600) // remember 过期时间,单为秒
.userDetailsService(userDetailService) // 处理自动登录逻辑
.and()
.authorizeRequests() // 授权配置
.antMatchers("/authentication/require","/code/image", "/login.html").permitAll() // 登录跳转 URL 无需认证
.anyRequest() // 所有请求
.authenticated() // 都需要认证
.and().csrf().disable();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
/<code>
閱讀更多 編程的boy 的文章