So I am working on a Spring boot project and security is one of the things I want to have in this project.
I am having this problem where my code always goes to the BadCredentialsException, but I thought my credentials are correct.
My AuthenticationController:
#RestController
#CrossOrigin
public class JwtAuthenticationController {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Autowired
private JwtUserDetailsService userDetailsService;
#RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(#RequestBody JwtRequest authenticationRequest) throws Exception {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
final UserDetails userDetails = userDetailsService
.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ResponseEntity<?> saveUser(#RequestBody UserDTO user) throws Exception {
return ResponseEntity.ok(userDetailsService.save(user));
}
}
My UserDetailsService:
#Service
public class JwtUserDetailsService implements UserDetailsService {
#Autowired
private UserDao userDao;
#Autowired
private PasswordEncoder bcryptEncoder;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
DAOUser user = userDao.findByUsername(username);
if (user == null){
throw new UsernameNotFoundException("User not found with username: " + username);
}
return new User(user.getUsername(), user.getPassword(), new ArrayList<>());
}
public DAOUser save(UserDTO user) {
DAOUser newUser = new DAOUser();
newUser.setUsername(user.getUsername());
newUser.setPassword(bcryptEncoder.bCryptPasswordEncoder().encode(user.getPassword()));
return userDao.save(newUser);
}
}
My WebSecurityConfig:
#Configuration
#AllArgsConstructor
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {//provides security for endpoints
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
private final AccountService accountService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// configure AuthenticationManager so that it knows from where to load
// user for matching credentials
// Use BCryptPasswordEncoder
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()//So we can send post requests without being rejected(if we using form based indication we want to enable this)
.authorizeRequests()
.antMatchers("/authenticate","/register")
.permitAll()//any request that goes trough that end point we want to allow.
.anyRequest()
.authenticated().and().exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider =
new DaoAuthenticationProvider();
provider.setPasswordEncoder(bCryptPasswordEncoder);
provider.setUserDetailsService(accountService);
return provider;
}
}
My RequestFilter:
#Component
public class JwtRequestFilter extends OncePerRequestFilter {
#Autowired
private JwtUserDetailsService jwtUserDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("")){
jwt = authorizationHeader.substring(7);
username = jwtTokenUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null){
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwt, userDetails)){
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
My PasswordEncoder:
#Configuration
public class PasswordEncoder{
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
My UserDao
#Entity
#Table(name = "myusers")
public class DAOUser {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column
private String username;
#Column
#JsonIgnore
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
My UserDao Interface
I know the name is confusing, but I just followed the tutorial and he gave it this name, I have not changed it, because I want it to work before I rename files.
#Repository
public interface UserDao extends CrudRepository<DAOUser, Integer> {
DAOUser findByUsername(String username);
}
I think these are all the files you need to help me. if you need more, just ask and I will upload them.
Can anyone help me with this problem?
Thanks!!
if you read the spring documentation on passwords (which you should of done before you ask here)
you will see that the format when storing a password is:
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
While when you are storing it you are just storing it and not adding the {bcrypt} prefix to the generated string before you store it.
newUser.setPassword(bcryptEncoder.bCryptPasswordEncoder().encode(user.getPassword()));
So when you are later providing a password, you are getting bad password since spring does not know what encoder to use when decoding the password.
Related
I can't seem to find the 403 error problem, when I send a JSON request to register in Postman I get the 403 Forbidden error with no message.
Postman Screenshot
SecureConfig.Java
This is the configuration file.
#EnableWebSecurity
#AllArgsConstructor
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
#Bean(BeanIds.AUTHENTICATION_MANAGER)
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(HttpSecurity httpSecurity) throws Exception{
httpSecurity.csrf()
.disable()
.authorizeRequests()
.antMatchers("/api/auth/**")
.permitAll()
.anyRequest()
.authenticated();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
AuthConroller
This is the controller of the project to authenticate, when I sign up the new user was added to the database successfully, it sends afterwards a mail verification to the fake mail using Mailtrap " https://mailtrap.io " . But when I request to receive the authentication token and the username I receive the error.
#RestController
#AllArgsConstructor
#RequestMapping(value = "/api/auth", consumes = MediaType.APPLICATION_JSON_VALUE)
public class AuthController {
private final AuthService authService;
#PostMapping("/signup")
public ResponseEntity<String> signup(#RequestBody RegisterRequest registerRequest ) {
authService.signup(registerRequest);
return new ResponseEntity<>("User's Registration was successful", HttpStatus.OK);
}
#GetMapping("accountVerification/{token}")
public ResponseEntity<String> verifyAccount(#PathVariable String token){
authService.verifyAccount(token);
return new ResponseEntity<>("Account created successfully", HttpStatus.OK);
}
#PostMapping("/")
public String login(#RequestBody LoginRequest loginRequest){
return authService.login(loginRequest);
}
}
AuthService
This is the autentication service.
#Service
#AllArgsConstructor
public class AuthService {
private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;
private final VerificationTokenRepository verificationTokenRepository;
private final MailService mailService;
private final AuthenticationManager authenticationManager;
private final JwtProvider jwtProvider;
public void signup(RegisterRequest registerRequest){
User user = new User();
user.setUsername(registerRequest.getUsername());
user.setEmail(registerRequest.getEmail());
user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));
user.setEnabled(false);
user.setCreated(Instant.now());
userRepository.save(user);
String token = generetedVerificationToken(user);
mailService.sendMail(new NotificationEmail("Please " +
"Activate Your Account", user.getEmail(), "Thanks " +
"for signing up. Click on the link below to go home: " +
"http://localhost:8080/api/auth/accountVerification/" + token));
}
private String generetedVerificationToken(User user){
String token = UUID.randomUUID().toString();
VerificationToken verificationToken = new VerificationToken();
verificationToken.setToken(token);
verificationToken.setUser(user);
verificationTokenRepository.save(verificationToken);
return token;
}
public void verifyAccount(String token) {
Optional<VerificationToken> verificationToken = verificationTokenRepository.findByToken(token);
fetchUserAndEnable(verificationToken.orElseThrow(() -> new SpringRedditException("Invalid Token")));
}
private void fetchUserAndEnable(VerificationToken verificationToken){
String username = verificationToken.getUser().getUsername();
Optional<org.springframework.security.core.userdetails.User> v = userRepository.findByUsername(username);
UserDetails user = userRepository.findByUsername(username).orElseThrow(() -> new SpringRedditException("User not found with name - " + username));
User s = (User) user;
s.setEnabled(true);
userRepository.save(s);
}
public String encodePassword(String password) { return passwordEncoder.encode(password);}
public String login(LoginRequest loginRequest){
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()));
SecurityContextHolder.getContext().setAuthentication(authentication);
return jwtProvider.generatedToken(authentication);
}
}
JwtProvider
This class is to generate the tokens for each user. I didn't use the JKS I've found in the tutoriel, because it provides an error.
#Service
public class JwtProvider {
// private KeyStore keyStore;
private Key key;
#PostConstruct
public void init() {
key = Keys.secretKeyFor(SignatureAlgorithm.HS512);
}
public String generatedToken(Authentication authentication){
org.springframework.security.core.userdetails.User principal =
(org.springframework.security.core.userdetails.User) authentication.getPrincipal();
return Jwts.builder()
.setSubject(principal.getUsername())
.signWith(key)
.compact();
}
}
UserDetailsServiceImpl
Here goes the implementation that gives the user authority to connect :
#Service
#AllArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
#Override
#Transactional
public UserDetails loadUserByUsername(String username) {
Optional<User> userOptional = userRepository.findByUsername(username);
User user = userOptional.orElseThrow(() -> new UsernameNotFoundException("No such user" +
"with username : " + username));
return new org.springframework.security.core.userdetails.User(user.getUsername(),
user.getPassword(), user.isEnabled(), true,
true, true,
getAuthorities("USER"));
}
private Collection<? extends GrantedAuthority> getAuthorities(String role){
return singletonList(new SimpleGrantedAuthority(role));
}
}
and last
AutheticationResponse
In the DTO file we have this class in order to receive the authenticatonToken and the username in postman as a JSON format.
#Data
#AllArgsConstructor
#NoArgsConstructor
public class AuthenticationResponse {
private String authenticationToken;
private String username;
}
After adding logging.level.org.springframework.security=DEBUG to application.properties file, I managed to see the reason this error appears in the below image.
Debug results Screenshot
I using spring security and jwt . I want login two way : One way by email, password and one way by phone and passcode. If user login by email when login success spring auto return token to user. It same way when user login by phone. I has write code login by email and it login by success and token return to user but i can't know how to implement by phone and passcode. When i implement by mutiple http login form, spring throw me error look like :
Field authenticationManager in com.baotrung.controller.JwtAuthenticationController required a single bean, but 2 were found:
- emailAuthenticationManager: defined by method 'authenticationManagerBean' in class path resource [com/baotrung/config/MultipleSecurityConfig$ApiConfigurationAdapter.class]
- PhoneAuthenticationManager: defined by method 'authenticationManagerBean' in class path resource [com/baotrung/config/MultipleSecurityConfig$PhoneConfigurationAppter.class]
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
Bellow my code
#Configuration
#EnableWebSecurity
public class MultipleSecurityConfig {
#Bean
public static PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Configuration
#Order(1)
public static class ApiConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// configure AuthenticationManager so that it knows from where to load
// user for matching credentials
// Use BCryptPasswordEncoder
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
}
#Bean(name = "emailAuthenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// We don't need CSRF for this example
httpSecurity.csrf().disable()
// dont authenticate this particular request
.authorizeRequests().antMatchers("/authenticate", "/register").permitAll().
// all other requests need to be authenticated
anyRequest().authenticated().and().
// make sure we use stateless session; session won't be used to
// store user's state.
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
#Configuration
#Order(100)
public static class PhoneConfigurationAppter extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private JwtUserPhoneDetailsService jwtUserPhoneDetailsService;
#Autowired
private JwtPhoneRequestFilter jwtPhoneRequestFilter;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// configure AuthenticationManager so that it knows from where to load
// user for matching credentials
// Use BCryptPasswordEncoder
auth.userDetailsService(jwtUserPhoneDetailsService).passwordEncoder(passwordEncoder());
}
#Bean(name = "PhoneAuthenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// We don't need CSRF for this example
httpSecurity.csrf().disable()
// dont authenticate this particular request
.authorizeRequests().antMatchers("/phone/authentication", "/phone/register").permitAll().
// all other requests need to be authenticated
anyRequest().authenticated().and().
// make sure we use stateless session; session won't be used to
// store user's state.
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtPhoneRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
}
Entity
User
public class UserDTO {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
PhoneUser
#Entity
#Table(name = "user_phone")
public class PhoneUser {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column
private String phone;
#Column
#JsonIgnore
private String passcode;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getPasscode() {
return passcode;
}
public void setPasscode(String passcode) {
this.passcode = passcode;
}
}
EmailJwtTokenFilter
#Slf4j
public class JwtAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private JwtTokenProvider tokenProvider;
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
Long userId = tokenProvider.getUserIdFromJWT(jwt);
UserDetails userDetails = customUserDetailsService.loadUserById(userId);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
log.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
}
PhoneJwtTokenFilter
#Slf4j
public class PhoneJwtAuthenticationFilter extends OncePerRequestFilter {
#Autowired
private JwtTokenProvider tokenProvider;
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
Long userId = tokenProvider.getUserIdFromJWT(jwt);
UserDetails userDetails = customUserDetailsService.loadUserById(userId);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
log.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
}
private String getJwtFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
}
AuthenticationController
#RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(#RequestBody JwtRequest authenticationRequest) throws Exception {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
final UserDetails userDetails = userDetailsService
.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
}
#RequestMapping(value = "/register", method = RequestMethod.POST)
public ResponseEntity<?> saveUser(#RequestBody UserDTO user) throws Exception {
return ResponseEntity.ok(userDetailsService.save(user));
}
private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}
I have a question. I need create two class OnePerRequestFilter for email and phone or only one request for both OnePerRequestFilter? . Why spring thow exception authenticationManagerBean ? . And how to impement user login two way : one way in email and one way in phone in same spring security . Thanks you
I successfully build in-memory authentication. But when I going to build it with database comes this error.
There is no PasswordEncoder mapped for the id "null"
This is followed tutorial - Spring Boot Tutorial for Beginners, 10 - Advanced Authentication using Spring Security | Mighty Java
There are classes
SpringSecurityConfiguration.java
#Configuration
#EnableWebSecurity
public class SpringSecurityConfiguration extends
WebSecurityConfigurerAdapter{
#Autowired
private AuthenticationEntryPoint entryPoint;
#Autowired
private MyUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().httpBasic()
.authenticationEntryPoint(entryPoint);
}
}
AuthenticationEntryPoint.java
#Configuration
public class AuthenticationEntryPoint extends BasicAuthenticationEntryPoint{
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.addHeader("WWW-Authenticate", "Basic realm -" +getRealmName());
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.println("Http Status 401 "+authException.getMessage());
}
#Override
public void afterPropertiesSet() throws Exception {
setRealmName("MightyJava");
super.afterPropertiesSet();
}
}
MyUserDetailsService .java
#Service
public class MyUserDetailsService implements UserDetailsService{
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if(user == null){
throw new UsernameNotFoundException("User Name "+username +"Not Found");
}
return new org.springframework.security.core.userdetails.User(user.getUserName(),user.getPassword(),getGrantedAuthorities(user));
}
private Collection<GrantedAuthority> getGrantedAuthorities(User user){
Collection<GrantedAuthority> grantedAuthority = new ArrayList<>();
if(user.getRole().getName().equals("admin")){
grantedAuthority.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
}
grantedAuthority.add(new SimpleGrantedAuthority("ROLE_USER"));
return grantedAuthority;
}
}
UserRepository interface
public interface UserRepository extends JpaRepository<User, Long>{
#Query("FROM User WHERE userName =:username")
User findByUsername(#Param("username") String username);
}
Role.java
#Entity
public class Role extends AbstractPersistable<Long>{
private String name;
#OneToMany(targetEntity = User.class , mappedBy = "role" , fetch = FetchType.LAZY ,cascade = CascadeType.ALL)
private Set<User> users;
//getter and setter
}
User.java
#Entity
public class User extends AbstractPersistable<Long>{
//AbstractPersistable class ignore primary key and column annotation(#Column)
private String userId;
private String userName;
private String password;
#ManyToOne
#JoinColumn(name = "role_id")
private Role role;
#OneToMany(targetEntity = Address.class, mappedBy = "user",fetch= FetchType.LAZY ,cascade =CascadeType.ALL)
private Set<Address> address; //Instead of Set(Unordered collection and not allow duplicates) we can use list(ordered and allow duplicate values) as well
//getter and setter}
If you have any idea plese inform. Thank you.
I changed MyUserDetailsService class adding passwordEncoder method.
Added lines
BCryptPasswordEncoder encoder = passwordEncoder();
Changed Line
//changed, user.getPassword() as encoder.encode(user.getPassword())
return new org.springframework.security.core.userdetails.User(--)
MyUserDetailsService.java
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
BCryptPasswordEncoder encoder = passwordEncoder();
User user = userRepository.findByUsername(username);
if(user == null){
throw new UsernameNotFoundException("User Name "+username +"Not Found");
}
return new org.springframework.security.core.userdetails.User(user.getUserName(),encoder.encode(user.getPassword()),getGrantedAuthorities(user));
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
As from Spring Security 5.x, Spring Security enforces you to use a password encoder if you're working with other than in-memory (production) databases.
Spring Security enforces this by activating the default DelegatingPasswordEncoder, which looks for PasswordEncoder beans.
By adding a BCryptPasswordEncoder, the DelegatingPasswordEncoder will return that instance to encrypt passwords.
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
I don't recommend you to do this, but if you really want to, you can override password encoding by adding {noop} to the password value.
This will treat the password by activating the NoOpPasswordEncoder instead of the default DelegatingPasswordEncoder and will treat your password as plain text.
Please note that this is not recommended if you deploy your app to a production environment!
In my Spring Boot MVC application I used Spring Security to provide authentication and users registration. Authentication and registration of users are working correctly, but after creating user account I want to login him automatically. While doing this I am receiving BadCredentialsException. This user with the same credentials normally is correctly login in with the login form. I appreciate any help from you. Below is my code:
Method from controller
#RequestMapping(value = "/register", method = RequestMethod.POST)
public String registerUser(#ModelAttribute("user") User user, BindingResult result,
WebRequest request, Errors errors) {
User registeredUser = null;
if (result.hasErrors() == false) {
registeredUser = createUserAccount(user, result);
}
if (registeredUser == null) {
return "/register";
}
securityService.autologin(registeredUser.getLogin(), registeredUser.getPassword());
return "/whiprounds";
}
SecurityServiceImpl (method authenticate is throwing exception)
#Service
public class SecurityServiceImpl implements SecurityService {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
private static final Logger logger = LoggerFactory.getLogger(SecurityServiceImpl.class);
#Override
public String findLoggedInLogin() {
Object userDetails = SecurityContextHolder.getContext().getAuthentication().getDetails();
if (userDetails instanceof UserDetails) {
return ((UserDetails) userDetails).getUsername();
}
return null;
}
#Override
public void autologin(String username, String password) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
authenticationManager.authenticate(usernamePasswordAuthenticationToken);
if (usernamePasswordAuthenticationToken.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
logger.debug(String.format("Auto login %s successfully!", username));
}
}
}
UserServiceImpl
#Service
public class UserServiceImpl implements UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Autowired
private RoleRepository roleRepository;
#Override
public User registerNewUserAccount(User user) throws LoginExistsException {
if (loginExists(user.getLogin())) {
throw new LoginExistsException("User with this login already exists");
}
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
user.setRoles(new HashSet<>((Collection<? extends Role>) roleRepository.findAll()));
return userRepository.save(user);
}
private boolean loginExists(String login) {
User user = userRepository.findByLogin(login);
if (user != null) {
return true;
}
return false;
}
}
UserDetailsServiceImpl
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
#Transactional(readOnly = true)
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = userRepository.findByLogin(s);
if (user == null) {
throw new UsernameNotFoundException(s);
}
Set<GrantedAuthority> grantedAuthorities = new HashSet<>();
for (Role role : user.getRoles()) {
grantedAuthorities.add(new SimpleGrantedAuthority(role.getName()));
}
return new org.springframework.security.core.userdetails.User(user.getLogin(), user.getPassword(), grantedAuthorities);
}
}
First of all, this method throwing an exception because of the password problem (As far as I can see from your code). Your method registerNewUserAccount returning User object which password has already been hashed. Then you passing it here:
securityService.autologin(registeredUser.getLogin(), registeredUser.getPassword());
So it turns out that you're passing hashed password into authenticationManager later. This is wrong - you should pass original password into it. Smth like this:
#RequestMapping(value = "/register", method = RequestMethod.POST)
public String registerUser(#ModelAttribute("user") User user, BindingResult result,
WebRequest request, Errors errors) {
User registeredUser = null;
String originalPassword = user.getPassword();
if (result.hasErrors() == false) {
registeredUser = createUserAccount(user, result);
}
if (registeredUser == null) {
return "/register";
}
securityService.autologin(registeredUser.getLogin(), originalPassword);
return "/whiprounds";
}
Second of all, authenticationManager.authenticate(usernamePasswordAuthenticationToken); - this method actually returns filled Authentication object (if authentication was successful), and you should put this object into SecurityContext, and not the one you've passed to `authenticationManager'. Smth like this:
#Override
public void autologin(String username, String password) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
Authentication auth = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
if (auth.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(auth);
logger.debug(String.format("Auto login %s successfully!", username));
}
}
Hope this helps.
I want to use Spring security with MongoDB (using Spring data) and retrieve the users from my own database for spring security. However, I can not do that since my userservice type does not seem to be supported.
This is my UserService class:
public class UserService {
private ApplicationContext applicationContext;
private MongoOperations mongoOperations;
public UserService() {
applicationContext = new AnnotationConfigApplicationContext(MongoConfig.class);
mongoOperations = (MongoOperations) applicationContext.getBean("mongoTemplate");
}
public User find(String username) {
return mongoOperations.findOne(Query.query(Criteria.where("username").is(username)), User.class);
}
}
And my SecurityConfig class:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserService userService;
#Autowired
public void configAuthBuilder(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userService); //THIS DOES NOT WORK
builder.inMemoryAuthentication().withUser("username").password("password").roles("USER");
}
}
The line I commented says:
The inferred type UserService is not a valid substitute for the bounded parameter <T extends UserDetailsService>.
How can I fix it so I can retrieve the users from my own database?
Service Layer
You have to create a separate service implementing org.springframework.security.core.userdetails.UserDetailsService and inject it inside the AuthenticationManagerBuilder.
#Component
public class SecUserDetailsService implements UserDetailsService{
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
/*Here add user data layer fetching from the MongoDB.
I have used userRepository*/
User user = userRepository.findByUsername(username);
if(user == null){
throw new UsernameNotFoundException(username);
}else{
UserDetails details = new SecUserDetails(user);
return details;
}
}
}
Model
UserDetails Should be also implemented. This is the POJO which will keep the user authenticated details by the Spring. You may include your Entity data object wrapped inside it, as I have done.
public class SecUserDetails implements UserDetails {
private User user;
public SecUserDetails(User user) {
this.user = user;
}
......
......
......
}
Security Config
Autowire the service that we created before and set it inside the AuthenticationManagerBuilder
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
SecUserDetailsService userDetailsService ;
#Autowired
public void configAuthBuilder(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService);
}
}
Create your own authentication provider providing a class that extends the UserDetailservice.
Ensure content scanning is enable in your spring context xml file.
<authentication-provider user-service-ref="userModelService">
<password-encoder hash="sha" />
</authentication-provider>
#Service
public class UserModelService implements UserDetailsService
{
#Autowired
private UserModelRepositoryImpl repository;
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
UserModel user = repository.findByUsername(username);
if( user == null )
throw new UsernameNotFoundException( "Name not found!" );
List<SimpleGrantedAuthority> authorities = Arrays.asList(new SimpleGrantedAuthority( user.getRole()));
return new User(user.getUsername(), user.getSHA1Password(), authorities );
}
public void saveUserDetails(UserModel userModel)
{
repository.save(userModel);
}
}
This class will enable spring query mongo for the username and password required for authentication. Next create the user model class.
public class UserModel
{
private String id;
#Indexed(unique=true)
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
Create the user implementation class that extends the DAO.
#Service
public class UserModelService implements UserDetailsService
{
#Autowired
private UserModelRepositoryImpl repository;
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
UserModel user = repository.findByUsername(username);
if( user == null )
throw new UsernameNotFoundException( "Oops!" );
List<SimpleGrantedAuthority> authorities = Arrays.asList(new SimpleGrantedAuthority( user.getRole()));
return new User(user.getUsername(), user.getSHA1Password(), authorities );
}
Finally configure mongo and you're done.