diff --git a/spring-security-modules/spring-security-web-thymeleaf/src/main/java/com/baeldung/customuserdetails/CustomUserDetails.java b/spring-security-modules/spring-security-web-thymeleaf/src/main/java/com/baeldung/customuserdetails/CustomUserDetails.java new file mode 100644 index 0000000000..380d9d1092 --- /dev/null +++ b/spring-security-modules/spring-security-web-thymeleaf/src/main/java/com/baeldung/customuserdetails/CustomUserDetails.java @@ -0,0 +1,94 @@ +package com.baeldung.customuserdetails; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.User; + +import java.util.Collection; +import java.util.Objects; + +public class CustomUserDetails extends User { + + private final String firstName; + private final String lastName; + private final String email; + + private CustomUserDetails(Builder builder) { + super(builder.username, builder.password, builder.authorities); + this.firstName = builder.firstName; + this.lastName = builder.lastName; + this.email = builder.email; + } + + public String getLastName() { + return this.lastName; + } + + public String getFirstName() { + return this.firstName; + } + + public String getEmail() { + return email; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + if (!super.equals(o)) + return false; + CustomUserDetails that = (CustomUserDetails) o; + return firstName.equals(that.firstName) && lastName.equals(that.lastName) && email.equals(that.email); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), firstName, lastName, email); + } + + public static class Builder { + + private String firstName; + private String lastName; + private String email; + private String username; + private String password; + private Collection authorities; + + public Builder withFirstName(String firstName) { + this.firstName = firstName; + return this; + } + + public Builder withLastName(String lastName) { + this.lastName = lastName; + return this; + } + + public Builder withEmail(String email) { + this.email = email; + return this; + } + + public Builder withUsername(String username) { + this.username = username; + return this; + } + + public Builder withPassword(String password) { + this.password = password; + return this; + } + + public Builder withAuthorities(Collection authorities) { + this.authorities = authorities; + return this; + } + + public CustomUserDetails build() { + return new CustomUserDetails(this); + } + } +} diff --git a/spring-security-modules/spring-security-web-thymeleaf/src/main/java/com/baeldung/customuserdetails/CustomUserDetailsService.java b/spring-security-modules/spring-security-web-thymeleaf/src/main/java/com/baeldung/customuserdetails/CustomUserDetailsService.java new file mode 100644 index 0000000000..e84f9eac55 --- /dev/null +++ b/spring-security-modules/spring-security-web-thymeleaf/src/main/java/com/baeldung/customuserdetails/CustomUserDetailsService.java @@ -0,0 +1,52 @@ +package com.baeldung.customuserdetails; + +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +@Service +public class CustomUserDetailsService implements UserDetailsService { + + private final PasswordEncoder passwordEncoder; + private final Map userRegistry = new HashMap<>(); + + public CustomUserDetailsService(PasswordEncoder passwordEncoder) { + this.passwordEncoder = passwordEncoder; + } + + @PostConstruct + public void init() { + userRegistry.put("user", new CustomUserDetails.Builder().withFirstName("Mark") + .withLastName("Johnson") + .withEmail("mark.johnson@email.com") + .withUsername("user") + .withPassword(passwordEncoder.encode("password")) + .withAuthorities(Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))) + .build()); + userRegistry.put("admin", new CustomUserDetails.Builder().withFirstName("James") + .withLastName("Davis") + .withEmail("james.davis@email.com") + .withUsername("admin") + .withPassword(passwordEncoder.encode("admin")) + .withAuthorities(Collections.singletonList(new SimpleGrantedAuthority("ROLE_ADMIN"))) + .build()); + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + final CustomUserDetails userDetails = userRegistry.get(username); + if (userDetails == null) { + throw new UsernameNotFoundException(username); + } + return userDetails; + } +} diff --git a/spring-security-modules/spring-security-web-thymeleaf/src/main/java/com/baeldung/customuserdetails/PasswordEncoderConfiguration.java b/spring-security-modules/spring-security-web-thymeleaf/src/main/java/com/baeldung/customuserdetails/PasswordEncoderConfiguration.java new file mode 100644 index 0000000000..fa19b62577 --- /dev/null +++ b/spring-security-modules/spring-security-web-thymeleaf/src/main/java/com/baeldung/customuserdetails/PasswordEncoderConfiguration.java @@ -0,0 +1,15 @@ +package com.baeldung.customuserdetails; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +public class PasswordEncoderConfiguration { + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/spring-security-modules/spring-security-web-thymeleaf/src/main/java/com/baeldung/customuserdetails/SecurityConfiguration.java b/spring-security-modules/spring-security-web-thymeleaf/src/main/java/com/baeldung/customuserdetails/SecurityConfiguration.java new file mode 100644 index 0000000000..5d5863a564 --- /dev/null +++ b/spring-security-modules/spring-security-web-thymeleaf/src/main/java/com/baeldung/customuserdetails/SecurityConfiguration.java @@ -0,0 +1,38 @@ +package com.baeldung.customuserdetails; + +import org.springframework.context.annotation.Configuration; +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.web.util.matcher.AntPathRequestMatcher; + +@Configuration +@EnableWebSecurity +public class SecurityConfiguration extends WebSecurityConfigurerAdapter { + + private final UserDetailsService userDetailsService; + + public SecurityConfiguration(UserDetailsService userDetailsService) { + this.userDetailsService = userDetailsService; + } + + @Override + protected void configure(HttpSecurity http) throws Exception { + http.userDetailsService(userDetailsService) + .authorizeRequests() + .anyRequest() + .authenticated() + .and() + .formLogin() + .loginPage("/login") + .permitAll() + .successForwardUrl("/index") + .and() + .logout() + .permitAll() + .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) + .logoutSuccessUrl("/login"); + } + +} diff --git a/spring-security-modules/spring-security-web-thymeleaf/src/main/java/com/baeldung/customuserdetails/SpringSecurityThymeleafApplication.java b/spring-security-modules/spring-security-web-thymeleaf/src/main/java/com/baeldung/customuserdetails/SpringSecurityThymeleafApplication.java new file mode 100644 index 0000000000..35cbc77552 --- /dev/null +++ b/spring-security-modules/spring-security-web-thymeleaf/src/main/java/com/baeldung/customuserdetails/SpringSecurityThymeleafApplication.java @@ -0,0 +1,12 @@ +package com.baeldung.customuserdetails; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringSecurityThymeleafApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringSecurityThymeleafApplication.class, args); + } +} diff --git a/spring-security-modules/spring-security-web-thymeleaf/src/main/java/com/baeldung/customuserdetails/ViewController.java b/spring-security-modules/spring-security-web-thymeleaf/src/main/java/com/baeldung/customuserdetails/ViewController.java new file mode 100644 index 0000000000..bc4c41392d --- /dev/null +++ b/spring-security-modules/spring-security-web-thymeleaf/src/main/java/com/baeldung/customuserdetails/ViewController.java @@ -0,0 +1,21 @@ +package com.baeldung.customuserdetails; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +@Controller +public class ViewController { + + @RequestMapping("/login") + public String login() { + return "login"; + } + + @RequestMapping({ "/index", "/" }) + public String index() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return "userdetails"; + } +} diff --git a/spring-security-modules/spring-security-web-thymeleaf/src/main/resources/templates/userdetails.html b/spring-security-modules/spring-security-web-thymeleaf/src/main/resources/templates/userdetails.html new file mode 100644 index 0000000000..5963fbc29e --- /dev/null +++ b/spring-security-modules/spring-security-web-thymeleaf/src/main/resources/templates/userdetails.html @@ -0,0 +1,25 @@ + + + +Welcome to Spring Security Thymeleaf tutorial + + +

