#RequestMapping(value="/login", method=RequestMethod.POST)
public String userLog(
Model model,
#ModelAttribute("password") String password,
#ModelAttribute("username") String username,
#ModelAttribute("token") String token) {
PasswordResetToken passToken = null;
if(!StringUtils.isEmpty(token)){
passToken = userserv.getPasswordResetToken(token);
if(passToken == null) {
String message = "Invalid Token.";
model.addAttribute("message", message);
return "redirect:/badRequest";
}
User user = passToken.getUser();
username = user.getUsername();
UserDetails userDetails = userSecurityService.loadUserByUsername(username);
Authentication authentication = new
UsernamePasswordAuthenticationToken(userDetails,userDetails.getPassword(), userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
model.addAttribute("user", user);
} else {
UserDetails userDetails = userSecurityService.loadUserByUsername(username);
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
model.addAttribute("user", userDetails);
}
return "redirect:/profile";
}
How do I display a username of the session context holder in thymeleaf?
When I use these live.
<h2 class="section-headline h2"><span th:text="${user.username}"></span></h2>
I get error.
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Tue Aug 18 14:01:28 EAT 2020
There was an unexpected error (type=Internal Server Error, status=500).
An error happened during template parsing (template: "class path resource [templates/users/profile.html]")
org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/users/profile.html]") etc.
But when I remove the line then it works well but without displaying the details of the person logged in.
Please help
Related
This is my LoginController
#PostMapping("/login")
#Transactional
public String login(#Valid #ModelAttribute("login") LoginRequest loginRequest, BindingResult result, HttpServletResponse response, Model model) {
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
boolean no = authentication.isAuthenticated();
if (!no) {
model.addAttribute("loginError", true);
logger.info("User is not auth");
return "form_login";
}
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal();
ResponseCookie jwtCookie = jwtUtils.generateJwtCookie(user);
response.addHeader(HttpHeaders.SET_COOKIE, jwtCookie.toString());
model.addAttribute("login", loginRequest);
return "redirect:/api/test/homePage";
}
As you can see, I want to check is user authenticated, I mean does user insert good credentials. You can see that I tried like this:
boolean no = authentication.isAuthenticated();
if (!no) {
model.addAttribute("loginError", true);
logger.info("User is not auth");
return "form_login";
}
But that return me a Whitelabel Error Page with 403 Forbidden, while I want to display error message on Thymeleaf user.
This is in thymeleaf:
<p th:if="${loginError}" class="error">Wrong user or password</p>
How I can check user credentials that he insert so if he enter wrong credentials show him wrong password/username?
Also my logger.info("User is not auth"); is not being printed even when user enter wrong credentials but I get 403 Forbidden.
So I am trying to authenticate a user using a POST login API but the issue is when the user does not exist (meaning username (unique) not in the database) the thrown error message is not displayed on the client side (POSTMAN). I tried debugging and the error is thrown but not displayed all I see is Status: 401 Unauthorized from POSTMAN
But when the user exists but the password doesn't match, it displays the correct thrown error message. NOTE: I am using spring's OAuth 2.0 Resource Server JWT
Controller method
#PostMapping(path = "/login", consumes = "application/json")
public ResponseEntity<?> login(#Valid #RequestBody UserDTO userDTO) throws UsernameNotFoundException {
LOGGER.info("Authenticating {}", userDTO.getUsername());
userDTOService.confirmUser(userDTO); // Where the issue occurs
Authentication authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(userDTO.getUsername(), userDTO.getPassword()));
return ResponseEntity.ok()
.header(
HttpHeaders.AUTHORIZATION,
tokenService.generateToken(authentication)
)
.build();
}
Service method (confirm user method)
public void confirmUser(UserDTO userDTO) throws UsernameNotFoundException {
/*
* Check if username exist in the database
* Check if the password provided equals password in database
* */
String username = userDTO.getUsername();
String password = userDTO.getPassword();
Optional<User> user = userRepository.findUserByUserName(username);
// This error is not displayed
if (user.isEmpty()) {
LOGGER.error("User {} does not exist", username);
throw new UsernameNotFoundException(username + " does not exist");
}
boolean checkingCredentials = user
.stream()
.anyMatch(
param ->
param.getUsername().equals(username)
&&
passwordEncoder.matches(password, param.getPassword())
);
if (!checkingCredentials) {
LOGGER.error("Bad user credentials");
throw new RuntimeException("Please check username or password");
}
}
The reason I was getting a 401 instead of the correct error message is because my approach was wrong. I had 2 solutions to this but I am not sure if the 2nd is the industry standard.
The first approach:
Pass the user credentials to a UsernamePasswordToken to generate a token.
Then I the token into the authentication manager to be authenticated
Surround the auth manager in a try catch block to return an exception. The thrown error message will be of your chosen.
The second approach:
I want to check if user exists in the database or else throw Not found exception
If step 1 passed then I want to check the user password trying to log in and the hashed password in the database. If they do not match, I want to throw an invalid password exception
If no error is thrown, then I want to pass the users name, password and authorities into UsernamePasswordAuthenticationToken().
I have a AuthorizationServer which uses password grant_type using spring security. I am using this for mobile application, when a user enter username password to log in, the app calls the token endpoint and generate a token if he/she is an authenticated user. This is all handled by password grant_type itself. For a unsuccessful log in it returns below general error with 400 HTTP status code.
{
"error": "invalid_grant",
"error_description": "Bad credentials"
}
But for my scenario I need customize this error message. Is their a way to change this error message ?
Note that i tried the suggested duplicate question -
Customize authentication failure response in Spring Security using AuthenticationFailureHandler
but it uses the formLogin and it's not working with my implementation.
Thank you,
Rajith
I couldn't find an answer to this problem for many days. Finally, I got help from one of my colleagues. He asked me to follow this tutorial and it worked for me. Now I could transform the default spring framework response to my response template as follows.
{
"status": 400,
"message": "Invalid username or password",
"timestamp": "2020-06-19T10:58:29.973+00:00",
"payload": null
}
But still, we don't know, why authenticationFailure handler is not working. Hope this helps.
If you want to change only the message text in the response, than it will be enough to add the messages.properties file to the classpath of your application with the following content:
AbstractUserDetailsAuthenticationProvider.badCredentials=Invalid username or password
This will lead to the response below:
{
"error": "invalid_grant",
"error_description": "Invalid username or password"
}
Sabin answer is works, but i need to throw the exception using BadCredentialsException,
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private final static Logger LOGGER = LogManager.getLogger(CustomAuthenticationProvider.class);
#Autowired
private UserService userService;
#Override
public Authentication authenticate(final Authentication authentication) throws AuthenticationException{
final String username = authentication.getName();
final String password = authentication.getCredentials().toString();
try {
/* CHECKING USER CREDENTIAL */
/* check account */
User userDetail = userService.findByUsername(username);
if (userDetail == null){
throw new Exception("User not found!");
}
/* check password */
String origPass = Utilities.getEncrypted(new String(Base64.decodeBase64(password)), username);
if(!userDetail.getPassword().equals(origPass)){
throw new Exception("Wrong username or password!");
}
/* check is active */
if(!userDetail.getIsActive()){
throw new Exception("User is not active!");
}
/* check allowance in web type */
if(Access.isWeb()){
if(!userDetail.getIsWeb())
throw new Exception("Web access prohibited!");
}
/* check allowance in mobile type */
if(Access.isMobile()){
if(!userDetail.getIsMobile())
throw new Exception("Mobile access prohibited!");
}
/* do some logs */
userService.login(userDetail);
return new UsernamePasswordAuthenticationToken(userDetail, "{noop}".concat(origPass), userDetail.getAuthorities());
} catch (Exception e) {
LOGGER.error("[OAUTH] Error : " + e.getLocalizedMessage());
throw new BadCredentialsException(e.getLocalizedMessage(), e);
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
What i want is, for
localhost:8080/home -> should be open to only authenticated - home page after login
localhost:8080/home?msg=asdsada -> should be open to anonymous - for login errors like wrong password
This is endpoind:
#GetMapping(value = { "/home"})
public ModelAndView getLoginPage(
#RequestParam(value = "msg", required = false) String message) throws IOException
I tried to add this to security config of spring
.regexMatchers("/home").authenticated()
.regexMatchers("/home?msg=.*").permitAll()
So config became like this:
http
.authorizeRequests().antMatchers(anonymousEndpoints).anonymous()
.antMatchers(permittedEndpoints).permitAll()
.regexMatchers("/home").authenticated()
.regexMatchers("/home?msg=.*").anonymous()
.and()
.authorizeRequests().anyRequest().fullyAuthenticated()
But for wrong password, it does not go to endpoind
localhost:8080/home?msg=asdsada
For logged user, it can go to
localhost:8080/home
also it can go to
localhost:8080/home?msg=asdsada
What am I doing wrong? I can also use endpoind to check if logged in or not. Like:
But i want spring scurity to do this. Give 403 forbidden for example.
#GetMapping(value = { "/home"})
public ModelAndView getLoginPage(
#RequestParam(value = "msg", required = false) String message) throws IOException{
Authentication authentication = SecurityUtil.getAuthentication(false);
if (authentication != null) {
logger.info("User: {} already logged in, redirecting to dashboard", authentication.getName());
web.response.sendRedirect("/dashboard");
return null;
}
else{//not logged in
if (msg != null)//and msg is not null so like wrong password
//do smth
}
return null;
}
Don't configure the specific path in Spring Security Config, just analyze it in the controller method. In config set permitAll for this path, but add an authentication or principal parameter in the method signature:
#GetMapping(value = { "/home"})
public ModelAndView getLoginPage(#RequestParam(value = "msg", required = false) String message, Authentication authentication) throws IOException {
if (msg != null) {
...
} else if (!authentication.isAuthenticated()) {
...
}
...
}
P.S. Method arguments: https://docs.spring.io/spring/docs/5.2.x/spring-framework-reference/web.html#mvc-ann-arguments
I am authenticating user with name and password from my database.
If the user name or password is incorrect then I am throwing a custom Exception.
but I am not getting expected status code and response.
I am new to Jersey and web services.
This is my function which creates a response :
public static Response error(int Status_code, String Request_status ,String data, String Response_error){
ResponseBuilder response = new ResponseBuilder();
response.Status_code = Status_code;
response.Request_status = Request_status;
response.data =data;
response.Response_error = Response_error;
return Response.status(Status_code).entity(response).build();
}
This is my custom Exception class code :
public class InvalidcredentialsException extends WebApplicationException {
public InvalidcredentialsException(Response response ) {
super(response);
}
}
This is how I am throwing this exception in my code if credentials are wrong(user name and password) in AuthenticateUser function in my Model.
throw new InvalidcredentialsException(ResponseBuilder.error(Response.Status.UNAUTHORIZED.getStatusCode(),"success","","invalid credentials"));
When I am checking my API I am getting 204 as response , but I am expecting a JSON with the parameters which I have provided.
I have implemented my API in the following way :
#Path("/user")
public class User {
#POST
#Path("/login")
#Consumes(MediaType.APPLICATION_JSON)
#Produces("application/json")
public void UserAuthentication(UserCredentials user) {
UserModel userAuthentication = new UserModel();
userAuthentication.AuthenticateUser(user);
}
}
I have used the following link to create Exception and throw :
JAX-RS / Jersey how to customize error handling?
This is my authenticate function :
public void AuthenticateUser(UserCredentials user) {
Database db = new Database();
Connection con = null;
PreparedStatement preparedStatement = null;
ResultSet rs = null;
try {
String username = user.getUsername();
String password = user.getPassword();
con = db.getConnection();
if (con != null) {
String selectQuery_UserDetails = "SELECT name,password FROM user WHERE name=? AND password = ?";
preparedStatement = con.prepareStatement(selectQuery_UserDetails);
preparedStatement.setString(1, username);
preparedStatement.setString(2, password);
rs = preparedStatement.executeQuery();
if (!rs.isBeforeFirst()) {
throw new InvalidcredentialsException(ResponseBuilder.error(Response.Status.UNAUTHORIZED.getStatusCode(),"success","","invalid credentials"));
}
}}catch (SQLException e) {
} finally {
db.closeConnection(con);
}
}
Thanks
You are catching but not handling the SQLException. When an error occurs, while accessing or trying to access the database, the exception is ignored and no error response is created. Maybe the database is not accessible or configured incorrectly.
You should wrap the exception into a RuntimeException like javax.ws.rs.InternalServerErrorException or just remove the catch statement. And you should log the error here or in an exception mapper, so that you are able to analyze and fix the problem.
I suggest to wrap the exception and log it like that:
}catch(SQLException e){
logger.log(Level.SEVERE, "db error while authenticate user", e);
throw new InternalServerErrorException("db error while authenticate user", e);
}
Now the runtime exception will be handled by a default exception mapper, which will generate the error response. Additional the error is logged. In this code I used java.util.logging - if necessary adjust it to the logging api you use.