I am getting an error when I try to create the authentication of a user, I have been able to discover that the error occurs because that user is not loaded in memory and this is a problem, since the users must leave the database, but I will not to dump all users in memory....
I took the tutorial from here: https://windoctor7.github.io/spring-jwt.html
The code is the following:
@Configuration
@EnableWebSecurity
@ComponentScan(basePackages = "es.....service")
public class ServiciosConfig extends WebSecurityConfigurerAdapter {
@Bean
public LoginFilter createLogin() throws Exception {
return new LoginFilter("/login", authenticationManager());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/login").permitAll() //permitimos el acceso a /login a cualquiera
.anyRequest().authenticated() //cualquier otra peticion requiere autenticacion
.and()
// Las peticiones /login pasaran previamente por este filtro
.addFilterBefore(createLogin(), UsernamePasswordAuthenticationFilter.class)
// Las demás peticiones pasarán por este filtro para validar el token
.addFilterBefore(new JwtFilter(),
UsernamePasswordAuthenticationFilter.class);
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Creamos una cuenta de usuario por default
auth.inMemoryAuthentication()
.withUser("edu")
.password("123")
.roles("ADMIN");
}
Here 3 things are done:
The requests are captured and if it is login, the user is verified in the DB, if it is correct, the token is created. (This is the problem)
If it is not login, check the token. (This works)
Save users in memory (This is the problem)
public class LoginFilter extends AbstractAuthenticationProcessingFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(LoginFilter.class);
@Autowired
private RolesUserRepository rolRepository;
public LoginFilter(String url, AuthenticationManager authManager) {
super(new AntPathRequestMatcher(url));
setAuthenticationManager(authManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException, IOException, ServletException {
// obtenemos el body de la peticion que asumimos viene en formato JSON
InputStream body = req.getInputStream();
// Realizamos un mapeo a nuestra clase User para tener ahi los datos
User user = new ObjectMapper().readValue(body, User.class);
// Finalmente autenticamos
LOGGER.info("Buscando al usuario: " + user.getUser() + " en la BD.");
RolesUser rol = this.rolRepository.findByUser(user.getUser());
if (rol.getRol() != null) {
LOGGER.info("El usuario: " + user.getUser() + " es correcto.");
List<GrantedAuthority> grantedAuths = AuthorityUtils.commaSeparatedStringToAuthorityList(rol.getRol());
return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(user.getUser(),
user.getPwd(), grantedAuths));
} else {
throw new AuthenticationException("Credenciales inválidas.") {
};
}
}
@Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
Authentication auth) throws IOException, ServletException {
// Si la autenticacion fue exitosa, agregamos el token a la respuesta
JwtUtil.addAuthentication(res, auth.getName());
}
}
class User {
private String user;
private String pwd;
public User() {}
public User(String user, String pwd) {
this.user = user;
this.pwd = pwd;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
As you can see, to authenticate the user I look for it in the database, if it is correct I do a
return getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken(user.getUser(), user.getPwd(), grantedAuths));
And now the problem: If the user exists in the DB but is not in the @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Creamos una cuenta de usuario por default
auth.inMemoryAuthentication()
.withUser("edu")
.password("123")
.roles("ADMIN");
}
It pops on the return getAuthenticationManager line....I get an error from the org.springframework.security.authentication.BadCredentialsException class AbstractAuthenticationProcessingFilter
: Bad credentials
But if it's in that line, it works perfectly, the problem is that I can't have the users loaded in memory, so I need to be able to "remove the from the code auth.inMemoryAuthentication()
and make it work, I "think" that maybe it must be because of the class used AbstractAuthenticationProcessingFilter
but it is the one that I always find on the web pages. Thanks!
-------------------------------------------------
After some modifications the code stays like this: But it keeps crashing:
In the WebSecurityConfigurerAdapter
@Configuration
@EnableWebSecurity
@ComponentScan(basePackages = "es....service")
public class ServiciosConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserServiceImpl userDetailService;
@Bean
public LoginFilter createLogin() throws Exception {
return new LoginFilter("/login", authenticationManager());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/login").permitAll() // permitimos el acceso a /login a
// cualquiera
.anyRequest().authenticated() // cualquier otra peticion requiere autenticacion
.and()
// Las peticiones /login pasaran previamente por este filtro
.addFilterBefore(createLogin(), UsernamePasswordAuthenticationFilter.class)
// Las demás peticiones pasarán por este filtro para validar el token
.addFilterBefore(new JwtFilter(), UsernamePasswordAuthenticationFilter.class);
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new StandardPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());
}
In the WebSecurityConfigurerAdapter, I make a "PassEncoder" and remove the cache which is what I don't want to have, because I want to get the users out of the database, so instead I create a new implementation:
@Service
public class UserServiceImpl implements UserDetailsService {
private static final Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);
@Autowired
private RolesUserRepository repository;
public void UserService(RolesUserRepository repository) {
this.repository = repository;
}
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
RolesUser rolUser = repository.findByUser(userName);
if(rolUser.getRol() != null) {
LOGGER.info("El usuario: " + userName + " es: " + rolUser.getRol());
List<GrantedAuthority> grantedAuths = AuthorityUtils.commaSeparatedStringToAuthorityList(rolUser.getRol());
return new MyUserPrincipal(grantedAuths,"$2a$10$slYQmyNdGzTn7ZLBXBChFOC9f6kFjAqPhccnP6DxlWXx2lPk1C3G6",userName);
}else {
throw new AuthenticationException("Las credenciales son incorrectas.") {
};
}
}
This is the new implementation, it checks if that user exists in the database (its password does not matter) and if it has a role, it exists and I create the userDetail class so that it can be validated.
public class MyUserPrincipal implements UserDetails {
/**
*
*/
private static final long serialVersionUID = 1L;
private List<GrantedAuthority> grantedAuths;
private String password;
private String userName;
public MyUserPrincipal(List<GrantedAuthority> grantedAuths, String password, String userName) {
this.grantedAuths = grantedAuths;
this.password = password;
this.userName = userName;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.grantedAuths;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.userName;
}
@Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return true;
}
}
Here I had to change the returns to true because they were giving me different errors.
Finally, in the class where it once had the check, it now remains empty: public class LoginFilter extends AbstractAuthenticationProcessingFilter {
public LoginFilter(String url, AuthenticationManager authManager) {
super(new AntPathRequestMatcher(url));
setAuthenticationManager(authManager);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException, IOException, ServletException {
// obtenemos el body de la peticion que asumimos viene en formato JSON
InputStream body = req.getInputStream();
// Realizamos un mapeo a nuestra clase User para tener ahi los datos
User user = new ObjectMapper().readValue(body, User.class);
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
user.getUser(),
user.getPwd(),
Collections.emptyList()
)
);
}
@Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
Authentication auth) throws IOException, ServletException {
// Si la autenticacion fue exitosa, agregamos el token a la respuesta
JwtUtil.addAuthentication(res, auth.getName());
}
}
class User {
private String user;
private String pwd;
public User() {
}
public User(String user, String pwd) {
this.user = user;
this.pwd = pwd;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
I have been guided for this part of: https://www.javainuse.com/spring/boot-jwt-mysql
The error I get is the same as before "BAD CREDENTIALS" in the class
AbstractUserDetailsAuthenticationProvider, Línea 171.
catch (AuthenticationException exception) {
if (cacheWasUsed) {
// There was a problem, so try again after checking
// we're using latest data (i.e. not from the cache)
cacheWasUsed = false;
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
In debugging, the authentication variable has the following:
org.springframework.security.authentication.UsernamePasswordAuthenticationToken@ffce9fa1: Principal: jose; Credentials: [PROTECTED]; Authenticated: true; Details: null; Not granted any authorities
Reading your post I realize that what you need is to be able to look for the logged user in the database, since you don't want it to be in memory, therefore what occurs to me is that you change in the configure method
is changed by
You will have to create the bean for the pwd enconder.
Then the LoginFilter class must be changed so that it implements org.springframework.security.core.userdetails.UserDetailsService This already implies a refactor so that the authentication is on the side of the ServiciosConfig class if you want to guide yourself with an example you can go to: [ https://github.com/OmarHHM/rest/] Greetings, I hope it works for you.