Simple Spring Security example not logging in - spring-boot

Morning!
Just started learning Spring Security with the help of the Baeldung tutorial at https://www.baeldung.com/spring-security-authentication-with-a-database.
However, it's not quite working. What I'm trying to do is connecting my simple H2 database, containing a User table with id, username and password (in plaintext for simplicity), with my secured web application.
I created WebSecurityConfig (extending WebSecurityConfigurerAdapter, see below), MyUserDetailsService (implementing UserDetailsService, see below) and LoggedInUser (implementing UserDetails, see below) classes.
WebSecurityConfig: This should secure all pages except home, login and register pages, which is working. Also, globalSecurityConfiguration should enable the login function by linking to the userDetailsService.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
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;
#Configuration
#EnableWebSecurity
#ComponentScan(basePackageClasses = MyUserDetailsService.class)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home", "/register").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Autowired
public void globalSecurityConfiguration(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
MyUserDetailsService: This gets the Repository injection to access my database. I check the database for the username, and if it's present, I return a new LoggedInUser.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
#Service
public class MyUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) {
List<User> users = userRepository.findByUsername(username);
if (users.size() == 0) {
throw new UsernameNotFoundException(username);
}
return new LoggedInUser(users.get(0));
}
}
And finally, the LoggedInUser class:
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
public class LoggedInUser implements UserDetails {
private User user;
public LoggedInUser(User user) {
this.user= user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return AuthorityUtils.createAuthorityList("ROLE_USER");
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getNickname();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
When I try to log in with some nonexistent username, the error message pops as it should. However, when I'm trying to log in with an existing username (with any password, right or wrong), it's not giving any error message but isn't logging me in either (at least I still can't access other secured pages of the app).
I'm omitting User and UserRepository classes since they're just pretty straightforward and well tested. My login page looks like that:
<html xmlns:th="http://www.thymeleaf.org" xmlns:tiles="http://www.thymeleaf.org">
<head>
<title>Spring Security Example</title>
</head>
<body>
<div class="container">
<form name="f" th:action="#{/login}" method="post">
<fieldset>
<legend>Please Login</legend>
<div th:if="${param.error}" class="alert alert-error">
Invalid username and password.
</div>
<div th:if="${param.logout}" class="alert alert-success">
You have been logged out.
</div>
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username"/>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password"/>
</div>
<button type="submit" class="btn btn-primary">Login</button>
</fieldset>
</form>
</div>
</body>
</html>
I know that the loadUserByUsername method isn't touching the password, but from what I've read, checking if the right password was entered happens automatically within the Security framework.
I also tried to implement my own AuthenticationProvider to use instead of the UserDetailsService to check if both username and password inputs match the database entries within the authenticate method, but then I encountered another problem - wrong credentials now get flagged right, but right credentials produced an error Cannot invoke "Object.toString()" because the return value of "org.springframework.security.core.Authentication.getCredentials()" is null. However the line the error mentioned was the one that reads the password from the user input - and since this only happens for passwords matching the correct one, this shouldn't be null. I'm not posting code here since probably this is a different issue though.
Thanks for any help! Remember, this is like the first time I touched any security framework, so better ELI5 :)

Related

Java Spring - Active Directory- How can I Get AD User Details (telNumber, full name, mail , address, description)?

