Spring security: get password in UserDetailsServiceMethod

spoilerd do

In order to get my account I have a external spring application that I need to login at. Why I need it is not important but in order to do a /login call on the API I need to get the password in the UserDetailsServiceMethod. Here is my security setup:

//https://auth0.com/blog/implementing-jwt-authentication-on-spring-boot/
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
    private UserDetailsService userDetailsService;
    private BCryptPasswordEncoder bCryptPasswordEncoder;

//Constructor gets authLogic for external authentication
@Autowired
public WebSecurity(@Qualifier("authLogic") UserDetailsService userDetailsService){
    this.userDetailsService = userDetailsService;
    this.bCryptPasswordEncoder = new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.cors().and().csrf().disable()
            .authorizeRequests()
            .antMatchers("/v2/api-docs", "/configuration/ui", "/swagger-resources", "/configuration/security", "/swagger-ui.html", "/webjars/**", "/swagger-resources/configuration/ui", "/swagger-resources/configuration/security").permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilter(new JwtAuthenticationFilter(authenticationManager()))
            .addFilter(new JwtAuthorizationFilter(authenticationManager()))
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}

@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    final CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(Arrays.asList(BANK_API, INVENTORY_API, MARKET_API)); //TODO: is dit correct??
    configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH"));
    configuration.setAllowCredentials(true);
    configuration.setAllowedHeaders(Arrays.asList("*"));
    configuration.setExposedHeaders(Arrays.asList("X-Auth-Token","Authorization","Access-Control-Allow-Origin","Access-Control-Allow-Credentials"));

    final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}
}

My UserDetailsServiceMethod implementation:

@Service
public class AuthLogic implements UserDetailsService {
    private HttpServletRequest request;
    private IAccountRepository accountRepository;
    private RestCallLogic restCall;

    @Autowired
    public AuthLogic(HttpServletRequest request, IAccountRepository accountRepository, RestCallLogic restCall){
        this.request = request;
        this.accountRepository = accountRepository;
        this.restCall = restCall;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //get password
        //make restcall to external login
    }
}

Is there a way I can get the password while using the spring security implementation. Because I could easily make my own class and do the login from there but it would be nice to use Spring security for it. Also the login returns a token that I can reform to a User. Maybe i'm just overthinking...

In order to make a API call i needed to write a custom AuthenticationProvider:

@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        UserDetails principal = new User(username, password, new ArrayList<>());

        return new UsernamePasswordAuthenticationToken(principal, password, new ArrayList<>());
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}
spoilerd do

After a week I finally got what I wanted. So i made a custom authentication provider that will make a REST call to my authentication API. If the username and password I gave are correct I'll get a JWT-token back that contains a username, roles and a ID. after that I just call a custom Authentication service that checks if the user id already exists in its database. If that isn't the case than I'll create a new user with the given id from the JWT-token.

Here is my custom authentication provider:

public class JwtAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    //custom authentication service
    private AuthLogic userDetailsImpl;

    public JwtAuthenticationProvider(AuthLogic userDetailsImpl) {
        this.userDetailsImpl = userDetailsImpl;
    }

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        //JWTUser is a custom class that extends the UserDetails class from spring
        JwtUser user = (JwtUser) userDetails;

        //call the custom auth service to check if the user exists in the database
        userDetailsImpl.loadUserByUsername(user.getUserID(), user.getUsername());
    }

    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        //get the token from a external authentication API
        String token = retrieveAccountData(new LoginWrapper(username, authentication.getCredentials().toString()));

        Claims claims = Jwts.parser()
                .setSigningKey(JWTKEY)
                .parseClaimsJws(token)
                .getBody();

        List<String> scopes = (List<String>) claims.get("scopes");
        int UserId = (int) claims.get("userID");
        List<GrantedAuthority> authorities = scopes.stream()
                .map(authority -> new SimpleGrantedAuthority(authority))
                .collect(Collectors.toList());

        //return the User
        return new JwtUser(UserId, username, authentication.getCredentials().toString(), authorities);
    }

    private String retrieveAccountData(LoginWrapper loginWrapper){
        URI uri = UriComponentsBuilder.fromUriString(BANK_LOGIN).build().toUri();
        Gson gson = new GsonBuilder().create();

        RequestEntity<String> request = RequestEntity
                .post(uri)
                .accept(MediaType.APPLICATION_JSON)
                .body(gson.toJson(loginWrapper));

        //post call
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> response = restTemplate.exchange(request, String.class);

        //check if status code is correct
        if(response.getStatusCode() != HttpStatus.OK) {
            throw new UsernameNotFoundException(loginWrapper.getUsername());
        }

        //convert to LoginWrapper
        return gson.fromJson(response.getBody(), TokenWrapper.class).getToken();
    }
}

And here is my custom authentication service:

@Service
public class AuthLogic {
    private IAccountRepository accountRepository;

    @Autowired
    public AuthLogic(IAccountRepository context) {
        this.accountRepository = context;
    }
trough with the jwt token)
    public UserDetails loadUserByUsername(int userId, String username) throws UsernameNotFoundException {
        Optional<Account> foundAccount = accountRepository.findById(userId);

        Account account;
        //check if user has logged in to our inventory API before, if not create new account
        if (!foundAccount.isPresent()) {
            account = accountRepository.save(new Account(userId, username));
        } else {
            account = foundAccount.get();
        }

        return new JwtUserPrincipal(account);
    }
}

In order to call the service from the provider you need to configure your WebSecurityConfigurerAdapter properly:

@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
    private JwtAuthenticationProvider authenticationProvider;

    @Autowired
    public WebSecurity(@Qualifier("authLogic") AuthLogic userDetailsImpl) {
        this.authenticationProvider = new JwtAuthenticationProvider(userDetailsImpl);
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider);
    }
}

I hope this answer helps.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Spring Security : Encrypt password

Spring Security - Get clear password on authentication and re-encrypt them

Spring security_Password Encryption

Username Password Authentication in Spring Security

Spring Security 5.2 Password Flow

Recover username and password with spring security

Password is not getting encoded in spring security

How to get the user entered values of username and password in a spring security authenticated login

How to use Spring security without password encoding?

Remove "Using default security password" on Spring Boot

Spring Security BASIC auth - matching password hash

Spring Security Basic Auth Password Rotation Issue

BCrypt: Empty Encoded Password with Spring Security

Read username/password from a file in Spring Security

Password encrypt and decrypt using Spring-security

Spring Security force logout when password change

spring security encode password with bcrypt algorithm

Is there a way to configure password encoder for default spring security password

why spring security gives empty password to password encoder?

Spring Security - Conceptual question around generated security password

Password encoding and decoding using Spring Security, Spring Boot and MongoDB

Spring Boot/ Spring Security, login form, password checking

Spring Security get custom headers

Password Security

Spring Security One-Time-Password (OTP) based Login

Spring Security - Encoded Password Does Not Look Like BCrypt

How to automatically login as a user using Spring Security without knowing their password?

Spring security authentication with 3 fields instead of just username and password

oAuth2 client with password grant in Spring Security