Spring boot and Thymeleaf Integration
Integrating Spring Boot with Thymeleaf and Spring Security Taglibs
What is Thymeleaf
Thymeleaf is a modern server-side Java template engine for both web and standalone environments.
It helps to develop simple web application with HTML pages by enriching dynamic attributes within it.
HTML templates written in Thymeleaf still look and work like HTML.
Steps To integrate Spring boot with Thymeleaf
Add maven dependencies to your pom.xml
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Add WebConfig.java for Java based configuration of Thymeleaf
package com.tvajjala.toggles.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.resource.PathResourceResolver;
import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect;
import org.thymeleaf.extras.springsecurity5.dialect.SpringSecurityDialect;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import java.util.List;
/**
* @author ThirupathiReddy V
*/
@EnableWebMvc
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* Reference to logger
*/
private static final Logger LOGGER = LoggerFactory.getLogger(WebConfig.class);
@Override
public void addViewControllers(final ViewControllerRegistry registry) {
registry.addRedirectViewController("/", "/ui/toggle-ui");
}
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/")
.setCachePeriod(3600)
.resourceChain(true)
.addResolver(new PathResourceResolver());
}
@Override
public void configureMessageConverters(final List<HttpMessageConverter<?>> converters) {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.registerModule(new JavaTimeModule());
converter.setObjectMapper(objectMapper);
converters.add(converter);
}
//@formatter:off
@Bean
public SpringResourceTemplateResolver templateResolver() {
final SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
// final TemplateResolver resolver = new TemplateResolver();
resolver.setPrefix("classpath:/templates/");
resolver.setSuffix(".html");
resolver.setTemplateMode("HTML");
resolver.setCharacterEncoding("UTF-8");
resolver.setCacheable(false);
resolver.setOrder(1);
return resolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
// to enable JSTL tag library
templateEngine.addDialect(new SpringSecurityDialect());
templateEngine.addDialect(new Java8TimeDialect());
/**
* Layout Dialect gives people the possibility of using hierarchical approach, but from a Thymeleaf-only perspective and without the need to use
* external libraries, like Apache Tiles. Thymeleaf Layout Dialect uses layout/decorator templates to style the content, as well as it can pass entire
* fragment elements to included pages. Concepts of this library are similar to SiteMesh or JSF with Facelets.
*/
return templateEngine;
}
@Bean
public ThymeleafViewResolver thymeleafViewResolver() {
final ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
return resolver;
}
@Override
public void configureDefaultServletHandling(final DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
Add Spring Security Configuration as shown below
package com.tvajjala.toggles.config;
import com.tvajjala.toggles.config.security.ApplicationSecurity;
import com.tvajjala.toggles.config.security.CustomUserDetailService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* Security Configuration
*
* @author ThirupathiReddy Vajjala
*/
@Configuration
@EnableWebSecurity
@EnableConfigurationProperties(ApplicationSecurity.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
ApplicationSecurity applicationSecurity;
private static final Logger LOGGER = LoggerFactory.getLogger(SecurityConfig.class);
@Override
protected void configure(final HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http
.authorizeRequests()
.antMatchers("/ui/**")
.hasAnyAuthority("ADMIN", "DEVELOPER", "QA")
.and().formLogin()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.PUT, "/toggles/**")
.hasAnyAuthority("ADMIN")
.and().httpBasic()
.and()
.logout()
.clearAuthentication(true)
.invalidateHttpSession(true)
.logoutSuccessUrl("/login")
.logoutUrl("/logout");
}
/* @Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return new CustomAuthenticationEntryPoint("TOGGLE");
}*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); (1)
}
@Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailService()).passwordEncoder(passwordEncoder()); (2)
}
@Bean
public UserDetailsService customUserDetailService() {
LOGGER.info("Users {} ", applicationSecurity.getUsers());
return new CustomUserDetailService(applicationSecurity.getUsers());(3)
}
}
-
Password encryption bean
-
Enabling Password encryption
-
In-Memory Users from application.yaml
Create your thymeleaf templates under resources/template folder
<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"> (1)
...
<small>[<span sec:authentication="name"/>]</small> (2)
....
</html>
-
spring security taglib directories inside html.
-
Logged in username .
Spring Security Integration with Spring boot
add In-Memory users section to your application.yml file
application:
security:
users:
- username: admin
password: '$2a$10$2pplRaD1nh3u96UEEhRshO3sCSsY17hBGn8Mqk4JDgYbuBgzRHZwi' #password
role: ADMIN
- username: developer
password: '$2a$10$2pplRaD1nh3u96UEEhRshO3sCSsY17hBGn8Mqk4JDgYbuBgzRHZwi' #password
role: DEVELOPER
- username: quality
password: '$2a$10$2pplRaD1nh3u96UEEhRshO3sCSsY17hBGn8Mqk4JDgYbuBgzRHZwi' #password
role: QA
Create Java Representation of this class, which extends org.springframework.security.core.userdetails.UserDetails
package com.tvajjala.toggles.config.security;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Collections;
/**
* @author ThirupathiReddy Vajjala
*/
public class ApplicationUser implements UserDetails {
private String role;
private String username;
private String password;
public String getRole() {
return role;
}
public void setRole(final String role) {
this.role = role;
}
public void setUsername(final String username) {
this.username = username;
}
public void setPassword(final String password) {
this.password = password;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singletonList(new SimpleGrantedAuthority(role));
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public String toString() {
return "ApplicationUser{" +
"role='" + role + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
Bind this class with properties to automatically load into application context using ConfigurationProperties
package com.tvajjala.toggles.config.security;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.ArrayList;
import java.util.List;
/**
* @author ThirupathiReddy Vajjala
*/
@ConfigurationProperties(prefix = "application.security")
public class ApplicationSecurity {
private List<ApplicationUser> users = new ArrayList<>();
public List<ApplicationUser> getUsers() {
return users;
}
public void setUsers(final List<ApplicationUser> users) {
this.users = users;
}
}
Write your CustomUserDetailService which extends org.springframework.security.core.userdetails.UserDetailsService
package com.tvajjala.toggles.config.security;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author ThirupathiReddy Vajjala
*/
public class CustomUserDetailService implements UserDetailsService {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomUserDetailService.class);
private final Map<String, ApplicationUser> appUsers = new HashMap<>();
public CustomUserDetailService(final List<ApplicationUser> users) {
appUsers.putAll(users.stream().collect(Collectors.toMap(user -> user.getUsername(), user -> user)));
LOGGER.warn("Users {} ", appUsers);
}
@Override
public ApplicationUser loadUserByUsername(final String username) throws UsernameNotFoundException {
if (!appUsers.containsKey(username)) {
LOGGER.warn("User {} not found", username);
throw new UsernameNotFoundException("User " + username + " Not found");
}
LOGGER.info("User with username {} found ", username);
return appUsers.get(username);
}
}
Refer Thymeleaf spring boot integration Source code for complete implementation
Comments
Post a Comment