In my college project i would like to get user informations from an AD Server such as the telephone number, the mail, the full name after an authentication.
So i use the default spring security login page and after the authentication, i get the dn and the permissions with an Authentication object. I would like to know how can i get the details of an ad user.
I would like to get his phone number to send a message with an API. This part is already working. I just need to extract the Ad user details to do it.
You will find my code below :
SecurityConfiguration.java :
package com.le_chatelet.le_chatelet_back.ldap;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationProvider;
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.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider =
new ActiveDirectoryLdapAuthenticationProvider( "mydomain.com", "ldap://adserverip:389");
activeDirectoryLdapAuthenticationProvider.setConvertSubErrorCodesToExceptions(true);
activeDirectoryLdapAuthenticationProvider.setUseAuthenticationRequestCredentials(true);
return activeDirectoryLdapAuthenticationProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception{
authenticationManagerBuilder
.authenticationProvider(activeDirectoryLdapAuthenticationProvider());
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception{
httpSecurity
.authorizeRequests()
.anyRequest()
.fullyAuthenticated()
.and()
.formLogin();
}
}
LoginController.java :
package com.le_chatelet.le_chatelet_back.ldap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.stream.Collectors;
#RestController
public class LoginController {
#Autowired
private UserInterface userInterface;
Logger logger = LoggerFactory.getLogger(LoginController.class);
#GetMapping("/hello")
public String sayHello()
{
return "hello world";
}
#GetMapping("/user")
#ResponseBody
public Authentication getLoggedUserDetail(Authentication authentication) {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
//get username
String username = authentication.getName();
logger.info("username : "+username);
// concat list of authorities to single string seperated by comma
String authorityString = authentication
.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
String role = "role_A";
boolean isCurrentUserInRole = authentication
.getAuthorities()
.stream()
.anyMatch(role::equals);
return authentication;
}
}
If someone can show me code example it would be appreciated.
You can set the a UserDetailsContextMapper on your Provider which allows custom strategy to be used for creating the UserDetails that will be stored as the principal in the Authentication.
provider.setUserDetailsContextMapper(new PersonContextMapper());
Then you can use the #AuthenticationPrincipal annotation in your Controller to get the Person (or a custom class) instance.
#GetMapping("/phone-number")
public String phoneNumber(#AuthenticationPrincipal Person person) {
return "Phone number: " + person.getTelephoneNumber();
}
You can find a full LDAP sample application provided by the Spring Security team.

failure of login page in thymeleaf

