五、kaptcha实现图形验证码

Kaptcha是谷歌开源的可高度配置的实用验证码生成工具。

一、验证码配置

加入依赖:


com.github.penggle
kaptcha
2.3.2

 

生成验证码配置:

@Configuration
public class KaptchaConfig {

//DefaultKaptcha是Producer的实现类
@Bean
public DefaultKaptcha producer() {
Properties properties = new Properties();
properties.put("kaptcha.border", "no");
properties.put("kaptcha.textproducer.font.color", "black");
properties.put("kaptcha.textproducer.char.space", "5");
properties.put("kaptcha.textproducer.font.names", "Arial,Courier,cmr10,宋体,楷体,微软雅黑");
Config config = new Config(properties);
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
defaultKaptcha.setConfig(config);
return defaultKaptcha;
}
}

 

新增验证码controller:

@Controller
@Slf4j
public class KaptchaController {
@Autowired
private Producer producer;

@GetMapping("/verify_code")
public Mono createVerifyCode(ServerWebExchange webExchange) throws IOException {
//生成验证码字符文本
String verifyCode = producer.createText();
log.info("生成的验证码:{}", verifyCode);
/**
* 将验证码保存到session中
*/
webExchange.getSession().map(webSession -> {
webSession.getAttributes().put("kaptchaVerifyCode",verifyCode);
return Mono.empty();
}).subscribe();

// 转换流信息写出
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
BufferedImage image = producer.createImage(verifyCode);
try {
ImageIO.write(image, "jpeg", os);
} catch (IOException e) {
log.error("ImageIO write err", e);
return Mono.empty();
}

webExchange.getResponse().getHeaders().add("Content-type","image/jpeg");
/**
* 将验证码图片转成DataBuffer并写入到客户端
*/
Flux dataBufferFlux = DataBufferUtils.read(new ByteArrayResource(os.toByteArray()), webExchange.getResponse().bufferFactory(), 1024 * 8);

return webExchange.getResponse().writeAndFlushWith(t -> {
t.onSubscribe(new Subscription() { // 这步是必须的,不然可能生成不了验证码
@Override
public void request(long l) {

}

@Override
public void cancel() {

}
});
t.onNext(dataBufferFlux);
t.onComplete();
});
}
}

 

二、表单登录使用验证码

修改MyReactiveSecurityConfig配置类,将/verify_code放开权限校验:

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.pathMatchers(LOGIN_PAGE).permitAll()
.pathMatchers("/verify_code").permitAll()
.anyExchange().authenticated()
)
// .httpBasic().disable()
// .formLogin().disable()
.formLogin()
.loginPage(LOGIN_PAGE)
.and()
// .loginPage(LOGIN_PAGE)
// .and()
.csrf().disable();

http.addFilterAt(authenticationManager(), SecurityWebFiltersOrder.FORM_LOGIN);
return http.build();
}

 

自定义AuthenticationWebFilter:

@Bean
public ReactiveAuthenticationManager userDetailsRepositoryReactiveAuthenticationManager() {
UserDetailsRepositoryReactiveAuthenticationManager manager = new UserDetailsRepositoryReactiveAuthenticationManager(this.reactiveUserDetailsService);
manager.setPasswordEncoder(passwordEncoder());
manager.setUserDetailsPasswordService(this.userDetailsPasswordService);
return manager;
}

@Bean
public AuthenticationWebFilter authenticationManager() {
AuthenticationWebFilter authenticationFilter = new AuthenticationWebFilter(userDetailsRepositoryReactiveAuthenticationManager());
authenticationFilter.setRequiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, LOGIN_PAGE));
authenticationFilter.setAuthenticationFailureHandler(new RedirectServerAuthenticationFailureHandler(LOGIN_PAGE + "?error"));
authenticationFilter.setAuthenticationConverter(new KaptchServerAuthenticationConverter());
authenticationFilter.setAuthenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("/"));
authenticationFilter.setSecurityContextRepository(new WebSessionServerSecurityContextRepository());
return authenticationFilter;
}

 

增加验证码解析类:

@Slf4j
public class KaptchServerAuthenticationConverter extends ServerFormLoginAuthenticationConverter {

private ObjectMapper objectMapper = new ObjectMapper();

private String usernameParameter = "username";

private String passwordParameter = "password";

private String kaptchaParamter = "kaptchaVerifyCode";

public KaptchServerAuthenticationConverter() {

}

public KaptchServerAuthenticationConverter(String usernameParameter, String passwordParameter,String kaptchaParamter) {
this.usernameParameter = usernameParameter;
this.passwordParameter = passwordParameter;
this.kaptchaParamter = kaptchaParamter;
}

public Mono apply(ServerWebExchange exchange) {
return exchange.getFormData().map(data -> {
return data.getFirst(kaptchaParamter);
}).zipWith(exchange.getSession().map(webSession -> {
return webSession.getAttribute(kaptchaParamter);
})).map(t-> {
String t1 = t.getT1();
Object t2 = t.getT2();

log.info("前端传来的验证码:{},session中的验证码:{}", t1, t2);
if (!Objects.equals(t1, t2)) {
throw new KaptchaAuthenticationException("验证码不正确");
} else {
return Mono.empty();
}
}).flatMap(t -> {
return super.apply(exchange);
});
}
}

public class KaptchaAuthenticationException extends AuthenticationException {
public KaptchaAuthenticationException(String msg, Throwable cause) {
super(msg, cause);
}

public KaptchaAuthenticationException(String msg) {
super(msg);
}
}

