initial implementation of registration captcha

This commit is contained in:
Rohan Sircar 2019-11-26 11:32:40 +05:30
parent ea4b2eb1b1
commit 49f765737e
38 changed files with 427 additions and 24 deletions

View File

@ -2,13 +2,10 @@ package org.ros.chatto;
import java.sql.SQLException; import java.sql.SQLException;
import org.ros.chatto.service.DBInitializerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication @SpringBootApplication
public class ChattoApplication extends SpringBootServletInitializer { public class ChattoApplication extends SpringBootServletInitializer {
@ -16,6 +13,21 @@ public class ChattoApplication extends SpringBootServletInitializer {
public static void main(String[] args) throws SQLException { public static void main(String[] args) throws SQLException {
SpringApplication application = new SpringApplication(ChattoApplication.class); SpringApplication application = new SpringApplication(ChattoApplication.class);
application.run(); application.run();
// WebCaptcha webCaptcha = WebCaptcha.builder().captchaBehaviour(new SimpleCaptchaBehavior()).build();
// webCaptcha.generateCaptcha();
//
// // @formatter:off
// webCaptcha = WebCaptcha.builder()
// .captchaBehaviour(
// ManualCaptchaBehaviour.builder()
// .length(8)
// .style("black")
// .build()
// ).build();
//
// // @formatter:on
} }
@Override @Override

View File

@ -77,7 +77,8 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
.anyRequest() .anyRequest()
// .hasAnyRole("USER", "ADMIN", "SUPER_USER") // .hasAnyRole("USER", "ADMIN", "SUPER_USER")
.authenticated().and().httpBasic().authenticationEntryPoint(authenticationEntryPoint) .authenticated()
.and().httpBasic().authenticationEntryPoint(authenticationEntryPoint)
// .and() // .and()
// .logout().invalidateHttpSession(true).clearAuthentication(true) // .logout().invalidateHttpSession(true).clearAuthentication(true)
// .logoutRequestMatcher(new AntPathRequestMatcher("/api/perform_logout")) // .logoutRequestMatcher(new AntPathRequestMatcher("/api/perform_logout"))

View File

@ -0,0 +1,10 @@
package org.ros.chatto.captcha;
import java.awt.image.BufferedImage;
interface CaptchaBehaviour {
public BufferedImage generateCaptcha();
public BufferedImage generateCaptcha(String captchaText);
public String getRandomChars(int size);
public String getRandomChars();
}

View File

@ -0,0 +1,37 @@
package org.ros.chatto.captcha;
import java.awt.image.BufferedImage;
import lombok.Builder;
/*Class for providing your own captcha generator*/
@Builder
public class ManualCaptchaBehaviour implements CaptchaBehaviour{
private final int length;
private final String style;
@Override
public BufferedImage generateCaptcha() {
// TODO Auto-generated method stub
return null;
}
@Override
public BufferedImage generateCaptcha(String captchaText) {
// TODO Auto-generated method stub
return null;
}
@Override
public String getRandomChars(int size) {
// TODO Auto-generated method stub
return null;
}
@Override
public String getRandomChars() {
// TODO Auto-generated method stub
return null;
}
}

View File

@ -0,0 +1,183 @@
package org.ros.chatto.captcha;
import javax.imageio.ImageIO;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
/**
* This class represents a simple captcha consisting
* of an image {@code png} and its text value.
* Comic Neue Bold Font.
* Capital english letters {@code ONLY}.
*
* @since 1.3
* @author Gennadiy Golovin
*/
public final class SimpleCaptcha {
private BufferedImage imagePng;
private char[] text;
/**
* Initializes a newly created default object
* consisting of 8 capital english letters.
*/
public SimpleCaptcha() {
this.text = getRandomChars();
try {
generateCaptcha();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Initializes a newly created object, which length
* depends on the passed {@code int} parameter,
* which {@code MUST} be greater than 0.
* If the condition is not met, initializes a newly
* created default object consisting of 8 symbols.
*
* @param length the quantity of symbols, that the
* captcha consists of, greater than 0.
*/
public SimpleCaptcha(int length) {
if (length < 1) {
this.text = getRandomChars();
} else {
this.text = getRandomChars(length);
}
try {
generateCaptcha();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Initializes a newly created object based on the passed
* {@link String} parameter, consisting of capital english
* letters. If the condition is not met, initializes a newly
* created default object consisting of 8 capital english letters.
*
* @param text the text string with the value of the captcha,
* length greater than 0.
*/
public SimpleCaptcha(String text) {
if (text == null || text.equals("")) {
this.text = getRandomChars();
} else {
this.text = text.toCharArray();
}
try {
generateCaptcha();
} catch (IOException e) {
this.text = getRandomChars();
try {
generateCaptcha();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
/**
* Returns the picture with captcha
*
* @return {@link BufferedImage}
*/
public BufferedImage getImagePng() {
return imagePng;
}
/**
* Returns the text value of the captcha
*
* @return {@link String}
*/
public String getText() {
return String.valueOf(text);
}
//////// //////// //////// //////// //////// //////// //////// ////////
private char[] getRandomChars() {
return getRandomChars(8);
}
private char[] getRandomChars(int quantity) {
char[] randomString = new char[quantity];
Random random = new Random();
int capitalLetter;
for (int i = 0; i < quantity; i++) {
capitalLetter = 65 + random.nextInt(26);
randomString[i] = (char) capitalLetter;
}
return randomString;
}
private void generateCaptcha() throws IOException {
int charsQuantity = this.text.length;
BufferedImage[] images = new BufferedImage[charsQuantity];
for (int i = 0; i < charsQuantity; i++) {
images[i] = ImageIO.read(SimpleCaptcha.class.getResourceAsStream("/pictures/" + this.text[i] + ".png"));
if (i % 2 == 0) {
images[i] = rotateImage(images[i], 25);
} else {
images[i] = rotateImage(images[i], -20);
}
}
int imageSize = 30;
int rotatedImageSize = (int) Math.sqrt(imageSize * imageSize * 2);
BufferedImage captchaImg = new BufferedImage(rotatedImageSize * (charsQuantity - 1) / 10 * 6 + rotatedImageSize, rotatedImageSize, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics2d = captchaImg.createGraphics();
graphics2d.setBackground(Color.WHITE);
graphics2d.clearRect(0, 0, captchaImg.getWidth(), captchaImg.getHeight());
for (int i = 0; i < charsQuantity; i++) {
captchaImg.getGraphics().drawImage(images[i], rotatedImageSize * i / 10 * 6, 0, null);
}
graphics2d.dispose();
this.imagePng = captchaImg;
}
private BufferedImage rotateImage(BufferedImage buffImage, double angle) {
double radian = Math.toRadians(angle);
double sin = Math.abs(Math.sin(radian));
double cos = Math.abs(Math.cos(radian));
int width = buffImage.getWidth();
int height = buffImage.getHeight();
int nWidth = (int) Math.floor((double) width * cos + (double) height * sin);
int nHeight = (int) Math.floor((double) height * cos + (double) width * sin);
BufferedImage rotatedImage = new BufferedImage(nWidth, nHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = rotatedImage.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
graphics.translate((nWidth - width) / 2, (nHeight - height) / 2);
graphics.rotate(radian, (double) (width / 2), (double) (height / 2));
graphics.drawImage(buffImage, 0, 0,null);
graphics.dispose();
return rotatedImage;
}
}

View File

@ -0,0 +1,40 @@
package org.ros.chatto.captcha;
import java.awt.image.BufferedImage;
import java.util.Random;
public class SimpleCaptchaBehavior implements CaptchaBehaviour {
@Override
public BufferedImage generateCaptcha() {
// TODO Auto-generated method stub
SimpleCaptcha simpleCaptcha = new SimpleCaptcha();
return simpleCaptcha.getImagePng();
}
@Override
public BufferedImage generateCaptcha(String captchaText) {
// TODO Auto-generated method stub
SimpleCaptcha simpleCaptcha = new SimpleCaptcha(captchaText);
return simpleCaptcha.getImagePng();
}
public String getRandomChars() {
return getRandomChars(8);
}
public String getRandomChars(int quantity)
{
char[] randomString = new char[quantity];
Random random = new Random();
int capitalLetter;
for (int i = 0; i < quantity; i++) {
capitalLetter = 65 + random.nextInt(26);
randomString[i] = (char) capitalLetter;
}
return new String(randomString);
}
}

View File

@ -0,0 +1,26 @@
package org.ros.chatto.captcha;
import java.awt.image.BufferedImage;
import lombok.Builder;
@Builder
public class WebCaptcha {
private final CaptchaBehaviour captchaBehaviour;
public BufferedImage generateCaptcha() {
return captchaBehaviour.generateCaptcha();
}
public BufferedImage generateCaptcha(String captchaText) {
return captchaBehaviour.generateCaptcha(captchaText);
}
public String getRandomChars() {
return captchaBehaviour.getRandomChars();
}
public String getRandomChars(int quantity) {
return captchaBehaviour.getRandomChars(quantity);
}
}

View File

@ -0,0 +1,16 @@
package org.ros.chatto.captcha;
import lombok.Builder;
@Builder
public class WebCaptchaBuilder {
private CaptchaBehaviour captchaBehaviour;
// public WebCaptchaBuilder(CaptchaBehaviour captchaBehaviour)
// {
// this.captchaBehaviour = captchaBehaviour;
// }
public WebCaptcha build()
{
return new WebCaptcha(captchaBehaviour);
}
}

View File

@ -1,15 +1,31 @@
package org.ros.chatto.controller; package org.ros.chatto.controller;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import javax.imageio.ImageIO;
import javax.validation.Valid; import javax.validation.Valid;
import org.ros.chatto.dto.UserRegistrationDTO; import org.ros.chatto.dto.UserRegistrationDTO;
import org.ros.chatto.service.CaptchaService;
import org.ros.chatto.service.UserService; import org.ros.chatto.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@Controller @Controller
@ -18,9 +34,23 @@ public class RegistrationController {
@Autowired @Autowired
private UserService userService; private UserService userService;
@Autowired
private CaptchaService captchaService;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final Map<Long, String> captchaMap = new ConcurrentHashMap<>();
@GetMapping("/registration") @GetMapping("/registration")
public String registrationForm(Model model) { public String registrationForm(Model model) {
model.addAttribute("userRegistrationDTO", new UserRegistrationDTO()); UserRegistrationDTO userRegistrationDTO = new UserRegistrationDTO();
String captchaText = captchaService.getRandomText();
userRegistrationDTO.setCaptchaText(captchaText);
logger.debug("captcha text = {}", captchaText);
Long captchaID = ThreadLocalRandom.current().nextLong();
userRegistrationDTO.setCaptchaID(captchaID);
captchaMap.put(captchaID, captchaText);
model.addAttribute("userRegistrationDTO", userRegistrationDTO);
return "registration"; return "registration";
} }
@ -29,10 +59,32 @@ public class RegistrationController {
@ModelAttribute("userRegistrationDTO") @Valid UserRegistrationDTO userRegistrationDTO, @ModelAttribute("userRegistrationDTO") @Valid UserRegistrationDTO userRegistrationDTO,
BindingResult bindingResult) { BindingResult bindingResult) {
if (bindingResult.hasErrors()) { if (bindingResult.hasErrors()) {
System.out.println("Input has errors!"); logger.warn("Registration input has errors!");
return "registration"; return "registration";
} }
userService.registerUser(userRegistrationDTO); logger.debug("Captcha text from user input = {}", userRegistrationDTO.getCaptchaInput());
logger.debug("Captcha text from captcha map = {}", captchaMap.get(userRegistrationDTO.getCaptchaID()));
if (userRegistrationDTO.getCaptchaInput().equals(captchaMap.get(userRegistrationDTO.getCaptchaID()))) {
logger.info("Registration captcha equal success");
} else {
logger.warn("Registration captcha equal fail");
}
// userService.registerUser(userRegistrationDTO);
return "user/home"; return "user/home";
} }
@GetMapping(value = "/img/{image_id}", produces = MediaType.IMAGE_PNG_VALUE)
public ResponseEntity<byte[]> getImage(@PathVariable("image_id") Long imageId) throws IOException {
final String captchaText = captchaMap.get(imageId);
final HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.IMAGE_PNG);
BufferedImage captchaBufferedImage = captchaService.createCaptchaImage(captchaText);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(captchaBufferedImage, "png", baos);
byte[] imageBytes = baos.toByteArray();
return new ResponseEntity<byte[]>(imageBytes, headers, HttpStatus.OK);
}
} }

View File

@ -5,6 +5,9 @@ import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern; import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size; import javax.validation.constraints.Size;
import lombok.Data;
@Data
public class UserRegistrationDTO { public class UserRegistrationDTO {
@Size(min = 4, max = 10, message = "Username must be between 4 and 10 characters") @Size(min = 4, max = 10, message = "Username must be between 4 and 10 characters")
@NotBlank(message = "Username should not be blank") @NotBlank(message = "Username should not be blank")
@ -16,20 +19,8 @@ public class UserRegistrationDTO {
// @Pattern(regexp = "^.*(?=.{6,})(?=.*d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$", message = "Invalid password format") // @Pattern(regexp = "^.*(?=.{6,})(?=.*d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$", message = "Invalid password format")
private String password; private String password;
public String getUserName() { private Long captchaID;
return userName; private String captchaText;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
private String captchaInput;
} }

View File

@ -0,0 +1,26 @@
package org.ros.chatto.service;
import java.awt.image.BufferedImage;
import org.ros.chatto.captcha.SimpleCaptchaBehavior;
import org.ros.chatto.captcha.WebCaptcha;
import org.springframework.stereotype.Service;
@Service
public class CaptchaService {
private final WebCaptcha webCaptcha;
public CaptchaService() {
webCaptcha = WebCaptcha.builder().captchaBehaviour(new SimpleCaptchaBehavior()).build();
}
public BufferedImage createCaptchaImage(String captchaText)
{
return webCaptcha.generateCaptcha(captchaText);
}
public String getRandomText()
{
return webCaptcha.getRandomChars();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 911 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

View File

@ -54,6 +54,15 @@
<label for="password-repeat">Repeat password: </label> <label for="password-repeat">Repeat password: </label>
<input class="form-control" type="password" id="password-repeat" required> <input class="form-control" type="password" id="password-repeat" required>
</div> </div>
<input type="hidden" th:value="${userRegistrationDTO.captchaID}" name="captchaID">
<!-- <span th:text="${userRegistrationDTO.captchaText}"></span> -->
<div class="form-group">
<label for="captcha">Enter this captcha:
<img th:src="@{'/img/' + ${userRegistrationDTO.captchaID}}" />
</label>
<input class="form-control" type="text" id="captcha" th:field="*{captchaInput}" required>
</div>
<div class="form-group"> <div class="form-group">
<input class="form-control btn btn-secondary" type="submit" value="Submit"> <input class="form-control btn btn-secondary" type="submit" value="Submit">
</div> </div>