I create login page and it fetch username and password from database
but when i enter correct username and password it throw invalid
credential exception .I check every thing properly and define
everything. I want when user successful login he can see products and
search products. but my login not working .it crashed every time
login.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="/fragments/head"></head>
<body>
<nav th:replace="/fragments/nav :: nav-front"></nav>
<div class="container-fluid mt-5">
<div class="row">
<div th:replace="/fragments/categories"></div>
<div class="col"></div>
<div class="col-6 text-center">
<h3 class="display-4">Login</h3>
<div class="alert alert-danger" th:if="${param.error}">
Invalid credentials
</div>
<form method="post" th:action="#{/login}">
<div class="form-group">
<label>Username:</label>
<input type="text" class="form-control" name="username" id="username" placeholder="Username">
</div>
<div class="form-group">
<label>Password:</label>
<input type="password" class="form-control" name="password" id="password" placeholder="Password">
</div>
<button class="btn btn-danger mb-5">Login</button>
<p>
Click <a th:href="#{/register}">here</a> to register.
</p>
</form>
</div>
<div class="col"></div>
</div>
</div>
<div th:replace="/fragments/footer"></div>
</body>
</html>
pagesController.kt
package com.nilmani.cmsshopingcart.controller
import com.nilmani.cmsshopingcart.model.Page
import com.nilmani.cmsshopingcart.repository.PageRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
#Controller
#RequestMapping("/")
class PagesController {
#Autowired
private lateinit var pageRepository: PageRepository
#GetMapping
fun home(model: Model?):String{
val page: Page? = pageRepository.findBySlug("home")
if (model != null) {
model.addAttribute("page",page)
}
return "page"
}
#GetMapping("/login")
fun login():String{
return "login"
}
#GetMapping("/{slug}")
fun page(#PathVariable slug: String, model: Model): String? {
val page: Page = pageRepository.findBySlug(slug) ?: return "redirect:/"
model.addAttribute("page", page)
return "page"
}
}
UserRepositoryUserDetailsService.kt
package com.nilmani.cmsshopingcart.security
import com.nilmani.cmsshopingcart.model.Admin
import com.nilmani.cmsshopingcart.model.UserEntity
import com.nilmani.cmsshopingcart.repository.AdminRepository
import com.nilmani.cmsshopingcart.repository.UserRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.stereotype.Service
#Service
class UserRepositoryUserDetailsService : UserDetailsService {
#Autowired
private lateinit var userRepo:UserRepository
#Autowired
private lateinit var adminRepo:AdminRepository
#Throws(UsernameNotFoundException::class)
override fun loadUserByUsername(email: String): UserDetails {
val user: UserEntity? = userRepo.findByUsername(email)
val admin: Admin? = adminRepo.findByUsername(email)
if (user != null) {
return User(user.username,user.password,ArrayList())
}
if (admin != null) {
return admin
}
throw UsernameNotFoundException("User: $email not found!")
}
}
UserEntity.kt
package com.nilmani.cmsshopingcart.model
import lombok.Data
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.UserDetails
import java.util.*
import javax.persistence.*
import javax.validation.constraints.Email
import javax.validation.constraints.Size
import kotlin.jvm.Transient
#Data
#Entity
#Table(name = "user")
data class UserEntity(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id:Int=0,
#Size(min = 5,message = "Name must be attlist 5 character long")
private var username:String="",
#Size(min = 8,message = "Password contain above 8 character long")
private var password:String="",
#Transient
val confirmPassword:String="",
#Email(message = "please enter a valid email")
val email:String="",
#Size(min = 10,message = "phone number must be 10 digit")
val phoneNumber:String="",
):UserDetails{
override fun getAuthorities(): List<SimpleGrantedAuthority> {
return listOf(SimpleGrantedAuthority("ROLE_USER"))
}
override fun getPassword(): String {
return password
}
override fun getUsername(): String {
return username
}
override fun isAccountNonExpired(): Boolean {
return true
}
override fun isAccountNonLocked(): Boolean {
return true
}
override fun isCredentialsNonExpired(): Boolean {
return true
}
override fun isEnabled(): Boolean {
return true
}
}
UserRepository.kt
package com.nilmani.cmsshopingcart.repository
import com.nilmani.cmsshopingcart.model.UserEntity
import org.springframework.data.jpa.repository.JpaRepository
interface UserRepository : JpaRepository<UserEntity,Int> {
fun findByUsername(email:String):UserEntity?
}
SecurityConfig.kt
package com.nilmani.cmsshopingcart.security
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
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
import org.springframework.stereotype.Service
#Service
#Configuration
#EnableWebSecurity
class SecurityConfig : WebSecurityConfigurerAdapter() {
#Autowired
private lateinit var userDetailsService: UserDetailsService
#Bean
fun encoder(): PasswordEncoder {
return BCryptPasswordEncoder()
}
#Throws(java.lang.Exception::class)
override fun configure(auth: AuthenticationManagerBuilder?) {
auth
?.userDetailsService(userDetailsService)
?.passwordEncoder(encoder())
}
#Throws(Exception::class)
override fun configure(http: HttpSecurity) {
http
.authorizeRequests()
.antMatchers("/category/**").hasAnyRole("USER", "ADMIN")
.antMatchers("/admin/**").hasAnyRole("USER")
.antMatchers("/").permitAll()
.and()
.formLogin()
.loginPage("/login")
.and()
.logout()
.logoutSuccessUrl("/")
.and()
.exceptionHandling()
.accessDeniedPage("/")
}
}
what is the region of failure of my loginPage
if you don't want to encrypt password spring automatically encrypted
password. so in that case use NoOpPasswordEncoder.getInstance()
instead BCryptPasswordEncoder()
so change the code
#Bean
fun encoder(): PasswordEncoder {
return BCryptPasswordEncoder()
}
To
#Bean
fun encoder(): PasswordEncoder {
return NoOpPasswordEncoder.getInstance()
}
and pass the instance of passwordEncoder in
AuthenticationManagerBuilder
this code works for me fine

Returning a cookie or token with LDAP authentication in Spring security

