Spring Security for single user - java

I have a web application in which a specific url '/newPost' should only be accessible to one user, the admin. When trying to navigate to '/newPost', the user should be redirected to a login page where he must verify his identity as admin. This all works, except for when the user fills out the login form, the form gets posted to '/login' everytime. I am currently at a loss as to why it is posting to '/login' instead of the path I redirect to using thyme leafs: th:action="#{/newPost}"
TLDR; I keep getting redirected to /login after submitting login form. I am using Spring boot, Spring security, and thymeleaf.
Controller:
/*Keeps getting called*/
#RequestMapping(value="/login", method=RequestMethod.GET)
public String login(Model model)
{
model.addAttribute("lc", new LoginCredentials());
System.out.println("Login controller");
return "login";
}
/*RequestMapping I want to be called*/
#RequestMapping(value="/newPost", method = RequestMethod.GET)
public String isLoggedIn(#ModelAttribute LoginCredentials lc, Model model)
{
if(lc.isAdmin())
{
System.out.println("lc is admin");
model.addAttribute("bp",new BlogPost());
return "newPost";
} else
{
System.out.println("lc is not admin");
return "login";
}
}
Login Form:
<form class="form-signin" th:action="#{/newPost}" th:object="${lc}" method = "post">
<h2 class="form-signin-heading">Please sign in</h2>
<label for="inputEmail" class="sr-only">Username</label>
<input type="text" id="username" class="form-control" th:field="*{inputUsername}" placeholder="Username" required="required" autofocus="autofocus" />
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" id="password" th:field="*{inputPsswd}" class="form-control" placeholder="Password" required ="required" />
<button class="btn btn-lg btn-primary btn-block" type="submit" style="background-color:#F6358A;">Sign in</button>
</form>
Security Configuration:
httpSecurity
.authorizeRequests()
.antMatchers("/","/videos","/BlogPost","/index","/aboutUs").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll();

What's your login page jsp name? Is that "login.jsp"?
You login method return is "login", that means it will return to login.jsp.
User return "/newPost" instead.

My above question was a mess. This was my first time trying to work with Java Spring and my question shows. I hope this explanation helps a future user.
Firstly:
The action should not be different from /login. I was essentially causing an infinite loop of logins because I was sending the user to /newPost by submitted the login form, but they could not access the /newPost until they provided the correct credentials. Spring dutifully redirected the user to /login in order to provide the correct credentials, repeating the process.
This:
th:action="#{/newPost}"
should be:
th:action="#{/login}"
with a corresponding RequestMapping like so:
#RequestMapping(value="/login", method=RequestMethod.POST)
public String loginPost(Model model)
{
//do foo
}
Secondly:
I attempted to do Spring Security's job for it.
if(lc.isAdmin())
{
System.out.println("lc is admin");
model.addAttribute("bp",new BlogPost());
return "newPost";
} else
{
System.out.println("lc is not admin");
return "login";
}
Since I only needed security for a single user, I should have configured an AuthenticationManagerBuilder object in my security configuration like so:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
{
try
{
auth
.inMemoryAuthentication()
.withUser("admin")
.password("password")
.roles("ADMIN");
} catch (Exception e) {
e.printStackTrace();
}
}
Thirdly:
Since I changed Springs global configuration, I should not pass in an object to login.html. The new form should use input fields like so:
<form class="form-signin" th:action="#{/login}" method = "post">
<h2 class="form-signin-heading">Please sign in</h2>
<label for="inputEmail" class="sr-only">Username</label>
<input type="text" id="username" name="username" class="form-control" placeholder="Username" required="required" autofocus="autofocus" />
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" id="password" name="password" class="form-control" placeholder="Password" required ="required" />
<button class="btn btn-lg btn-primary btn-block" type="submit" style="background-color:#F6358A;">Sign in</button>
</form>

Related

Update operation is not performing -Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported]

