I am new to Spring boot and I am trying to configure the security for my api. I am using PasswordEncoding:
public static String encodePassword(String plainPassword){
BCryptPasswordEncoder bCryptPasswordEncoder=new BCryptPasswordEncoder();
return bCryptPasswordEncoder.encode(plainPassword);
}
In the SecurityConfig class I got the following method:
#Bean
public PasswordEncoder getPasswordEncoder(){
return new BCryptPasswordEncoder();
}
But each time given the same input the output is always different, can someone explain to me the reason behind this and how I can possibly fix this?
This is by design, there's nothing for you to "fix". The reason is because the BCrypt algorithm includes a salt, which will be different every time you call it. What this means is that if you're trying to encode a plain-text password to a hash and compare it to another hash, it's not going to match. You can, however, use the method, matches, in BCryptPasswordEncoder to compare.
Here's a test that demonstrates this
#Test
public void encodeAndMatch() {
BCryptPasswordEncoder bc = new BCryptPasswordEncoder();
String p1 = bc.encode("password");
String p2 = bc.encode("password");
String p3 = bc.encode("password");
assertNotEquals(p1, p2);
assertNotEquals(p1, p3);
assertNotEquals(p2, p3);
assertTrue(bc.matches("password", p1));
assertTrue(bc.matches("password", p2));
assertTrue(bc.matches("password", p3));
}
Here you can see that the same password generated three distinct hashes, but the encoder can still compare the original plain-text password to each of them and match.
Related
Spring Boot Application:
a #RestController receives the following payload:
{
"cartoon": "The Little Mermaid",
"characterNames": ["Ariel", "Prince Eric", "Sebastian", "Flounder"]
}
I need to process it in the following way:
Get the unique Id for each character name: make an HTTP call to "cartoon-characters" microservice, that returns ids by names
Transform data received by the controller:
replace character names with appropriate ids that were received on the previous step from "cartoon-characters" microservice.
{
"cartoon": "The Little Mermaid",
"characterIds": [1, 2, 3, 4]
}
Send an HTTP POST request to "cartoon-db" microservice with transformed data.
Map the response from "cartoon-db" to the internal representation that is the controller return value.
The problem that I got:
I need to implement all these steps using the paradigm of Reactive Programming (non-blocking\async processing) with Spring WebFlux (Mono|Flux) and Spring Reactive WebClient - but I have zero experience with that stack, trying to read about it as much as I can, plus googling a lot but still, have a bunch of unanswered questions, for example:
Q1. I have already configured reactive webClient that sends a request to "cartoon-characters" microservice:
public Mono<Integer> getCartoonCharacterIdbyName(String characterName) {
return WebClient.builder().baseUrl("http://cartoon-characters").build()
.get()
.uri("/character/{characterName}", characterName)
.retrieve()
.bodyToMono(Integer.class);
}
As you may see, I have got a list of cartoon character names and for each of them I need to call getCartoonCharacterIdbyName(String name) method, I am not sure that the right option to call it in series, believe the right option: parallel execution.
Wrote the following method:
public List<Integer> getCartoonCharacterIds(List<String> names) {
Flux<Integer> flux = Flux.fromStream(names.stream())
.flatMap(this::getCartoonCharacterIdbyName);
return StreamSupport.stream(flux.toIterable().spliterator(), false)
.collect(Collectors.toList());
}
but I have doubts, that this code does parallel WebClient execution and also, code calls flux.toIterable() that block the thread, so with this implementation I lost non-blocking mechanism.
Are my assumptions correct?
How do I need to rewrite it to having parallelism and non-blocking?
Q2.
Is it technically possible to transform input data received by the controller (I mean replace names with ids) in reactive style: when we operate with Flux<Integer> characterIds, but not with the List<Integer> of characterIds?
Q3. Is it potentially possible to get not just transformed Data object, but Mono<> after step 2 that can be consumed by another WebClient in Step 3?
Actually it's a good question since understanding the WebFlux, or project reactor framework, when it comes to chaining micro-services requires a couple of steps.
The first is to realize that a WebClient should take a publisher in and return a publisher. Extrapolate this to 4 different method signatures to help with thinking.
Mono -> Mono
Flux -> Flux
Mono -> Flux
Flux -> Mono
For sure, in all cases, it is just Publisher->Publisher, but leave that until you understand things better. The first two are obvious, and you just use .map(...) to handle objects in the flow, but you need to learn how to handle the second two. As commented above, going from Flux->Mono could be done with .collectList(), or also with .reduce(...). Going from Mono->Flux seems to generally be done with .flatMapMany or .flatMapIterable or some variation of that. There are probably other techniques. You should never use .block() in any WebFlux code, and generally you will get a runtime error if you try to do so.
In your example you want to go to
(Mono->Flux)->(Flux->Flux)->(Flux->Flux)
As you said, you want
Mono->Flux->Flux
The second part is to understand about chaining Flows. You could do
p3(p2(p1(object)));
Which would chain p1->p2->p3, but I always found it more understandable to make a "Service Layer" instead.
o2 = p1(object);
o3 = p2(o2);
result = p3(o3);
This code is just much easier to read and maintain and, with some maturity, you come to understand the worth of that statement.
The only problem I had with your example was doing a Flux<String> with WebClient as a #RequestBody. Doesn't work. See WebClient bodyToFlux(String.class) for string list doesn't separate individual values. Other than that, it's a pretty straightforward application. You'll find when you debug it that it gets to the .subscribe(System.out::println) line before it gets to the Flux<Integer> ids = mapNamesToIds(fn) line. This is because the Flow is setup before it is executed. Takes a while to understand this but it is the point of the project reactor framework.
#SpringBootApplication
#RestController
#RequestMapping("/demo")
public class DemoApplication implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
Map<Integer, CartoonCharacter> characters;
#Override
public void run(ApplicationArguments args) throws Exception {
String[] names = new String[] {"Ariel", "Prince Eric", "Sebastian", "Flounder"};
characters = Arrays.asList( new CartoonCharacter[] {
new CartoonCharacter(names[0].hashCode(), names[0], "Mermaid"),
new CartoonCharacter(names[1].hashCode(), names[1], "Human"),
new CartoonCharacter(names[2].hashCode(), names[2], "Crustacean"),
new CartoonCharacter(names[3].hashCode(), names[3], "Fish")}
)
.stream().collect(Collectors.toMap(CartoonCharacter::getId, Function.identity()));
// TODO Auto-generated method stub
CartoonRequest cr = CartoonRequest.builder()
.cartoon("The Little Mermaid")
.characterNames(Arrays.asList(names))
.build();
thisLocalClient
.post()
.uri("cartoonDetails")
.body(Mono.just(cr), CartoonRequest.class)
.retrieve()
.bodyToFlux(CartoonCharacter.class)
.subscribe(System.out::println);
}
#Bean
WebClient localClient() {
return WebClient.create("http://localhost:8080/demo/");
}
#Autowired
WebClient thisLocalClient;
#PostMapping("cartoonDetails")
Flux<CartoonCharacter> getDetails(#RequestBody Mono<CartoonRequest> cartoonRequest) {
Flux<StringWrapper> fn = cartoonRequest.flatMapIterable(cr->cr.getCharacterNames().stream().map(StringWrapper::new).collect(Collectors.toList()));
Flux<Integer> ids = mapNamesToIds(fn);
Flux<CartoonCharacter> details = mapIdsToDetails(ids);
return details;
}
// Service Layer Methods
private Flux<Integer> mapNamesToIds(Flux<StringWrapper> names) {
return thisLocalClient
.post()
.uri("findIds")
.body(names, StringWrapper.class)
.retrieve()
.bodyToFlux(Integer.class);
}
private Flux<CartoonCharacter> mapIdsToDetails(Flux<Integer> ids) {
return thisLocalClient
.post()
.uri("findDetails")
.body(ids, Integer.class)
.retrieve()
.bodyToFlux(CartoonCharacter.class);
}
// Services
#PostMapping("findIds")
Flux<Integer> getIds(#RequestBody Flux<StringWrapper> names) {
return names.map(name->name.getString().hashCode());
}
#PostMapping("findDetails")
Flux<CartoonCharacter> getDetails(#RequestBody Flux<Integer> ids) {
return ids.map(characters::get);
}
}
Also:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class StringWrapper {
private String string;
}
#Data
#Builder
public class CartoonRequest {
private String cartoon;
private List<String> characterNames;
}
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class CartoonCharacter {
Integer id;
String name;
String species;
}
I have a use case in my application that should prevent the user from choosing one of their last 3 passwords while resetting their password. I'm using Angular for the front end and Spring Boot for the back end . In my scenario, the user passwords are stored as bcrypt hash.
How can I compare the password entered by the user with the last 3 stored bcrypt passwords?
When I run the following code snipped example,
BCryptPasswordEncoder b = new BCryptPasswordEncoder();
for(int i =0;i<10;i++) {
System.out.println(b.encode("passw0rd"));
}
it generates the following bcrypt hashes. each hash is different which is reasonable because when I check the org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder, I can see the salt generated is random value.
$2a$10$tztZsPFZ.T.82Gl/VIuMt.RDjayTwuMLAkRkO9SB.rd92vHWKZmRm
$2a$10$yTHyWDmcCBq3OSPOxjj4TuW9qXYE31CU.fFlWxppii9AizL0lKMzO
$2a$10$Z6aVwg.FNq/2I4zmDjDOceT9ha0Ur/UKsCfdADLvNHiZpR7Sz53fC
$2a$10$yKDVeOUvfTQuTnCHGJp.LeURFcXK6JcHB6lrSgoX1pRjxXDoc8up.
$2a$10$ZuAL06GS7shHz.U/ywb2iuhv2Spubl7Xo4NZ7QOYw3cHWK7/7ZKcC
$2a$10$4T37YehBTmPWuN9j.ga2XeF9GHy6EWDhQS5Uc9bHvJTK8.xIm1coS
$2a$10$o/zxjGkArT7YdDkrk5Qer.oJbZAYpJW39iWAWFqbOhpTf3FmyfWRC
$2a$10$eo7yuuE2f7XqJL8Wjyz.F.xj78ltWuMS1P0O/I6X7iNPwdsWMVzu6
$2a$10$3ErH2GtZpYJGg1BhfgcO/uOt/L2wYg4RoO8.fNRam458WWdymdQLW
$2a$10$IksOJvL/a0ebl4R2/nbMQ.XmjNARIzNo8.aLXiTFs1Pxd06SsnOWa
Spring security configuration.
#Configuration
#Import(SecurityProblemSupport.class)
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#PostConstruct
public void init() {
try {
authenticationManagerBuilder
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
} catch (Exception e) {
throw new BeanInitializationException("Security configuration failed", e);
}
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
you can use matches method in BCryptPasswordEncoder, something like this:
b.matches("passw0rd", hash)
Actually I found my answer .
I realized that I can use matches function in the class org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder.
System.out.println(b.matches("passw0rd", "$2a$10$tztZsPFZ.T.82Gl/VIuMt.RDjayTwuMLAkRkO9SB.rd92vHWKZmRm"));
Spring Security just reads the salt from previously generated hash and rehashes the input password again with same salt. And it compares both final hashes and obviously it will be same.
Example:
Password: test
Hash: $2a$10$nCgoWdqJwQs9prt7X5a/2eWLn88I8pon6iNat90u4rq4mHqtoPGQy
Hash has 3 segments each separated by $ symbol. 2a is version of the Bcrypt, 10 is the total rounds and nCgoWdqJwQs9prt7X5a/2e is the salt.
So spring security takes the password test and salt nCgoWdqJwQs9prt7X5a/2e and runs the hashing method. Obviously it generates the same hash as the password and salt matches.
Try the below :
BCryptPasswordEncoder bc = new BCryptPasswordEncoder();
boolean passChecker = bc.matches("Normal Password Here", "Hashed Password Here");
I had been facing a scenario where I had to verify my old password which is stored as bcrypted into DB in order to Change the password.
then I did it this way.
BCryptPasswordEncoder b = new BCryptPasswordEncoder();
if(b.matches(oldNormalPassword, #Password)){ // code ...}
Why does the strength of the BCryptPasswordEncoder affect the startup time of the server? There are no hashes generated at startup, so I'm wondering why this does have any effect on the startup.
Of course, I understand that checking whether a password matches takes time, but at start up is strange.
Code looks like this:
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(17); // Affects startup time tremendously
}
#Autowired
BCryptPasswordEncoder bcryptEncoder;
#Autowired
CustomUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(bcryptEncoder);
}
(Depending on your configuration)
Have a look at the spring DaoAuthenticationProvider
The following method is called at startup:
private void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.userNotFoundEncodedPassword = passwordEncoder.encodePassword(
USER_NOT_FOUND_PASSWORD, null);
this.passwordEncoder = passwordEncoder;
}
This was introduced so the server has an encoded password to verify the password against from a username attempting to authenticate doesn't exist.
/**
* The plaintext password used to perform
* {#link PasswordEncoder#isPasswordValid(String, String, Object)} on when the user is
* not found to avoid SEC-2056.
*/
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
Refer to:
https://github.com/spring-projects/spring-security/issues/2280
https://jira.spring.io/browse/SEC-2056
Note: If you set the strength to 17 and your server is taking an extra 5minutes to start, it will take your sever approximately 5minutes to verify each users password when they authenticate.
This issue is now resolved, so upgrade your Spring Boot/Spring Security if necessary.
Spring Security calls PasswordEncoder.matches() regardless of whether the user was found, so that hackers can't detect if the user existed or not by comparing response times. To create the dummy encrypted password or hash for non-existing users, Spring Security calls PasswordEncoder.encode("userNotFoundEncodedPassword") once and reuses the result. This call was previously made during startup but is now called lazily when first needed.
The constructor of BCryptPasswordEncoder is not doing anything on startup depending on the password strength :
public BCryptPasswordEncoder(int strength) {
this(strength, null);
}
public BCryptPasswordEncoder(int strength, SecureRandom random) {
if (strength != -1 && (strength < BCrypt.MIN_LOG_ROUNDS || strength > BCrypt.MAX_LOG_ROUNDS)) {
throw new IllegalArgumentException("Bad strength");
}
this.strength = strength;
this.random = random;
}
Having seen this, I dont think that only changing the strength parameter can increase startup time as described.
But when you actually use the encrypter, 'strength' will for sure impact the performance. So may be you are encrypting many passwords somewhere at startup ?
Can anyone tell why this didn't work? The code works great when I run it from my unit tests. Security gets setup perfectly and our service works great just like I expect.
However, when I deployed it to our application server (weblogic), my service fails every time because my tokens are not getting setup. I got it working by setting up the tokens every time my send(final ServiceAPInvoice invoice) method gets called.
My question is why does the tokens not get setup by my constructor when this is deployed in our Weblogic environment? What causes this issue? OAuthSecurityContextHolder is a static class. Is that playing into my issue? Will I still run into issues if I setup the tokens each time my send method is called? I haven't noticed any issues yet but have not done any load testing
I am using Spring's OAuthRestTemplate (1.0) and I have non-expiring tokens that I need to setup.
Here is where the magic happens. I had to rename the code slightly to make it generic so hopefully I don't have any typos:
public class ServiceRestTemplate {
private final OAuthRestTemplate serviceOAuthRestTemplate;
private final String apUri;
private final String arUri;
private final String tokenValue;
private final String tokenSecret;
public ServiceRestTemplate(final OAuthRestTemplate serviceOAuthRestTemplate,
final String apUri,
final String arUri,
final String tokenValue,
final String tokenSecret) {
this.serviceOAuthRestTemplate = serviceOAuthRestTemplate;
this.apUri = apUri;
this.arUri = arUri;
this.tokenSecret = tokenSecret;
this.tokenValue = tokenValue;
setContext(tokenValue, tokenSecret); // I expected this to be enough to setup my tokens 1 time
}
private void setContext(final String tokenValue, final String tokenSecret) {
final OAuthConsumerToken accessToken = new OAuthConsumerToken();
accessToken.setAccessToken(true);
accessToken.setResourceId(serviceOAuthRestTemplate.getResource().getId());
accessToken.setValue(tokenValue);
accessToken.setSecret(tokenSecret);
final OAuthSecurityContextImpl securityContext = new OAuthSecurityContextImpl();
if (securityContext.getAccessTokens() == null) {
securityContext.setAccessTokens(new HashMap<String, OAuthConsumerToken>());
}
if (!securityContext.getAccessTokens().containsKey(accessToken.getResourceId())) {
securityContext.getAccessTokens().put(accessToken.getResourceId(), accessToken);
}
OAuthSecurityContextHolder.setContext(securityContext);
}
#Override
public ServiceWebResponse send(final ServiceAPInvoice invoice) {
setContext(this.tokenValue, this.tokenSecret); // This line of code is the workaround to fixed my issue.
final ServiceWebResponse serviceResponse = serviceOAuthRestTemplate.postForObject(apUri,
invoice,
ServiceWebResponse.class);
return serviceResponse;
}
}
I am experiencing problems when configurating my Jersey Client with the ApacheConnector. It seems to ignore all request headers that I define in a WriterInterceptor. I can tell that the WriterInterceptor is called when I set a break point within WriterInterceptor#aroundWriteTo(WriterInterceptorContext). Contrary to that, I can observe that the modification of an InputStream is preserved.
Here is a runnable example demonstrating my problem:
public class ApacheConnectorProblemDemonstration extends JerseyTest {
private static final Logger LOGGER = Logger.getLogger(JerseyTest.class.getName());
private static final String QUESTION = "baz", ANSWER = "qux";
private static final String REQUEST_HEADER_NAME_CLIENT = "foo-cl", REQUEST_HEADER_VALUE_CLIENT = "bar-cl";
private static final String REQUEST_HEADER_NAME_INTERCEPTOR = "foo-ic", REQUEST_HEADER_VALUE_INTERCEPTOR = "bar-ic";
private static final int MAX_CONNECTIONS = 100;
private static final String PATH = "/";
#Path(PATH)
public static class TestResource {
#POST
public String handle(InputStream questionStream,
#HeaderParam(REQUEST_HEADER_NAME_CLIENT) String client,
#HeaderParam(REQUEST_HEADER_NAME_INTERCEPTOR) String interceptor)
throws IOException {
assertEquals(REQUEST_HEADER_VALUE_CLIENT, client);
// Here, the header that was set in the client's writer interceptor is lost.
assertEquals(REQUEST_HEADER_VALUE_INTERCEPTOR, interceptor);
// However, the input stream got gzipped so the WriterInterceptor has been partly applied.
assertEquals(QUESTION, new Scanner(new GZIPInputStream(questionStream)).nextLine());
return ANSWER;
}
}
#Provider
#Priority(Priorities.ENTITY_CODER)
public static class ClientInterceptor implements WriterInterceptor {
#Override
public void aroundWriteTo(WriterInterceptorContext context)
throws IOException, WebApplicationException {
context.getHeaders().add(REQUEST_HEADER_NAME_INTERCEPTOR, REQUEST_HEADER_VALUE_INTERCEPTOR);
context.setOutputStream(new GZIPOutputStream(context.getOutputStream()));
context.proceed();
}
}
#Override
protected Application configure() {
enable(TestProperties.LOG_TRAFFIC);
enable(TestProperties.DUMP_ENTITY);
return new ResourceConfig(TestResource.class);
}
#Override
protected Client getClient(TestContainer tc, ApplicationHandler applicationHandler) {
ClientConfig clientConfig = tc.getClientConfig() == null ? new ClientConfig() : tc.getClientConfig();
clientConfig.property(ApacheClientProperties.CONNECTION_MANAGER, makeConnectionManager(MAX_CONNECTIONS));
clientConfig.register(ClientInterceptor.class);
// If I do not use the Apache connector, I avoid this problem.
clientConfig.connector(new ApacheConnector(clientConfig));
if (isEnabled(TestProperties.LOG_TRAFFIC)) {
clientConfig.register(new LoggingFilter(LOGGER, isEnabled(TestProperties.DUMP_ENTITY)));
}
configureClient(clientConfig);
return ClientBuilder.newClient(clientConfig);
}
private static ClientConnectionManager makeConnectionManager(int maxConnections) {
PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager();
connectionManager.setMaxTotal(maxConnections);
connectionManager.setDefaultMaxPerRoute(maxConnections);
return connectionManager;
}
#Test
public void testInterceptors() throws Exception {
Response response = target(PATH)
.request()
.header(REQUEST_HEADER_NAME_CLIENT, REQUEST_HEADER_VALUE_CLIENT)
.post(Entity.text(QUESTION));
assertEquals(200, response.getStatus());
assertEquals(ANSWER, response.readEntity(String.class));
}
}
I want to use the ApacheConnector in order to optimize for concurrent requests via the PoolingClientConnectionManager. Did I mess up the configuration?
PS: The exact same problem occurs when using the GrizzlyConnector.
After further research, I assume that this is rather a misbehavior in the default Connector that uses a HttpURLConnection. As I explained in this other self-answered question of mine, the documentation states:
Whereas filters are primarily intended to manipulate request and
response parameters like HTTP headers, URIs and/or HTTP methods,
interceptors are intended to manipulate entities, via manipulating
entity input/output streams
A WriterInterceptor is not supposed to manipulate the header values while a {Client,Server}RequestFilter is not supposed to manipulate the entity stream. If you need to use both, both components should be bundled within a javax.ws.rs.core.Feature or within the same class that implements two interfaces. (This can be problematic if you need to set two different Prioritys though.)
All this is very unfortunate though, since JerseyTest uses the Connector that uses a HttpURLConnection such that all my unit tests succeeded while the real life application misbehaved since it was configured with an ApacheConnector. Also, rather than suppressing changes, I wished, Jersey would throw me some exceptions. (This is a general issue I have with Jersey. When I for example used a too new version of the ClientConnectionManager where the interface was renamed to HttpClientConnectionManager I simply was informed in a one line log statement that all my configuration efforts were ignored. I did not discover this log statement til very late in development.)