All:
I have a basic program for Ldap authentication which returns a "Principal User "
package com.bpm.cbl.premium.controller;
import java.security.Principal;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
#RestController
#RequestMapping("custom")
public class LDAPAuthController {
public static String domain;
public static String URL;
#Value("${activedirectory.domain}")
private String adDomain;
#Value("${activedirectory.url}")
private String adURL;
#PostConstruct
public void init(){
domain = adDomain;
URL = adURL;
}
#GetMapping("/user-login")
#ResponseBody
public Principal user(Principal user) {
return user;
}
#Configuration
#Order(SecurityProperties.BASIC_AUTH_ORDER)
protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic().and()
.logout().and()
.authorizeRequests()
.antMatchers("/index.html", "/", "/home", "/login", "/assets/**").permitAll()
.anyRequest().authenticated()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
#Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider = new
ActiveDirectoryLdapAuthenticationProvider(domain, URL);
return activeDirectoryLdapAuthenticationProvider;
}
}
}
I dont know how to return a cookie or token instead of a object .. Iam new to spring security..Can someone help pls
I have reference to another post but not sure whether it will work how to achieve Ldap Authentication using spring security(spring boot)
Can someone pls provide some inputs pls
Ok I got a solution; Posting for the benefit of all..
There are lot of confusing articles in the internet and many forums but it is very simple
Replace the function under #GetMapping("/user-login") above with a function that returns the cookie in the respose body.. Pass httpserveletresponse as argument for the function along with any other arguments needed.. Thats it the cookie will be returned in the response header;

Spring boot authentication issue?