I'm trying to develop an application in spring boot + thymeleaf, and I'm able to retrieve the logged in user details in the profile tab from the MySQL database, but when I try to change one or two field details (update) and hit the update button it is showing me an error message - Fri Sep 04 20:39:47 IST 2020
There was an unexpected error (type=Method Not Allowed, status=405).
Request method 'POST' not supported
see my controller code (I'm using #RestController annotated on top of the class)-
#RequestMapping(value = "/profile", method = RequestMethod.PUT)
public ModelAndView updateProfile(#ModelAttribute Customer customer, HttpSession session) {
ModelAndView model = new ModelAndView();
Customer exist = cRepo.findByCustEmail(customer.getCustEmail());
if(exist != null) {
if(exist.getCustEmail().equals(session.getAttribute("emailsession"))) {
cRepo.save(customer);
model.addObject("msg", "User Details has been successfully updated!!");
model.setViewName("profile");
}
}else {
model.addObject("exist", "Please enter correct email address!");
String email = (String) session.getAttribute("emailsession");
Customer cust = cRepo.findByCustEmail(email);
model.addObject("customer", cust);
model.setViewName("profile");
}
return model;
}
Thymleaf code (html) -
<div align="center" class="alert alert-success" th:if="${msg}" th:utext="${msg}"></div>
<div align="center" class="alert alert-danger" th:if="${exist}" th:utext="${exist}"></div>
<!-- Modal HTML -->
<div id="myModal">
<div class="modal-dialog modal-login">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Profile Details</h4>
</div>
<div class="modal-body">
<form name="myForm" th:action="#{/profile}" th:object="${customer}" method="post">
<div class="form-group">
<i class="fa fa-id-card"></i>
<input name="id" type="text" class="form-control" placeholder="Enter Id" th:field="${customer.custId}" disabled="true" required="required" />
</div>
<div class="form-group">
<i class="fa fa-user"></i>
<input name="name" type="text" class="form-control" placeholder="Enter Name" th:field="${customer.custName}" required="required" />
</div>
<div class="form-group">
<i class="fa fa-envelope"></i>
<input name="email" type="email" class="form-control" placeholder="Enter Email" th:field="${customer.custEmail}" required="required" />
</div>
<div class="form-group">
<i class="fa fa-lock"></i>
<input name="password" type="text" class="form-control" placeholder="Enter Password" th:field="${customer.custPassword}" required="required" />
</div>
<div class="form-group">
<input type="submit" class="btn btn-primary btn-block btn-lg" value="Update" />
</div>
</form>
</div>
</div>
</div>
</div>
I want when user login and visit he/she should be able to check his/her profile(which I'm able to do working code) and when the user wants to update few fields(1-2 based on choice) and hit update he/she should be able to update the details (not create new user or record) because when I use #Controller on top of class then this code work and create new user instead update.
Your controller is annotated with #RequestMapping(value = "/profile", method = RequestMethod.PUT) which makes it a PUT endpoint. However, your request is clearly a POST. If we look at your html form it contains method="post". HTML forms only support GET and POST as valid methods so you need to update your endpoint to be a POST endpoint.
tldr;
RequestMapping(value = "/profile", method = RequestMethod.PUT)
to
RequestMapping(value = "/profile", method = RequestMethod.POST)
You request mapping in is POST but Controller has set to accept request as PUT.
<form name="myForm" th:action="#{/profile}" th:object="${customer}" **method="post"**>
#RequestMapping(value = "/profile", method = **RequestMethod.PUT**)
Just keep these in similar way both should be same.
Please check what I find and resolve this.
#RequestMapping(value = "/profile" ,method = RequestMethod.POST)
public ModelAndView updateProfile(#ModelAttribute Customer customer, HttpSession session) {
ModelAndView model = new ModelAndView();
Customer exist = cRepo.findByCustEmail(customer.getCustEmail());
if(exist != null) {
if(exist.getCustEmail().equals(session.getAttribute("emailsession"))) {
**exist.setCustId(exist.getCustId());
exist.setCustName(customer.getCustName());
exist.setCustEmail(customer.getCustEmail());
exist.setCustPassword(customer.getCustPassword());**
cRepo.save(exist);
model.addObject("msg", "User Details has been successfully updated!!");
model.addObject("customer", exist);
model.setViewName("profile");
}
}else {
model.addObject("exist", "Please enter correct email address!");
String email = (String) session.getAttribute("emailsession");
Customer cust = cRepo.findByCustEmail(email);
model.addObject("customer", cust);
model.setViewName("profile");
}
return model;
}

restrict users to access URL directly - spring security

I am trying to implement spring security and there are no errors / exceptions, but it is not working, I am not able to redirect to the success page.
My success page is /sucessPage, /Verify will verifying the user details against database.
I want to verify the user with 3 different parameters
SecurityConfig.java
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Resource(name="AdminDB")
private DataSource datasource;
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new Md5PasswordEncoder());
auth.eraseCredentials(false);
}
#Override
protected void configure(HttpSecurity security) throws Exception {
logger.info("Inside SecurityConfig - configure() - Spring security");
security
.authorizeRequests()
.antMatchers("/resources/**", "/unsecured/**", "/","/Verify").permitAll()
.anyRequest().authenticated()
.and().formLogin()
.loginPage("/").permitAll()
.usernameParameter("email")
//.passwordParameter("password")
.defaultSuccessUrl("/sucessPage/")
.successHandler(successhandler())
.failureUrl("/")
.and().logout()
.invalidateHttpSession(true).deleteCookies("JSESSIONID")
.permitAll()
.and().exceptionHandling().accessDeniedPage("/systemError.html")
.and().csrf().disable();
}
#Bean
public CustomAuthenticationSuccessHandler successhandler() {
return new CustomAuthenticationSuccessHandler();
}
}
login page - HTML:
<form id="form" name="Form" action="#" th:action="#{/verify}" th:object="${Form}" method="post">
<div class="50">
<label for="first-name" class="label-first-name">First Name*</label>
<input type="text" id="firstName" name="first-name" placeholder="Enter First Name" th:field="*{firstName}">
</div>
<div class="50">
<label for="last-name" class="label-last-name">Last Name*</label>
<input type="text" id="lastName" name="last-name" placeholder="Enter Last Name" th:field="*{lastName}">
</div>
</div>
<div class="100">
<label for="email-1" class="label-email-address">Email Address*<span class="label-note">This is your login username</span></label>
<input type="text" id="email" name="email" placeholder="Enter Email" th:field="*{email}">
</div>
<div class="100">
<label for="email-2" class="label-email-address">Verify Email Address*</label>
<input type="text" id="verifyEmail" name="verifyEmail" placeholder="Re-enter Email" th:field="*{verifyEmail}">
</div>
<div class="100">
<label for="phone" class="label-phone-number">Phone Number<span class="label-note">(optional)</span></label>
<input type="text" id="phoneNumber" name="phoneNumber" placeholder="Enter Phone Number" th:field="*{phoneNumber}">
</div>
<input type="submit" id="submitBtn" value="Next">
</form>
On submit it should verify user and redirect to the /sucessPage. I just want to restrict users to access other URLs directly and other flow will go as it is.
right now it's redirecting back to "/" without any error.
UserDetailsServiceImpl.java
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private LoginManager loginManager;
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException, DataAccessException {
UserDetails user = null;
try {
user = loginManager.findUserByEmail(userName);
} catch (LoginException e) {
throw new UsernameNotFoundException("Username not found: " + e.getMessage());
}
return user;
}
}
loadUserByUsername takes only 1 argument. I want to verify the user by firstName, lastName, email against db and redirecting to success with spring security.Thanks
I think your SecurityConfig.java is incomplete
.formLogin().loginPage("/") is missing :
.loginProcessingUrl("/j_spring_security_check")
and
.defaultSuccessUrl("/sucessPage")