调用父类super.apply解析用户名和密码,在解析用户名和密码之前对比前端传过来的验证码和WebSession中的验证码是否一致。不一致则抛出KaptchaAuthenticationException。

 

修改登录页doLogin.html:







Document















点击验证码图片可以刷新验证码。

三、JSON格式登录使用验证码

修改JsonServerAuthenticationConverter增加验证码判断:

@Slf4j
public class JsonServerAuthenticationConverter extends ServerFormLoginAuthenticationConverter {

private ObjectMapper objectMapper = new ObjectMapper();

private String usernameParameter = "username";

private String passwordParameter = "password";

private String kaptchaParamter = "kaptchaVerifyCode";

public JsonServerAuthenticationConverter() {

}

public JsonServerAuthenticationConverter(String usernameParameter,String passwordParameter, String kaptchaParamter) {
this.usernameParameter = usernameParameter;
this.passwordParameter = passwordParameter;
this.kaptchaParamter = kaptchaParamter;
}

public Mono apply(ServerWebExchange exchange) {

return exchange.getRequest().getBody().map(t -> {
String s = t.toString(StandardCharsets.UTF_8);
log.info("参数:{}",s);
Map<String , Object> map = null;
try {
map = objectMapper.readValue(s, Map.class);
} catch (JsonProcessingException e) {
log.info("解析JSON参数异常",e);
map = null;
}

return map;
}).zipWith(exchange.getSession().map(session -> {
return session.getAttribute(kaptchaParamter);
})).map(tuple -> {
Map<String, Object> map = tuple.getT1();
Object t2 = tuple.getT2();

if (map != null && !map.isEmpty()) {
log.info("前端传来的验证码:{},session中的验证码:{}", map.get(kaptchaParamter), t2);
if (!Objects.equals(map.get(kaptchaParamter),t2)) {
throw new KaptchaAuthenticationException("验证码不正确");
} else {
String username = (String) map.get(usernameParameter);
String password = (String) map.get(passwordParameter);

Authentication unauthenticated = UsernamePasswordAuthenticationToken.unauthenticated(username, password);
return unauthenticated;
}
}
return null;
}).next();
}
}

 

修改配置MyReactiveSecurityConfig:

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.pathMatchers(LOGIN_PAGE).permitAll()
.pathMatchers("/verify_code").permitAll()
.anyExchange().authenticated()
)
.httpBasic().disable()
.formLogin().disable()
.csrf().disable();

http.addFilterAt(authenticationManager(), SecurityWebFiltersOrder.FORM_LOGIN);
return http.build();
}

@Bean
public ReactiveAuthenticationManager userDetailsRepositoryReactiveAuthenticationManager() {
UserDetailsRepositoryReactiveAuthenticationManager manager = new UserDetailsRepositoryReactiveAuthenticationManager(this.reactiveUserDetailsService);
manager.setPasswordEncoder(passwordEncoder());
manager.setUserDetailsPasswordService(this.userDetailsPasswordService);
return manager;
}

@Bean
public AuthenticationWebFilter authenticationManager() {
AuthenticationWebFilter authenticationFilter = new AuthenticationWebFilter(userDetailsRepositoryReactiveAuthenticationManager());
authenticationFilter.setRequiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, LOGIN_PAGE));
authenticationFilter.setAuthenticationFailureHandler((webFilterExchange, exception) -> {
log.info("认证异常",exception);
ServerWebExchange exchange = webFilterExchange.getExchange();

exchange.getResponse().getHeaders().add("Content-type", MediaType.APPLICATION_JSON_UTF8_VALUE);

Flux dataBufferFlux = DataBufferUtils.read(new ByteArrayResource("认证失败".getBytes(StandardCharsets.UTF_8)), exchange.getResponse().bufferFactory(), 1024 * 8);

return exchange.getResponse().writeAndFlushWith(t -> {
t.onSubscribe(new Subscription() {
@Override
public void request(long l) {

}

@Override
public void cancel() {

}
});
t.onNext(dataBufferFlux);
t.onComplete();
});
});
authenticationFilter.setAuthenticationConverter(new JsonServerAuthenticationConverter());
authenticationFilter.setAuthenticationSuccessHandler((webFilterExchange, authentication) -> {
log.info("认证成功:{}",authentication);
ServerWebExchange exchange = webFilterExchange.getExchange();

exchange.getResponse().getHeaders().add("Content-type", MediaType.APPLICATION_JSON_UTF8_VALUE);

Flux dataBufferFlux = DataBufferUtils.read(new ByteArrayResource("认证成功".getBytes(StandardCharsets.UTF_8)), exchange.getResponse().bufferFactory(), 1024 * 8);

return exchange.getResponse().writeAndFlushWith(t -> {
t.onSubscribe(new Subscription() {
@Override
public void request(long l) {

}

@Override
public void cancel() {

}
});
t.onNext(dataBufferFlux);
t.onComplete();
});
});
authenticationFilter.setSecurityContextRepository(new WebSessionServerSecurityContextRepository());
return authenticationFilter;
}

 

用Postman调用http://localhost:8080/verify_code获取验证码,在调用http://localhost:8080/doLogin进行登录。

 

 

 

参考:https://blog.csdn.net/weixin_44014864/article/details/128629697
参考:https://blog.csdn.net/m0_47119893/article/details/122397803
参考:https://blog.csdn.net/wstever/article/details/128698695

posted @ 2023-06-07 20:32  shigp1  阅读(1084)  评论(0)    收藏  举报