As you can see on : http://website-live-245110.appspot.com/ (gccloud hosted site) following the code logic, any username with 4 characters should be able to log in. Although this is not the case at the moment and i have trouble understanding why.
You should try logging in multiple times to grasp the issue.
CustomAuthenticationProvider.java
package com.spring.authprovider;
import java.util.ArrayList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider{
#Autowired
private ThirdPartyAuthProviderClient thirdPartyAuthProviderClient;
// one a user logs in, the authentication variable is filled with the details of the authentication
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// when the user logs in to the application, our object will be filled by spring
String name = authentication.getName();
Object password = authentication.getCredentials(); //object that encapsulates password that user types
// not printing or storing password anyone
if(thirdPartyAuthProviderClient.shouldAuthenticate(name,password)) {
// the array list is for roles, because we are not using it now, we are sending it an empty one
return new UsernamePasswordAuthenticationToken(name, password, new ArrayList<>());
} else {
System.out.println("authentication failed for user: " + name);
}
return null;
}
#Override
public boolean supports(Class<?> authentication) {
// there are multiple ways of authentication, use use username and password
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
ThirdPartyAuthProviderClient.java
package com.spring.authprovider;
import org.springframework.stereotype.Component;
#Component
public class ThirdPartyAuthProviderClient {
//emulates request to third party application
public boolean shouldAuthenticate(String username, Object password) {
// 3rd party request to see if user is correct or no or should be logged in
// user with username with 4 digits can be logged in to the application
return username.length() == 4;
}
}
WebSecurityConfig.java
package com.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import com.spring.authprovider.CustomAuthenticationProvider;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider authProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/", "/home", "/time").permitAll() // any request matching /, /home, /time
// can be accessed by anyone
.anyRequest().authenticated() // any other request needs to be authenticated
.and().authorizeRequests().antMatchers("/admin/**") // only admin can access /admin/anything
.hasRole("ADMIN")
.and().formLogin().loginPage("/login") // permit all to form login--- we use loginPage to use custom page
.permitAll()
.and().logout() // permit all to form logout
.permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//specify auth provider
auth.authenticationProvider(authProvider);
}
// configuration of static resources
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/templates/**", "/assets/**");
}
}
MvcConfig.java
package com.spring;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#Configuration
public class MvcConfig implements WebMvcConfigurer {
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/home").setViewName("home");
registry.addViewController("/").setViewName("home");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login").setViewName("login");
}
}
Templates
hello.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<form th:action="#{/logout}" method="post">
<input type="submit" value="Sign Out"/>
</form>
</body>
</html>
home.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example</title>
</head>
<body>
<h1>Welcome!</h1>
<p>Click <a th:href="#{/hello}">here</a> to see a greeting.</p>
</body>
</html>
login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
Invalid username and password.
</div>
<div th:if="${param.logout}">
You have been logged out.
</div>
<form th:action="#{/login}" method="post">
<div><label> User Name : <input type="text" name="username"/> </label></div>
<div><label> Password: <input type="password" name="password"/> </label></div>
<div><input type="submit" value="Sign In"/></div>
</form>
</body>
</html>
I expect it to either log me in when a username with 4 characters is entered, Or output Invalid username and password. Error.
Code is here : https://github.com/jeffpascal/Spring-and-springboot/tree/devs/SpringSecurity

Deployed Spring App is not getting routed to correct URL on first logins

I deployed a Spring App to Heroku. I am using Spring Security for logging in and registration. My problem is that for new users, when they initially log-in, it takes them to the base URL (the URL that Heroku gave me for my site). All of my main html files are in a folder named "cheese". The problem is that it directs me to the main URL (instead of "/cheese/account", which is where I direct it to be routed in my SecurityConfig), and I get a white label error.
This only happens the first time. When they log on again, it takes them to the correct URL, which is "/cheese/account". Also, once in a while, I will click on the base URL that heroku gave for my site, and it gives me just that URL, and doesn't direct me to "/cheese/login". This will happen if I try to access the URL from an incognito window.
I dont have this problem at all when running it locally. Here is the relevant code...Let me know if you need anything, in addition.
SecurityConfig
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.sql.DataSource;
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("select email as principal, password as credentials, true from customer where email=?")
.authoritiesByUsernameQuery("select customer_email as principal, role_id as role from user_roles where customer_email=?")
.passwordEncoder(passwordEncoder()).rolePrefix("ROLE_");
}
#Override
protected void configure(HttpSecurity http) throws Exception{
http
.csrf().disable()
.authorizeRequests()
.antMatchers(
"/**/webjars/**",
"/cheese/signup",
"/cheese/login",
"/cheese/success").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/cheese/login")
.defaultSuccessUrl("/cheese/account")
.permitAll();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
UserController
package com.example.demo.controllers;
import com.example.demo.models.Customer;
import com.example.demo.models.data.CustomerDao;
import com.example.demo.models.services.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
#Controller
#RequestMapping("cheese")
public class UserController {
#Autowired
private CustomerDao customerDao;
#Autowired
UserService userService;
#RequestMapping(value = "login")
public String loginPage(Model model) {
model.addAttribute("title", "Login Page");
model.addAttribute("customer", new Customer());
return "cheese/login";
}
#RequestMapping(value = "account")
public String accountInfo(Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Customer customer = customerDao.findByEmail(authentication.getName());
model.addAttribute("name", customer.getName());
model.addAttribute("customer", customer);
return "cheese/account";
}
#GetMapping("signup")
public String displaySignUpForm(Model model) {
model.addAttribute("title", "Sign Up");
model.addAttribute("customer", new Customer());
return "cheese/signup";
}
#PostMapping(value = "signup")
public String processSignUp(Model model, #ModelAttribute Customer customer, Errors errors) {
if (errors.hasErrors()) {
return "cheese/signup";
}
userService.createUser(customer);
return "cheese/success";
}
}
MainController
package com.example.demo.controllers;
import com.example.demo.models.Cheese;
import com.example.demo.models.data.CheeseDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
#RequestMapping(value = "cheese")
#Controller
public class MainController {
#Autowired
CheeseDao cheeseDao;
#RequestMapping(value = "")
public String hello(Model model) {
model.addAttribute("title", "Grocery List");
model.addAttribute("cheeses", cheeseDao.findAll());
return "cheese/index";
}
#GetMapping("add")
public String displayAddCheeseForm(Model model) {
model.addAttribute("title", "Add Cheese");
model.addAttribute("cheese", new Cheese());
return "cheese/add";
}
#PostMapping("add")
public String processAddCheeseForm(Model model,
#ModelAttribute #Valid Cheese cheese,
Errors errors) {
if (errors.hasErrors()) {
return "cheese/add";
}
cheeseDao.save(cheese);
return "redirect:";
}
#RequestMapping(value = "remove", method = RequestMethod.GET)
public String displayRemoveCheeseForm(Model model) {
model.addAttribute("cheeses", cheeseDao.findAll());
model.addAttribute("title", "Remove Cheese");
return "cheese/remove";
}
#RequestMapping(value = "remove", method = RequestMethod.POST)
public String processRemoveCheeseForm(Model model, #RequestParam int[] cheeseIds) {
for (int id : cheeseIds) {
cheeseDao.deleteById(id);
}
return "redirect:";
}
}

Resources