Spring Boot authentication without formLogin() but with custom login form

I'm working on a blog application with Spring Boot Security.
My overridden configure method looks like:
#Override
protected void configure(HttpSecurity httpSec) throws Exception {
httpSec
.authorizeRequests()
.antMatchers("/users").authenticated()
.antMatchers("/admin", "/db").hasRole("ADMIN")
.antMatchers("/**").permitAll()
//.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.and()
.logout().logoutSuccessUrl("/login?logout").permitAll();
httpSec.csrf().disable();
httpSec.headers().frameOptions().disable();
}
And my custom login form:
<form name="login" th:action="#{/login}" method="post" class="form-signin">
<h1>Please log in!</h1>
<div th:if="${param.error}" class="alert alert-danger">Wrong username and/or password.</div>
<div th:if="${param.logout}" class="alert alert-success">You logged out successfully.</div>
<label for="username">Username</label>
<input type="text" name="username" class="form-control" placeholder="username" required="true"/>
<label for="password">Password</label>
<input type="password" name="password" class="form-control" placeholder="password" required="true"/>
<br/>
<button type="submit" class="btn btn-lg btn-primary btn-block">Log in</button>
<br/>
You can register here.
<hr/>
</form>
The default behaviour of Spring Security is that when I send a request to an URL which needs to be authenticated (for example /users or /admin in my case) it automatically redirects to this custom login page.
I would like to disable this automatic redirection. When authentication is needed I would like to throw a custom exception (which I handle with a separate Class with #ControllerAdvice annotation) instead with message like "You have to log in to see this content". But I would like to reach my custom login page via navigation menu to authenticate "manually".
How could I reach this?
So far I have tried .formLogin().disabled(). In this way I can still reach my custom login page, but when I try to submit it gave an error "Method not allowed". But it is logical since th:action="#{/login}" can't send the username and password to /login.
I have found a solution. Maybe not perfect, but it works.
First of all, I have commented out the login/logout part in my configure method:
#Override
protected void configure(HttpSecurity httpSec) throws Exception {
httpSec
.authorizeRequests()
.antMatchers("/users").authenticated()
.antMatchers("/admin", "/db").hasRole("ADMIN")
.antMatchers("/**").permitAll()
/**
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.and()
.logout().logoutSuccessUrl("/login?logout").permitAll();
*/
.and()
.csrf().disable()
.headers().frameOptions().disable();
}
From now when authentication is needed it won't redirect to any (custom) login screen. But the Forbidden (403) error should be handled:
case "Forbidden":
if (SecurityContextHolder.getContext().getAuthentication().getAuthorities().toString().equals("[ROLE_ANONYMOUS]"))
error.put("error", "You have to log in to see this content");
else error.put("error", "It is only for admins");
break;
Next step is creating a login form into the navigation menu:
<form th:action="#{/loginauth}" method="post">
<span sec:authorize="!isAuthenticated()">
<input type="text" name="email" placeholder="e-mail" required="true"/>
<input type="password" name="password" placeholder="password" required="true"/>
<button type="submit" class="btn btn-success btn-xs">Log in</button>
</span>
</form>
After submit we have to be forwarded to a #PostMapping in one of the #Controller classes:
#PostMapping("/loginauth")
public String authenticateLogin(HttpServletRequest request) {
loginService.authenticateBlogUser(request.getParameter("email"), request.getParameter("password"));
return "redirect:/";
}
Finally this data should be sent to a service layer to process:
BlogUserRepository blogUserRepository;
#Autowired
public void setBlogUserRepository(BlogUserRepository blogUserRepository) {
this.blogUserRepository = blogUserRepository;
}
public void authenticateBlogUser(String email, String password) throws UsernameNotFoundException {
BlogUser user = blogUserRepository.findByEmail(email);
if (user == null || !user.getPassword().equals(password))
throw new UsernameNotFoundException("Wrong e-mail and/or password");
Collection<GrantedAuthority> authorities = new HashSet<>();
Set<Role> roles = user.getRoles();
for (Role role : roles)
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getAuth()));
SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(user.getEmail(), user.getPassword(), authorities));
}

