I am currently working on a Spring Boot project with Thymeleaf.
Using the following form should send the data to a REST endpoint:
<form th:action="@{/post/create}" method="post" th:object="${postDto}" accept-charset="UTF-8">
<div class="mb-3">
<label for="title" class="form-label">Titel</label>
<input type="text" class="form-control" id="title" name="title" th:field="*{title}" required>
</div>
<div class="mb-3">
<label for="content" class="form-label">Beschreibung</label>
<textarea class="form-control" id="content" name="content" rows="4" th:field="*{content}" required></textarea>
</div>
<div class="mb-3">
<label for="event" class="form-label">Event</label>
<select class="form-control" id="event" name="event" th:field="*{eventId}">
<option th:value="${0}">Kein Event</option>
<option th:each="event : ${events}" th:value="${event.id}" th:text="${event.name} + ' - ' + ${event.getClassName()}"></option>
</select>
</div>
<div class="mb-3">
<label for="topic" class="form-label">Thema</label>
<select class="form-control" id="topic" name="topic" th:field="*{topicId}">
<option th:value="${0}">Kein Thema</option>
<option th:each="topic : ${topics}" th:value="${topic.id}" th:text="${topic.name}"></option>
</select>
</div>
<div class="mb-3">
<label for="visibility" class="form-label">Sichtbarkeit</label>
<select class="form-control" id="visibility" name="visibility" th:field="*{visibility}">
<option th:value="${0}">Für alle sichtbar</option>
<option th:each="role : ${roles}" th:value="${role.getVisibilityScore()}" th:text="${role.getVisibilityScore()} + ' - ' + ${role.name}"></option>
</select>
</div>
<div class="modal-footer">
<input type="hidden" name="_csrf" value="${_csrf.token}"/>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
<button type="submit" class="btn btn-primary">Absenden</button>
</div>
</form>
The REST-Endpoint is receiving the data and should create a Post object in the ServiceImpl:
@PostMapping("/post/create")
public String createPost(@ModelAttribute @Valid PostDto postDto,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute(ERROR_MESSAGE_ATTRIBUTE, "Es gab Probleme, den Beitrag anzulegen. Versuche es erneut.");
return TEMPLATE_LOCATION;
}
try {
postService.savePost(postDto);
model.addAttribute(SUCCESS_MESSAGE_ATTRIBUTE, "Beitrag wurde erstellt!");
} catch (RuntimeException e) {
model.addAttribute(ERROR_MESSAGE_ATTRIBUTE, e.getMessage());
}
return TEMPLATE_LOCATION;
}
PostDto:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PostDto {
@NotNull
@NotEmpty
private String title;
@Lob
@Column(length = 100000)
@NotNull
@NotEmpty
private String content;
private Long eventId;
private Long topicId;
@Min(0)
@Max(100)
@NotNull
private Long visibility;
}
ServiceImpl:
@Override
public void savePost(PostDto postDto) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User user = userService.findByMailAddress(authentication.getName());
if (!permissionService.canCreatePosts(user)) {
throw new InsufficientPermissionsException(INSUFFICIENT_PERMISSIONS_EXCEPTION);
}
Post post = Post.builder()
.title(postDto.getTitle())
.content(postDto.getContent())
.visibility(postDto.getVisibility())
.event(eventService.getById(postDto.getEventId()))
.topic(topicService.getById(postDto.getTopicId()))
.creator(user)
.creationDate(LocalDateTime.now())
.build();
postRepository.save(post);
topicService.mailToSubscribers(post.getTopic());
}
Using the Spring Data JPA the object should get persisted in a database and users can read the post on the site.
After submitting the form, i logged the PostDto object and its already messed up and character like "ä", "ö", "ü" or other will be replaced with a "?".
I tried setting the following settings in the application.properties
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.enabled=true
server.servlet.encoding.force=true
spring.thymeleaf.encoding=UTF-8
I found no solution in the web and used ChatGPT but got no awnser that fixed my problem.
I have implemented an AttributeConverter which was missing a StandardCharset. Thats why I had this issue.
You can see the solution right here:
@Component
@Converter
public class DataEncryptionConverter implements AttributeConverter<String, String> {
private final KeyStoreManager keyStoreManager;
private static final String ALGORITHM = "AES";
@Autowired
public DataEncryptionConverter(KeyStoreManager keyStoreManager) {
this.keyStoreManager = keyStoreManager;
}
@Override
public String convertToDatabaseColumn(String attribute) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, keyStoreManager.loadOrCreateSecretKey());
byte[] encryptedData = cipher.doFinal(attribute.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedData);
} catch (Exception e) {
throw new RuntimeException("Encryption error", e);
}
}
@Override
public String convertToEntityAttribute(String dbData) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keyStoreManager.loadOrCreateSecretKey());
byte[] decryptedData = cipher.doFinal(Base64.getDecoder().decode(dbData));
return new String(decryptedData, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("Decryption error", e);
}
}
}