Browse Source

initial implementation of registration captcha

master
Rohan Sircar 5 years ago
parent
commit
49f765737e
  1. 18
      chatto/src/main/java/org/ros/chatto/ChattoApplication.java
  2. 3
      chatto/src/main/java/org/ros/chatto/WebSecurityConfiguration.java
  3. 10
      chatto/src/main/java/org/ros/chatto/captcha/CaptchaBehaviour.java
  4. 37
      chatto/src/main/java/org/ros/chatto/captcha/ManualCaptchaBehaviour.java
  5. 183
      chatto/src/main/java/org/ros/chatto/captcha/SimpleCaptcha.java
  6. 40
      chatto/src/main/java/org/ros/chatto/captcha/SimpleCaptchaBehavior.java
  7. 26
      chatto/src/main/java/org/ros/chatto/captcha/WebCaptcha.java
  8. 16
      chatto/src/main/java/org/ros/chatto/captcha/WebCaptchaBuilder.java
  9. 58
      chatto/src/main/java/org/ros/chatto/controller/RegistrationController.java
  10. 25
      chatto/src/main/java/org/ros/chatto/dto/UserRegistrationDTO.java
  11. 26
      chatto/src/main/java/org/ros/chatto/service/CaptchaService.java
  12. BIN
      chatto/src/main/resources/pictures/A.png
  13. BIN
      chatto/src/main/resources/pictures/B.png
  14. BIN
      chatto/src/main/resources/pictures/C.png
  15. BIN
      chatto/src/main/resources/pictures/D.png
  16. BIN
      chatto/src/main/resources/pictures/E.png
  17. BIN
      chatto/src/main/resources/pictures/F.png
  18. BIN
      chatto/src/main/resources/pictures/G.png
  19. BIN
      chatto/src/main/resources/pictures/H.png
  20. BIN
      chatto/src/main/resources/pictures/I.png
  21. BIN
      chatto/src/main/resources/pictures/J.png
  22. BIN
      chatto/src/main/resources/pictures/K.png
  23. BIN
      chatto/src/main/resources/pictures/L.png
  24. BIN
      chatto/src/main/resources/pictures/M.png
  25. BIN
      chatto/src/main/resources/pictures/N.png
  26. BIN
      chatto/src/main/resources/pictures/O.png
  27. BIN
      chatto/src/main/resources/pictures/P.png
  28. BIN
      chatto/src/main/resources/pictures/Q.png
  29. BIN
      chatto/src/main/resources/pictures/R.png
  30. BIN
      chatto/src/main/resources/pictures/S.png
  31. BIN
      chatto/src/main/resources/pictures/T.png
  32. BIN
      chatto/src/main/resources/pictures/U.png
  33. BIN
      chatto/src/main/resources/pictures/V.png
  34. BIN
      chatto/src/main/resources/pictures/W.png
  35. BIN
      chatto/src/main/resources/pictures/X.png
  36. BIN
      chatto/src/main/resources/pictures/Y.png
  37. BIN
      chatto/src/main/resources/pictures/Z.png
  38. 9
      chatto/src/main/resources/templates/registration.html

18
chatto/src/main/java/org/ros/chatto/ChattoApplication.java

@ -2,13 +2,10 @@ package org.ros.chatto;
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.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
public class ChattoApplication extends SpringBootServletInitializer {
@ -16,6 +13,21 @@ public class ChattoApplication extends SpringBootServletInitializer {
public static void main(String[] args) throws SQLException {
SpringApplication application = new SpringApplication(ChattoApplication.class);
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

3
chatto/src/main/java/org/ros/chatto/WebSecurityConfiguration.java

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

10
chatto/src/main/java/org/ros/chatto/captcha/CaptchaBehaviour.java

@ -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();
}

37
chatto/src/main/java/org/ros/chatto/captcha/ManualCaptchaBehaviour.java

@ -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;
}
}

183
chatto/src/main/java/org/ros/chatto/captcha/SimpleCaptcha.java

@ -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;
}
}

40
chatto/src/main/java/org/ros/chatto/captcha/SimpleCaptchaBehavior.java

@ -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);
}
}

26
chatto/src/main/java/org/ros/chatto/captcha/WebCaptcha.java

@ -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);
}
}

16
chatto/src/main/java/org/ros/chatto/captcha/WebCaptchaBuilder.java

@ -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);
}
}