HTTP method POST is not supported by this URL Java servlet

Can someone please tell me why i am getting error:HTTP Status 405 – Method Not Allowed ?
I am trying accomplish that after method doPost() ,user will be redirected to "/logout" controller ,where is invalidate session.
It's funny because method is called ,do everything what should do(update user in database), but after send to user error 405.Another where i use doPost() (for example: LoginController) working well ,but when i try compere and find bug ,i dont see any :<
<div class="container">
<div class="col-md-8 col-md-offset-2">
<form method="post" action="account">
<div class="form-group">
<label for="email">Email address</label>
<input name="email" type="email" class="form-control" id="email"
value="${sessionScope.loggedUser.email}" required aria-describedby="emailHelp"
placeholder="Enter email">
</div>
<div class="form-group">
<label for="password">Password</label>
<input name="password" type="password" minlength="5" maxlength="40" required class="form-control"
id="password" placeholder="Password">
</div>
<div class="form-group">
<label for="repeatPassword">Repeat Password</label>
<input name="repeatPassword" type="password" minlength="5" maxlength="40" required class="form-control"
id="repeatPassword" placeholder="Password">
</div>
<input class="btn btn-lg btn-primary btn-block" type="submit" value="Save changes"/>
</form>
</div>
</div>
#WebServlet("/account")
public class AccountController extends HttpServlet {
#Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String email = req.getParameter("email");
String password = req.getParameter("password");
String repeatPassword = req.getParameter("repeatPassword");
if (email == null || password == null || repeatPassword == null) {
doGet(req, resp);
return;
}
if (password.equals(repeatPassword)) {
HttpSession session = req.getSession();
User user = (User) session.getAttribute("loggedUser");
user.setEmail(email);
String sha1hexPassword = DigestUtils.sha1Hex(password);
user.setPassword(sha1hexPassword);
UserService service = new UserService();
try {
service.update(user);
} catch (UpdateObjectException e) {
e.printStackTrace();
}
req.getRequestDispatcher("/logout").forward(req, resp);
} else {
req.setAttribute("errorMessage", "Passwords not the same");
req.setAttribute("fragment", "account");
req.getRequestDispatcher("WEB-INF/index.jsp").forward(req, resp);
}
}
}
Thanks for any hint.
Your doGet() method call is inside the server doPost() code. You should redirect the response, doGet is for recieving a GET request and any query string.
Problem solved. Here:
req.getRequestDispatcher("/logout").forward(req, resp);
i should do
resp.sendRedirect(req.getContextPath()+"/logout");
because in "/logout" i have only doGet() method ,and if i use "getRequestDispatcher()" its try to find doPost() method.

