I'm trying to make a blog w/ spring boot. I am using #Validated and groupings for certain requests. For Example hitting my signup route, I validate the email using #NotBlank and #Email on my User model
Controller:
package com.vweinert.fedditbackend.controllers;
import com.vweinert.fedditbackend.security.jwt.AuthEntryPointJwt;
import org.modelmapper.ModelMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.vweinert.fedditbackend.entities.User;
import com.vweinert.fedditbackend.request.auth.LoginRequest;
import com.vweinert.fedditbackend.request.auth.SignupRequest;
import com.vweinert.fedditbackend.dto.AuthDto;
import com.vweinert.fedditbackend.service.inter.UserService;
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
#RequestMapping("/api/auth")
public class AuthController {
private final UserService userService;
private final ModelMapper modelMapper;
private static final Logger logger = LoggerFactory.getLogger(AuthController.class);
public AuthController(UserService userService, ModelMapper modelMapper) {
this.userService = userService;
this.modelMapper = modelMapper;
logger.debug("Auth controller initialized");
}
#PostMapping("/signin")
public ResponseEntity<?> loginUser(#Validated(LoginRequest.class) #RequestBody User loginRequest) {
try {
logger.debug("logging in user {},",loginRequest);
User user = userService.sigInUser(loginRequest);
AuthDto authDto = modelMapper.map(user, AuthDto.class);
return ResponseEntity.ok(authDto);
} catch (Exception e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
#PostMapping("/signup")
public ResponseEntity<?> registerUser(#Validated(SignupRequest.class) #RequestBody User signUpRequest) {
try {
logger.debug("signing up user {},", signUpRequest);
User user = userService.registerUser(signUpRequest);
AuthDto authDto = modelMapper.map(user, AuthDto.class);
return ResponseEntity.ok(authDto);
} catch (Exception e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
}
Model:
package com.vweinert.fedditbackend.entities;
import java.time.LocalDateTime;
import java.util.Set;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.JoinTable;
import jakarta.persistence.Transient;
import jakarta.persistence.FetchType;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonInclude;
import org.hibernate.annotations.CreationTimestamp;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import com.vweinert.fedditbackend.request.auth.LoginRequest;
import com.vweinert.fedditbackend.request.auth.SignupRequest;
#Entity
#Table(name="users")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable=false,unique = true)
#NotBlank(groups = {SignupRequest.class}, message = "email is blank")
#Email(groups = {SignupRequest.class}, message = "invalid email")
private String email;
#Column(nullable=false,updatable = false,unique = true)
#NotBlank(groups = {LoginRequest.class, SignupRequest.class},message = "username is blank")
#Size(min = 8, max = 32, groups = {LoginRequest.class, SignupRequest.class}, message = "username must be between 8 and 32 characters")
private String username;
#Column(nullable=false)
#NotBlank(groups = {LoginRequest.class, SignupRequest.class},message = "Missing password")
#Size(min = 8, max = 32, groups = {LoginRequest.class, SignupRequest.class}, message = "password must be between 8 and 32 characters")
private String password;
#Column(columnDefinition = "text")
private String about;
#Column(nullable=false,updatable = false)
#CreationTimestamp
private LocalDateTime createdAt;
private LocalDateTime passwordChangedAt;
private LocalDateTime aboutChangedAt;
#Column(nullable = false)
#Builder.Default
private Boolean deleted = false;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable( name = "user_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
#OneToMany(mappedBy = "user",fetch = FetchType.LAZY)
private Set<Post> posts;
#OneToMany(mappedBy = "user",fetch = FetchType.LAZY)
private Set<Comment> comments;
#Transient
#JsonInclude()
private String jwt;
public User(String username, String email, String password){
this.username = username;
this.email = email;
this.password = password;
}
}
Signup request interface for the #Validated groups
package com.vweinert.fedditbackend.request.auth;
public interface SignupRequest {
}
Web Security Config:
package com.vweinert.fedditbackend.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.vweinert.fedditbackend.security.jwt.AuthEntryPointJwt;
import com.vweinert.fedditbackend.security.jwt.AuthTokenFilter;
import com.vweinert.fedditbackend.security.services.UserDetailsServiceImpl;
import com.vweinert.fedditbackend.security.jwt.JwtUtils;
#Configuration
#EnableGlobalMethodSecurity(
prePostEnabled = true)
public class WebSecurityConfig {
private final UserDetailsServiceImpl userDetailsService;
private final AuthEntryPointJwt unauthorizedHandler;
private final JwtUtils jwtUtils;
public WebSecurityConfig(JwtUtils jwtUtils, UserDetailsServiceImpl userDetailsService, AuthEntryPointJwt unauthorizedHandler) {
this.jwtUtils = jwtUtils;
this.userDetailsService = userDetailsService;
this.unauthorizedHandler = unauthorizedHandler;
}
#Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter(jwtUtils,userDetailsService);
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/home/**").permitAll()
.anyRequest().authenticated()
);
http.authenticationProvider(authenticationProvider());
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
Authentication entry Point:
package com.vweinert.fedditbackend.security.jwt;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
#Component
public class AuthEntryPointJwt implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class);
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
logger.error("Unauthorized error: {}", authException.getMessage());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
final Map<String, Object> body = new HashMap<>();
body.put("status", HttpServletResponse.SC_UNAUTHORIZED);
body.put("error", "Unauthorized");
body.put("message", authException.getMessage());
body.put("path", request.getServletPath());
final ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getOutputStream(), body);
}
}
Currently I am getting an AuthenticationException going through my commence method that implements the AuthenticationEntryPoint class. I would like to have custom exceptions that simply return the message attached to my #Email annotation, though I'm really not sure how to do this.
When I do a postman call with this (with no authorization and an intentionally invalid email):
{
"email":"emailssss",
"username":"emailsssssssss",
"password":"emailly123theemail"
}
I get:
{
"path": "/error",
"error": "Unauthorized",
"message": "Full authentication is required to access this resource",
"status": 401
}
and in the console I get:
2023-01-04T14:37:25.768-06:00 WARN 36022 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public org.springframework.http.ResponseEntity<?> com.vweinert.fedditbackend.controllers.AuthController.registerUser(com.vweinert.fedditbackend.entities.User): [Field error in object 'user' on field 'email': rejected value [emailssss]; codes [Email.user.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.email,email]; arguments []; default message [email],[Ljakarta.validation.constraints.Pattern$Flag;#49be6e2e,.*]; default message [invalid email]] ]
2023-01-04T14:37:25.774-06:00 ERROR 36022 --- [nio-8080-exec-1] c.v.f.security.jwt.AuthEntryPointJwt : Unauthorized error: Full authentication is required to access this resource
I would like to get something like:
{
"message": "invalid email"
}
added
.requestMatchers("/error").permitAll()
To my filterChain in my WebSecurityConfig file. I did not know that when #Validated fails to validate it redirects to /error
Related
While there is user in the database, another user with exactly the same credentials is "successfully" inserted in the database...
Hello! I'm building my own ecommerce app and I included spring security. Now, while I was developing security part of the app, I tried it to see if it's working and once I entered desired info in the request body, for the first request the user was successfully inserted in the database, but when I tried to do it the second time, to check if the userExists which throws and error that user is already registered, works, it just added another user with the same credentials (name, lastname, mail and so). I go to userRepository to check if there is already the user with the same email, it makes sense what i wrote, but it doesnt work...Please help...Here are all the files:
EDIT : Mistakenly copied UserRegistrationController twice...So here is the userService.java:
package com.marin.thrift.service;
import com.marin.thrift.dao.UserRepository;
import com.marin.thrift.entity.User;
import lombok.AllArgsConstructor;
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.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
#Service
#AllArgsConstructor
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
private final static String USER_NOT_FOUND = "user with email %s not found";
private final BCryptPasswordEncoder bCryptPasswordEncoder;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return userRepository.findByUsername(email).orElseThrow(()-> new UsernameNotFoundException(String.format(USER_NOT_FOUND, email)));
}
public String singUpUser(User user){
boolean userExists = userRepository.findByUsername(user.getEmail()).isPresent();
if(userExists){
return "user already in place";
}
String encodedPassword = bCryptPasswordEncoder.encode(user.getPassword());
user.setPassword(encodedPassword);
userRepository.save(user);
return "it works";
}
}
package com.marin.thrift.registration;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping(path = "api/v1/registration")
#AllArgsConstructor
public class UserRegistrationController {
private RegistrationService registrationService;
#PostMapping
public String register(#RequestBody registrationRequest request){
return registrationService.register(request);
}
}
package com.marin.thrift.registration;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping(path = "api/v1/registration")
#AllArgsConstructor
public class UserRegistrationController {
private RegistrationService registrationService;
#PostMapping
public String register(#RequestBody registrationRequest request){
return registrationService.register(request);
}
}
package com.marin.thrift.registration;
import com.marin.thrift.entity.Role;
import com.marin.thrift.entity.User;
import com.marin.thrift.service.UserService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
#Service
#AllArgsConstructor
public class RegistrationService {
private final EmailValidator emailValidator;
private final UserService userService;
public String register(registrationRequest request) {
Boolean isValidEmail = emailValidator.test(request.getEmail());
if (!isValidEmail){
throw new IllegalStateException("Email is not valid");
}
return userService.singUpUser(new User(request.getFirstName(), request.getLastName(),
request.getPassword(), request.getEmail(), Role.USER));
}
}
package com.marin.thrift.entity;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.persistence.*;
import java.util.Collection;
import java.util.Date;
#Entity
#Data
#Table(name = "users")
public class User implements UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "date_of_birth")
private Date dateOfBirth;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Column(name = "email")
private String email;
private Role role;
private Boolean locked = false;
private Boolean enabled = false;
public User(String firstName, String lastName, String password, String email, Role role) {
this.firstName = firstName;
this.lastName = lastName;
this.password = password;
this.email = email;
this.role = role;
}
public User() {
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.name());
return null;
}
#Override
public String getPassword() {
return password;
}
#Override
public String getUsername(){
return email;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return !locked;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return enabled;
}
}
package com.marin.thrift.security.config;
import com.marin.thrift.service.UserService;
import lombok.AllArgsConstructor;
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;
#Configuration
#AllArgsConstructor
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserService userService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception{
http.csrf()
.disable().authorizeRequests()
.antMatchers("/api/v*/registration/**").permitAll()
.anyRequest().authenticated().and().formLogin();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.authenticationProvider(daoAuthenticationProvider());
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider(){
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(bCryptPasswordEncoder);
provider.setUserDetailsService(userService);
return provider;
}
}
package com.marin.thrift.registration;
import lombok.*;
#Getter
#AllArgsConstructor
#EqualsAndHashCode
#ToString
public class registrationRequest {
private final String firstName;
private final String lastName;
private final String password;
private final String email;
}
Without UserService it's hard to say what is happen. But if I see your jpa mapping I can guess that if you want unique entry per unique user, you have to delete generated column id. And instead you have to put #Id annotation on email. In this case you need also make changes in sql table.
Or as alternative you can stay by current mapping and simply conduct findByEmail before save entity. And if Optional is not empty - then user is already in database registred.
I just fixed my mistake. The mistake was in the database where I forgot to set email as unique. Once I did that, the code works perfectly.
I try to realise an authorization, but cannot get the cause of this problem: Encoded password does not look like BCrypt
My actions sequence: open localhost:8080/login, write "user" in login input and "password" in password (user with such login and password is guaranteed to exist), submit, here the problem appears (Encoded password does not look like BCrypt is written in console), login is failed.
I am new at Spring, so I need your experienced advice. I guess, the reason is in the auth.userDetailsService(userService).passwordEncoder(passwordEncoder()); in protected void configure(AuthenticationManagerBuilder auth) method in WebSecurityConfig.java, but I cannot apply any solution I've found.
WebSecurityConfig.java
package com.todo.todo.config;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import com.todo.todo.services.UserService;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig{
#Autowired
private UserService userService;
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.antMatchers("/",
"/index",
"/users",
"/registrate",
"/deleteuser/**",
"/webjars/**").permitAll()
.anyRequest().authenticated()
)
.formLogin((form) -> form
.loginPage("/login")
.permitAll()
)
.logout((logout) -> logout.permitAll());
return http.build();
}
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
UserService.java
package com.todo.todo.services;
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 com.todo.todo.repositories.UserRepository;
#Service
public class UserService implements UserDetailsService{
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails userDetails = userRepository.findByUsername(username);
if(userDetails == null) throw new UsernameNotFoundException("No such username");
return userDetails;
}
}
UserRepository.java
package com.todo.todo.repositories;
import org.springframework.data.jpa.repository.JpaRepository;
import com.todo.todo.models.User;
public interface UserRepository extends JpaRepository<User, Long>{
User findByUsername(String username);
}
UserController.java
package com.todo.todo.controllers;
import java.time.Instant;
import java.util.Collections;
import java.util.Map;
import java.util.NoSuchElementException;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
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.PostMapping;
import org.springframework.web.servlet.ModelAndView;
import com.todo.todo.models.Role;
import com.todo.todo.models.User;
import com.todo.todo.repositories.UserRepository;
#Controller
public class UserController {
private final String registratePage = "registrate";
// private final String loginPage = "login";
private final String redirectLoginPage = "redirect:/login";
private final String redirectUsersPage = "redirect:/users";
#Autowired
private UserRepository userRepository;
#GetMapping("/users")
public ModelAndView getHomePage(#AuthenticationPrincipal User user){
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("users");
modelAndView.addObject("users", userRepository.findAll());
return modelAndView;
}
#GetMapping("/deleteuser/{id}")
public String deleteTask(#PathVariable("id") Long id, Model model){
User user = userRepository.findById(id).orElseThrow(() -> new NoSuchElementException("User not found by id = " + id));
userRepository.delete(user);
return redirectUsersPage;
}
#GetMapping("/registrate")
public String getRegistratePage(){
return registratePage;
}
#PostMapping("/registrate")
public String registrateUser(#Valid User user, Map<String, Object> map){
User userFromDatabase = userRepository.findByUsername(user.getUsername());
if(userFromDatabase != null){
map.put("message", "User has been already registrated!");
return registratePage;
}
user.setCreatedDate(Instant.now());
user.setRoles(Collections.singleton(Role.USER));
userRepository.save(user);
map.put("message", "User has been successfully registrated!");
return redirectLoginPage;
}
}
User.java
package com.todo.todo.models;
import java.time.Instant;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.CollectionTable;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Table;
import javax.validation.constraints.NotBlank;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import lombok.Getter;
import lombok.Setter;
#Getter
#Setter
#Entity
#Table(name = "usr")
public class User implements UserDetails{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotBlank(message = "Fill username")
private String username;
#NotBlank(message = "Fill password")
private String password;
private Instant createdDate;
#ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER)
#CollectionTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"))
#Enumerated(EnumType.STRING)
private Set<Role> roles;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
this.createdDate = Instant.now();
this.roles = new HashSet<>();
}
#Override
public String toString() {
return String.format("User{id=%d, username='%s', password='%s', createdDate='%s'}",
id, username, password, createdDate);
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return getRoles();
}
#Override
public String getUsername() {
return username;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>LOGIN</title>
</head>
<body>
<div th:if="${param.error}">
Invalid username and password.
</div>
<div th:if="${param.logout}">
You have been logged out.
</div>
<span th:text="${message}"></span>
<form th:action="#{/login}" method="POST">
<div><label> Username : <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>
Create new user
To users
</body>
</html>
I solved this
WebSecurityConfig.java
package com.todo.todo.config;
import org.springframework.beans.factory.annotation.Autowired;
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.crypto.bcrypt.BCryptPasswordEncoder;
import com.todo.todo.services.UserService;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.antMatchers("/",
"/index",
"/tasks",
"/users",
"/registrate",
"/logout",
"/deleteuser/**",
"/create",
"/delete/**",
"/update/**",
"/create_task",
"/update_task",
"/h2-console/**",
"/webjars/**").permitAll()
.anyRequest().authenticated()
)
.formLogin((form) -> form
.loginPage("/login")
.permitAll()
)
.logout((logout) -> logout.permitAll());
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userService)
.passwordEncoder(new BCryptPasswordEncoder());
}
}
UserController.java
Added encoding password in #PostMapping("/registrate")
// some imports here
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
// some methods here
#PostMapping("/registrate")
public String registrateUser(#Valid User user, Map<String, Object> map){
User userFromDatabase = userRepository.findByUsername(user.getUsername());
if(userFromDatabase != null){
map.put("message", "User has been already registrated!");
return registratePage;
}
String encodedPassword = new BCryptPasswordEncoder().encode(user.getPassword());
user.setPassword(encodedPassword);
user.setCreatedDate(Instant.now());
user.setRoles(Collections.singleton(Role.USER));
userRepository.save(user);
map.put("message", "User has been successfully registrated!");
return redirectLoginPage;
}
Not sure what is happening here. The error is
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'authenticationController': Unsatisfied dependency expressed through field 'userService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService' - Unsatisfied dependency expressed through constructor parameter 1"
Am I missing something here?
My controller -
import com.***.model.User;
import com.***.service.AuthTokenService;
import com.***.service.Authentication;
import com.***.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
#Controller
#RestController
#RequestMapping("/api/v1")
public class AuthenticationController {
#Autowired
UserService userService;
AuthTokenService authTokenService;
#GetMapping(path = "/users")
public ResponseEntity<?> listUser() {
List<User> resource = userService.getUser();
return ResponseEntity.ok(resource);
}
#PostMapping(path = "/register")
public ResponseEntity<?> register(#RequestBody User newUser) {
User user = userService.findUserByEmail(newUser.getEmail());
User unconfirmedUser = userService.registerUser(newUser);
return ResponseEntity.ok(unconfirmedUser);
}
My UserService -
import com.***.model.User;
import com.***.repository.UserRepository;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.util.List;
#Service
#Component
#AllArgsConstructor
public class UserService {
#Autowired
UserRepository userRepository;
AuthTokenService authTokenService;
EmailSenderService emailSenderService;
void sendAuthenticationEmail(String userMail, String token) {
final SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setTo(userMail);
mailMessage.setSubject("Mail Confirmation Link!");
mailMessage.setFrom("<Mail Service>");
mailMessage.setText( "Thank you for joining ***! Please click below to activate your account." + "http://8080/api/v1/confirm?token=" + token);
emailSenderService.sendEmail(mailMessage);
}
public User registerUser(User user) {
final Authentication authenticationToken = new Authentication(user);
authTokenService.saveAuthenticationToken(authenticationToken);
sendAuthenticationEmail(user.getEmail(), authenticationToken.getUserToken());
return userRepository.save(user);
}
public void confirmUser(Authentication authenticationToken) {
final User user = authenticationToken.getUser();
user.setEnabled(true);
userRepository.save(user);
authTokenService.deleteAuthenticationToken((long) authenticationToken.getId());
}
}
Authentication -
import com.***.model.User;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
import java.time.LocalDate;
import java.util.UUID;
#Entity
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Authentication {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String userToken;
private LocalDate dateTimeCreated;
#OneToOne(targetEntity = User.class, fetch = FetchType.EAGER)
#JoinColumn(nullable = false, name = "id")
private User user;
Authentication(User user) {
this.user = user;
this.dateTimeCreated = LocalDate.now();
this.userToken = UUID.randomUUID().toString();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserToken() {
return userToken;
}
public void setUserToken(String userToken) {
this.userToken = userToken;
}
public LocalDate getDateTimeCreated() {
return dateTimeCreated;
}
public void setDateTimeCreated(LocalDate dateTimeCreated) {
this.dateTimeCreated = dateTimeCreated;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
Authentication token repo -
package com.***.repository;
import com.***.service.Authentication;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
#Repository
public interface AuthenticationTokenRepository extends CrudRepository<Authentication, Long> {
Optional<Authentication> findAuthenticationToken(String token);
}
Picture of file structure HERE
Log of error
Based on your error it looks like you don't know how to create JpqRepository method signature based queries. You can read about it here : https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods
I'm guessing that your method in AuthenticationTokenRepository called findAuthenticationToken(String someField) should be named findBySomeField(String someField). Also make sure you have getter for someField
Would be easier if you could provide your AuthenticationTokenRepository and Authenctication classes
Edit:
Change your method in AuthenticationTokenRepository to findByUserToken(String userToken)
This question already has answers here:
Springboot Security hasRole not working
(3 answers)
Closed 4 years ago.
I am using spring security in my spring boot app.
My app works well with in-memory authentication.
But when users are loaded from the database it does not authenticate. It returns 403 access denied error code. UserDetailsService is able to fetch the user information from the database but I dont know where it is going wrong. I am new to spring.
here is my complete code related to security
User.java
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import lombok.Data;
#Entity
#Table(name = "user")
#Data
public class User {
#Id
#NotNull
#GeneratedValue(strategy = GenerationType.AUTO)
private Long userId;
#Column(name = "USERNAME", unique = true)
#NotNull
private String username;
#Column(name = "PASSWORD")
#NotNull
private String password;
#Column(name = "DISPLAY_NAME")
private String displayName;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "id")
private Set<Role> userRoles;
private String profilePicturePath;
}
Role.java
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Data;
#Entity
#Table(name = "roles")
#Data
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String role;
}
UserRepository.java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.cloudsofts.cloudschool.people.users.pojos.User;
#Repository("userRepository")
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
RoleRepository.java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.cloudsofts.cloudschool.people.users.pojos.Role;
#Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
}
UserService.java
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import com.cloudsofts.cloudschool.people.users.pojos.User;
import com.cloudsofts.cloudschool.people.users.repositories.UserRepository;
#Service
public class UserService {
#Autowired
UserRepository userRep;
#Autowired
private PasswordEncoder passwordEncoder;
public List<User> getAllUsers() {
List<User> users = userRep.findAll();
return users;
}
public void addUser(User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
userRep.save(user);
}
public void updateUser(User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
userRep.save(user);
}
public void deleteUser(Long id) {
userRep.delete(id);
}
public User getUser(Long id) {
return userRep.findOne(id);
}
}
RoleService.java
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.cloudsofts.cloudschool.people.users.pojos.Role;
import com.cloudsofts.cloudschool.people.users.repositories.RoleRepository;
#Service
public class RoleService {
#Autowired
RoleRepository userRoleRep;
public void addUserRole(Role role) {
userRoleRep.save(role);
}
public void updateUserRole(Role role) {
userRoleRep.save(role);
}
public void deleteUserRole(Long id) {
userRoleRep.delete(id);
}
public Role getUserRole(Long id) {
return userRoleRep.findOne(id);
}
public List<Role> getAllUserRoles() {
return userRoleRep.findAll();
}
}
CustomUserDetails.java
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import lombok.Data;
#Data
public class CustomUserDetails implements UserDetails {
private static final long serialVersionUID = 1L;
private User user;
public CustomUserDetails(final User user) {
this.user = user;
}
public CustomUserDetails() {
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
final Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
Set<Role> roles = null;
if (user != null) {
roles = user.getUserRoles();
}
if (roles != null) {
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority(role.getRole()));
}
}
return authorities;
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
CustomUserDetailsService.java
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 com.cloudsofts.cloudschool.people.users.pojos.CustomUserDetails;
import com.cloudsofts.cloudschool.people.users.pojos.Role;
import com.cloudsofts.cloudschool.people.users.pojos.User;
import com.cloudsofts.cloudschool.people.users.repositories.UserRepository;
#Service("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(username);
} else {
System.out.println("______________________________________________________________");
System.out.println("username: " + user.getUsername());
System.out.println("password: " + user.getPassword());
System.out.println("Roles: ");
for (Role role : user.getUserRoles()) {
System.out.println(role.getRole());
}
System.out.println("______________________________________________________________");
return new CustomUserDetails(user);
}
}
}
SecurityConfig.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import com.cloudsofts.cloudschool.security.CustomUserDetailsService;
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Qualifier("userDetailsService")
#Autowired
CustomUserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
try {
auth.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder());
System.out.println("_________________________________________________");
String username = SecurityContextHolder.getContext().getAuthentication().getName();
System.out.println("_________________________________________________");
System.out.println("You have logged in as " + username);
System.out.println("_________________________________________________");
} catch (Exception e) {
System.out.println("_________________________________________________");
System.out.println(e.getMessage());
System.out.println("_________________________________________________");
}
}
#Bean(name = "passwordEncoder")
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/*
* #Autowired public void configureGlobal(AuthenticationManagerBuilder auth)
* throws Exception {
* auth.inMemoryAuthentication().withUser("student").password("student").roles(
* "student").and().withUser("admin") .password("admin").roles("admin"); }
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
// http.authorizeRequests().anyRequest().permitAll();
// http.authorizeRequests().antMatchers("/api/**").permitAll();
http.authorizeRequests().antMatchers("/student/**").hasAnyRole("student", "admin");
http.authorizeRequests().antMatchers("/api/admin/**").hasRole("admin");
http.authorizeRequests().antMatchers("/library/**").hasAnyRole("librarian", "admin");
http.httpBasic();
// http.formLogin().and().logout().logoutSuccessUrl("/login?logout").permitAll();
}
}
Screenshots
Postman Screenshot
Browser Screenshot
Users in the db
Roles in the db
User-Role mapping
Console output after giving the credentials
You seems to using the BCryptPasswordEncoder to encrypt and decrypt password. But 'Users' table screenshot shows the password in plain text. Can you validate the place where you are saving or updating the user, it is actually encoding the password and also the password encoder bean is of type 'BCryptPasswordEncoder'
I got the problem solved.
I had to add ROLE_ as a prefix to the roles.
Now everything works fine
I am going to implement spring security on my spring boot application. For that I usually follow Spring boot and Spring Security integration for Rest API's and I got details at SpringSecurity : Authenticate User with Custom UserDetailsService.
Entity: User and Role creates 3 tables into database (user, role & user_roles)
User
package com.maxpro.entity;
import javax.persistence.*;
import java.util.Set;
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String username;
private String password;
private boolean enabled;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
// getters, setters & toString
}
Role
package com.maxpro.entity;
import javax.persistence.*;
import java.util.Set;
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String role;
#ManyToMany(mappedBy = "roles", fetch = FetchType.LAZY)
private Set<User> users;
// getters, setters & toString
}
Repository: UserRepository
package com.maxpro.repository;
import com.maxpro.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository("userRepository")
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
Config: WebSecurityConfigurer uses WebUserDetailsService custom authentication builder
WebSecurityConfigurer
package com.maxpro.security;
import com.maxpro.repository.UserRepository;
import com.maxpro.security.service.WebUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
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.web.util.matcher.AntPathRequestMatcher;
#Configuration
#EnableWebSecurity
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
#Autowired
private UserRepository userRepository;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsServiceBean());
}
#Override
public UserDetailsService userDetailsServiceBean() throws Exception {
return new WebUserDetailsService(userRepository);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/css/**", "/img/**", "/js/**").permitAll()
.antMatchers("/", "/home").permitAll()
.antMatchers("/admin/**").hasAuthority("ADMIN")
.antMatchers("/user/**").hasAuthority("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.usernameParameter("username").passwordParameter("password").permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
// .and()
// .exceptionHandling().accessDeniedPage("/403")
// .and()
// .csrf();
;
}
}
WebUserDetailsService
package com.maxpro.security.service;
import com.maxpro.entity.Role;
import com.maxpro.entity.User;
import com.maxpro.repository.UserRepository;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
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 javax.transaction.Transactional;
import java.util.HashSet;
import java.util.Set;
#Transactional
public class WebUserDetailsService implements UserDetailsService {
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(WebUserDetailsService.class);
private UserRepository userRepository;
public WebUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try {
User user = userRepository.findByUsername(username);
if (user == null) {
LOGGER.debug("user not found with the provided username");
return null;
}
LOGGER.debug(" user from username " + user.toString());
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getAuthorities(user));
}
catch (Exception e){
throw new UsernameNotFoundException("User not found");
}
}
private Set<GrantedAuthority> getAuthorities(User user){
Set<GrantedAuthority> authorities = new HashSet<>();
for(Role role : user.getRoles()) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role.getRole());
authorities.add(grantedAuthority);
}
LOGGER.debug("user authorities are " + authorities.toString());
return authorities;
}
}
Problems: Sometime it can authenticate and sometime not. Why?
Questions: Is there any problem with loadUserByUsername() in WebUserDetailsService custom authentication builder?
Notes: I figured out that username is passed to loadUserByUsername() in WebUserDetailsService class. I think UserRepository returns null User even though I passed correct username to the repository.
Resources: Here is my codes on Github.