58
chatto/src/main/java/org/ros/chatto/controller/RegistrationController.java

@ -1,15 +1,31 @@
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 org.ros.chatto.dto.UserRegistrationDTO;
import org.ros.chatto.service.CaptchaService;
import org.ros.chatto.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
@ -18,9 +34,23 @@ public class RegistrationController {
@Autowired
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")
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";
}
@ -29,10 +59,32 @@ public class RegistrationController {
@ModelAttribute("userRegistrationDTO") @Valid UserRegistrationDTO userRegistrationDTO,
BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
System.out.println("Input has errors!");
logger.warn("Registration input has errors!");
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";
}
@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);
}
}

25
chatto/src/main/java/org/ros/chatto/dto/UserRegistrationDTO.java

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

26
chatto/src/main/java/org/ros/chatto/service/CaptchaService.java

@ -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();
}
}

BIN
chatto/src/main/resources/pictures/A.png

After

Width: 30  |  Height: 30  |  Size: 612 B

BIN
chatto/src/main/resources/pictures/B.png

After

Width: 30  |  Height: 30  |  Size: 603 B

BIN
chatto/src/main/resources/pictures/C.png

After

Width: 30  |  Height: 30  |  Size: 590 B

BIN
chatto/src/main/resources/pictures/D.png

After

Width: 30  |  Height: 30  |  Size: 561 B

BIN
chatto/src/main/resources/pictures/E.png

After

Width: 30  |  Height: 30  |  Size: 446 B

BIN
chatto/src/main/resources/pictures/F.png

After

Width: 30  |  Height: 30  |  Size: 391 B

BIN
chatto/src/main/resources/pictures/G.png

After

Width: 30  |  Height: 30  |  Size: 694 B

BIN
chatto/src/main/resources/pictures/H.png

After

Width: 30  |  Height: 30  |  Size: 466 B

BIN
chatto/src/main/resources/pictures/I.png

After

Width: 30  |  Height: 30  |  Size: 381 B

BIN
chatto/src/main/resources/pictures/J.png

After

Width: 30  |  Height: 30  |  Size: 439 B

BIN
chatto/src/main/resources/pictures/K.png

After

Width: 30  |  Height: 30  |  Size: 582 B

BIN
chatto/src/main/resources/pictures/L.png

After

Width: 30  |  Height: 30  |  Size: 387 B

BIN
chatto/src/main/resources/pictures/M.png

After

Width: 30  |  Height: 30  |  Size: 911 B

BIN
chatto/src/main/resources/pictures/N.png

After

Width: 30  |  Height: 30  |  Size: 606 B

BIN
chatto/src/main/resources/pictures/O.png

After

Width: 30  |  Height: 30  |  Size: 732 B

BIN
chatto/src/main/resources/pictures/P.png

After

Width: 30  |  Height: 30  |  Size: 588 B

BIN
chatto/src/main/resources/pictures/Q.png

After

Width: 30  |  Height: 30  |  Size: 830 B

BIN
chatto/src/main/resources/pictures/R.png

After

Width: 30  |  Height: 30  |  Size: 693 B

BIN
chatto/src/main/resources/pictures/S.png

After

Width: 30  |  Height: 30  |  Size: 775 B

BIN
chatto/src/main/resources/pictures/T.png

After

Width: 30  |  Height: 30  |  Size: 378 B

BIN
chatto/src/main/resources/pictures/U.png

After

Width: 30  |  Height: 30  |  Size: 656 B

BIN
chatto/src/main/resources/pictures/V.png

After

Width: 30  |  Height: 30  |  Size: 705 B

BIN
chatto/src/main/resources/pictures/W.png

After

Width: 30  |  Height: 30  |  Size: 755 B

BIN
chatto/src/main/resources/pictures/X.png

After

Width: 30  |  Height: 30  |  Size: 715 B

BIN
chatto/src/main/resources/pictures/Y.png

After

Width: 30  |  Height: 30  |  Size: 655 B

BIN
chatto/src/main/resources/pictures/Z.png

After

Width: 30  |  Height: 30  |  Size: 568 B

9
chatto/src/main/resources/templates/registration.html

@ -54,6 +54,15 @@
<label for="password-repeat">Repeat password: </label>
<input class="form-control" type="password" id="password-repeat" required>
</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">
<input class="form-control btn btn-secondary" type="submit" value="Submit">
</div>

Loading…
Cancel
Save