Welcome

+

Spring Security Thymeleaf tutorial

+
Text visible to user.
+
Text visible to admin.
+
Text visible only to + authenticated users.
+ Authenticated username: +
+ Authenticated user's firstName: +
+ Authenticated user's lastName: +
+ Authenticated user's email: +
+ Authenticated user roles: +
+ + + \ No newline at end of file diff --git a/spring-security-modules/spring-security-web-thymeleaf/src/test/java/com/baeldung/customuserdetails/SpringSecurityThymeleafApplicationIntegrationTest.java b/spring-security-modules/spring-security-web-thymeleaf/src/test/java/com/baeldung/customuserdetails/SpringSecurityThymeleafApplicationIntegrationTest.java new file mode 100644 index 0000000000..c3b11e648a --- /dev/null +++ b/spring-security-modules/spring-security-web-thymeleaf/src/test/java/com/baeldung/customuserdetails/SpringSecurityThymeleafApplicationIntegrationTest.java @@ -0,0 +1,29 @@ +package com.baeldung.customuserdetails; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.context.WebApplicationContext; + +import static org.junit.Assert.assertNotNull; + +import com.baeldung.customuserdetails.ViewController; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SpringSecurityThymeleafApplicationIntegrationTest { + + @Autowired + ViewController viewController; + @Autowired + WebApplicationContext wac; + + @Test + public void whenConfigured_thenLoadsContext() { + assertNotNull(viewController); + assertNotNull(wac); + } + +} diff --git a/spring-security-modules/spring-security-web-thymeleaf/src/test/java/com/baeldung/customuserdetails/ViewControllerIntegrationTest.java b/spring-security-modules/spring-security-web-thymeleaf/src/test/java/com/baeldung/customuserdetails/ViewControllerIntegrationTest.java new file mode 100644 index 0000000000..73d4bda112 --- /dev/null +++ b/spring-security-modules/spring-security-web-thymeleaf/src/test/java/com/baeldung/customuserdetails/ViewControllerIntegrationTest.java @@ -0,0 +1,42 @@ +package com.baeldung.customuserdetails; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view; + +import com.baeldung.customuserdetails.PasswordEncoderConfiguration; + +@RunWith(SpringRunner.class) +@WebMvcTest +@Import(PasswordEncoderConfiguration.class) +public class ViewControllerIntegrationTest { + + @Autowired + private WebApplicationContext context; + MockMvc mvc; + + @Before + public void setup() { + mvc = MockMvcBuilders + .webAppContextSetup(context) + .build(); + } + + @Test + public void givenUser_whenPerformingGet_thenReturnsIndex() throws Exception { + mvc.perform(get("/index").with(user("user").password("password"))).andExpect(status().isOk()).andExpect(view().name("userdetails")); + } + +}