Reputation: 8935
I am using SpringBoot 2.3.1.RELEASE with Java 14.
I have Spring Security working correctly, i.e. it can receive a username & password and return a jwt token. Various api calls are validated successfully against the token.
Here is my WebSecurityConfigurerAdapter
SecurityConfig.java
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("datasource1")
private DataSource dataSource;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery("SELECT username, password, (NOT disabled) as enabled FROM members "+
"WHERE username = ?")
.authoritiesByUsernameQuery("SELECT m.username, t.name as authority FROM members m " +
"JOIN administrator a ON a.member_id = m.member_id " +
"JOIN admin_type t ON t.admin_type_id = a.admin_type_id "+
"WHERE m.username = ?");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// set up the jwt auth
http.cors().disable();
http.csrf().disable().authorizeRequests().antMatchers("/authenticate").permitAll()//.anyRequest().authenticated()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.and().exceptionHandling()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // don't manage sessions, using jwt
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
// define the role mappings
http.authorizeRequests()
.antMatchers("/admin").hasAuthority("approver admin")
.antMatchers("/approvals").hasAuthority("approver admin")
//.antMatchers("/rest/*").hasAuthority("approver admin")
.antMatchers("/hello").permitAll();
// INSERT INTO admin_type (admin_type_id, name, description) VALUES ((SELECT MAX(admin_type_id) +1 FROM admin_type), 'approver admin', 'Able to alter approval requests');
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new NexctPasswordEncoder();
}
}
I also have a RESTful Resource. This receives an AuthenticationRequest
containing a username and raw password.
ApprovalsResource.java
@RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword())
);
} catch (BadCredentialsException e) {
logger.info("Incorrect username or password for "+authenticationRequest.getUsername());
throw new Exception("Incorrect username or password", e);
}
final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final String jwt = jwtTokenUtil.generateToken(userDetails);
final String username = jwtTokenUtil.extractUserName(jwt);
logger.info("User just logged in: "+username);
return ResponseEntity.ok(new AuthenticationResponse(jwt));
}
NexctPasswordEncoder.java
public class NexctPasswordEncoder implements PasswordEncoder {
Logger logger = LoggerFactory.getLogger(NexctPasswordEncoder.class);
@Override
public String encode(CharSequence rawPassword) {
return encodeString(rawPassword.toString());
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
String encoded = encodeString(rawPassword.toString());
boolean match = encoded.equals(encodedPassword);
return match;
}
private String encodeString(String s) {
String encryptedPassword = null;
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-384");
byte[] pt = s.getBytes();
byte[] out = messageDigest.digest(pt);
encryptedPassword = HexConvert.ByteToHexString(out);
} catch (NoSuchAlgorithmException e) {
logger.error("Error trying to encode password");
}
return encryptedPassword;
}
}
This all works perfectly with the NexctPasswordEncoder
when a raw (unencrypted) password is received. The NexctPasswordEncoder
encrypts the password to be compared it to the encrypted password in the database.
Problem
I also need to cater for the case when I receive the encrypted password rather then the raw password.
Solution
I need to get the above to work with two different PasswordEncoder
's.
Ideally, in the ApprovalsResource
where the request with the username and password is received, and the following is called:
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword())
I would like to set the relevant PasswordEncoder
(one that encrypts the password and one that does not).
Question
How do I swap the PasswordEncoder
depending on a parameter from the request?
Upvotes: 1
Views: 2145
Reputation: 8205
Actually solution is very simple.
You can't use DelegatePasswordEncoder. Because passwords need to be stored with prefix. And when they stored with prefix, spring security will use the password encoder for that prefix.
You can do one of the following:
boolean match = encoded.equals(encodedPassword) || rawPassword.equals(encodedPassword);
public class NexctPasswordEncoder implements PasswordEncoder {
Logger logger = LoggerFactory.getLogger(NexctPasswordEncoder.class);
@Override
public String encode(CharSequence rawPassword) {
return encodeString(rawPassword.toString());
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
String encoded = encodeString(rawPassword.toString());
boolean match = encoded.equals(encodedPassword) || rawPassword.equals(encodedPassword);
return match;
}
private String encodeString(String s) {
String encryptedPassword = null;
try {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-384");
byte[] pt = s.getBytes();
byte[] out = messageDigest.digest(pt);
encryptedPassword = HexConvert.ByteToHexString(out);
} catch (NoSuchAlgorithmException e) {
logger.error("Error trying to encode password");
}
return encryptedPassword;
}
}
Upvotes: 1
Reputation: 695
Checkout the DelegatingPasswordEncoder: https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/crypto/password/DelegatingPasswordEncoder.html
It allows to determine which other PasswordEncoder to use based on a prefix in the password value.
Upvotes: 1