Spring Security always returns BadCredentialsException at DaoAuthenticationProvider on user authentication [duplicate]

I've generated a Spring Boot web application using Spring Initializer, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file.
Technologies used:
Spring Boot 1.4.2.RELEASE, Spring 4.3.4.RELEASE, Thymeleaf 2.1.5.RELEASE, Tomcat Embed 8.5.6, Maven 3, Java 8
This is my security config class:
#Configuration
#EnableWebSecurity
#PropertySource("classpath:/config/app-${APP-KEY}.properties")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${securityConfig.formLogin.loginPage}")
private String loginPage;
#Bean
public StandardPasswordEncoder encoder() {
return new StandardPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage(loginPage)
.permitAll()
.loginProcessingUrl("/tdk/login")
.failureUrl("/tdk/login?error=true")
.defaultSuccessUrl("/events/list")
.and()
.exceptionHandling()
.accessDeniedPage("/denied")
.and()
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/mockup/**").permitAll()
.antMatchers("/users/**").permitAll()
.antMatchers("/books/**").permitAll()
.antMatchers("/welcome/**").authenticated()
.and()
.logout()
.permitAll()
.logoutSuccessUrl("/index.html");
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.passwordEncoder(new StandardPasswordEncoder())
.withUser("test1").password("c1f02fa50809b7f715576198eda6466cd17f63404ae6eded7c22290b025baf3868bc8f785267d4ae").roles("ADMIN").and()
.withUser("test2").password("test2").roles("USER").and()
.withUser("test3").password("test3").roles("SUPERADMIN");
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertyDefaultConfig() {
return new PropertySourcesPlaceholderConfigurer();
}
}
This is my Junit Tests that works properly
public class StandardPasswordEncoderTests {
#Test
public void getPasswordForTest1() {
StandardPasswordEncoder encoder = new StandardPasswordEncoder();
String password = "test1";
assertTrue(
encoder.matches(password, "c1f02fa50809b7f715576198eda6466cd17f63404ae6eded7c22290b025baf3868bc8f785267d4ae"));
}
}
Here my login template
<form th:action="#{/tdk/login}" method="post">
<p th:if="${param.error}">
Bad Credentials ${param.error}
</p>
<p th:if="${loginError}" class="error">Wrong user or password</p>
<div class="input_label"><i class="fa fa-user"></i><input type="text" name="user" placeholder="User" /></div>
<div class="input_label"><i class="fa fa-key"></i><input type="password" name="pass" placeholder="Password" /></div>
<input type="submit" value="LOGIN" />
</form>
But whatever I put:
test1 / c1f02fa50809b7f715576198eda6466cd17f63404ae6eded7c22290b025baf3868bc8f785267d4ae
or
test2 / test2
I see the message Bad Credentials ${param.error} in the output of my template
The parameter names for username and password in your login page are not matching the names in Spring Security configuration.
You could change the Spring Security configuration to use the parameter names from your login page. Or you could change the login page to use the default parameter names.
See FormLoginConfigurer#usernameParameter:
The HTTP parameter to look for the username when performing authentication. Default is "username".
and FormLoginConfigurer#passwordParameter:
The HTTP parameter to look for the password when performing authentication. Default is "password".
Your modified login page (with default parameter names):
<form th:action="#{/tdk/login}" method="post">
<p th:if="${param.error}">
Bad Credentials ${param.error}
</p>
<p th:if="${loginError}" class="error">Wrong user or password</p>
<div class="input_label">
<i class="fa fa-user"></i>
<input type="text" name="username" placeholder="User" />
</div>
<div class="input_label">
<i class="fa fa-key"></i>
<input type="password" name="password" placeholder="Password" />
</div>
<input type="submit" value="LOGIN" />
</form>

Categories

Resources