basic auth Archives - Piotr's TechBlog https://piotrminkowski.com/tag/basic-auth/ Java, Spring, Kotlin, microservices, Kubernetes, containers Fri, 21 May 2021 13:09:41 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.1 https://i0.wp.com/piotrminkowski.com/wp-content/uploads/2020/08/cropped-me-2-tr-x-1.png?fit=32%2C32&ssl=1 basic auth Archives - Piotr's TechBlog https://piotrminkowski.com/tag/basic-auth/ 32 32 181738725 Secure Rate Limiting with Spring Cloud Gateway https://piotrminkowski.com/2021/05/21/secure-rate-limiting-with-spring-cloud-gateway/ https://piotrminkowski.com/2021/05/21/secure-rate-limiting-with-spring-cloud-gateway/#comments Fri, 21 May 2021 11:12:04 +0000 https://piotrminkowski.com/?p=9733 In this article, you will learn how to enable rate limiting for an authenticated user with Spring Cloud Gateway. Why it is important? API gateway is an entry point to your microservices system. Therefore, you should provide there a right level of security. Rate limiting can prevent your API against DoS attacks and limit web scraping. You can easily […]

The post Secure Rate Limiting with Spring Cloud Gateway appeared first on Piotr's TechBlog.

]]>
In this article, you will learn how to enable rate limiting for an authenticated user with Spring Cloud Gateway. Why it is important? API gateway is an entry point to your microservices system. Therefore, you should provide there a right level of security. Rate limiting can prevent your API against DoS attacks and limit web scraping.

You can easily configure rate limiting with Spring Cloud Gateway. For a basic introduction to this feature, you may refer to my article Rate Limiting in Spring Cloud Gateway with Redis. Similarly, today we will also use Redis as a backend for a rate limiter. Moreover, we will configure an HTTP basic authentication. Of course, you can provide some more advanced authentication mechanisms like an X509 certificate or OAuth2 login. If you think about it, read my article Spring Cloud Gateway OAuth2 with Keycloak.

Source Code

If you would like to try it by yourself, you may always take a look at my source code. In order to do that you need to clone my repository sample-spring-cloud-gateway. Then you should go to the src/test/java directory, and just follow my instructions in the next sections.

1. Dependencies

Let’s start with dependencies. Since we will create an integration test, we need some additional libraries. Firstly, we will use the Testcontainers library. It allows us to run Docker containers during the JUnit test. We will use it for running Redis and a mock server, which is responsible for mocking a downstream service. Of course, we need to include a starter with Spring Cloud Gateway and Spring Data Redis. To implement an HTTP basic authentication we also need to include Spring Security. Here’s a full list of required dependencies in Maven pom.xml.

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
</dependency>
<dependency>
   <groupId>org.testcontainers</groupId>
   <artifactId>mockserver</artifactId>
   <scope>test</scope>
</dependency>
<dependency>
   <groupId>org.mock-server</groupId>
   <artifactId>mockserver-client-java</artifactId>
   <scope>test</scope>
</dependency>

2. Configure an HTTP Basic Authentication

In order to configure an HTTP basic authentication, we need to create the @Configuration bean annotated with @EnableWebFluxSecurity. That’s because Spring Cloud Gateway is built on top of Spring WebFlux and Netty. Also, we will create a set of test users with MapReactiveUserDetailsService.

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

   @Bean
   public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
      http.authorizeExchange(exchanges -> 
         exchanges.anyExchange().authenticated())
            .httpBasic();
      http.csrf().disable();
      return http.build();
   }

   @Bean
   public MapReactiveUserDetailsService users() {
      UserDetails user1 = User.builder()
            .username("user1")
            .password("{noop}1234")
            .roles("USER")
            .build();
      UserDetails user2 = User.builder()
            .username("user2")
            .password("{noop}1234")
            .roles("USER")
            .build();
      UserDetails user3 = User.builder()
            .username("user3")
            .password("{noop}1234")
            .roles("USER")
            .build();
      return new MapReactiveUserDetailsService(user1, user2, user3);
   }
}

3. Configure Spring Cloud Gateway Rate Limiter key

A request rate limiter feature needs to be enabled using the component called GatewayFilter. This filter takes an optional keyResolver parameter. The KeyResolver interface allows you to create pluggable strategies derive the key for limiting requests. In our case, it will be a user login. Once a user has been successfully authenticated, its login is stored in the Spring SecurityContext. In order to retrieve the context for a reactive application, we should use ReactiveSecurityContextHolder.

@Bean
KeyResolver authUserKeyResolver() {
   return exchange -> ReactiveSecurityContextHolder.getContext()
           .map(ctx -> ctx.getAuthentication()
              .getPrincipal().toString());
}

4. Test Scenario

In the test scenario, we are going to simulate incoming traffic. Every single request needs to have a Authorization header with the user credentials. A single user may send 4 requests per minute. After exceeding that limit Spring Cloud Gateway will return the HTTP code HTTP 429 - Too Many Requests. The traffic is addressed to the downstream service. Therefore, we are running a mock server using Testcontainers.

spring-gateway-rate-limiting-arch

5. Testing Spring Cloud Gateway secure rate limiter

Finally, we may proceed to the test implementation. I will use JUnit4 since I used it before for the other examples in the sample repository. We have three parameters used for rate limiter configuration: replenishRate, burstCapacity and requestedTokens. Since we also allow less than 1 request per second we need to set the right values for burstCapacity and requestedTokens. In short, the requestedTokens property sets how many tokens a request costs. On the other hand, the burstCapacity property is the maximum number of requests (or cost) that is allowed for a user.

During the test we randomly set the username between user1, user2 and user3. The test is repeated 20 times.

@SpringBootTest(webEnvironment = 
   SpringBootTest.WebEnvironment.DEFINED_PORT,
                properties = {"rateLimiter.secure=true"})
@RunWith(SpringRunner.class)
public class GatewaySecureRateLimiterTest {

   private static final Logger LOGGER = 
      LoggerFactory.getLogger(GatewaySecureRateLimiterTest.class);
   private Random random = new Random();

   @Rule
   public TestRule benchmarkRun = new BenchmarkRule();

   @ClassRule
   public static MockServerContainer mockServer = 
      new MockServerContainer();
   @ClassRule
   public static GenericContainer redis = 
      new GenericContainer("redis:5.0.6").withExposedPorts(6379);

   @Autowired
   TestRestTemplate template;

   @BeforeClass
   public static void init() {
      System.setProperty("spring.cloud.gateway.routes[0].id", "account-service");
      System.setProperty("spring.cloud.gateway.routes[0].uri", "http://" + mockServer.getHost() + ":" + mockServer.getServerPort());
      System.setProperty("spring.cloud.gateway.routes[0].predicates[0]", "Path=/account/**");
      System.setProperty("spring.cloud.gateway.routes[0].filters[0]", "RewritePath=/account/(?<path>.*), /$\\{path}");
      System.setProperty("spring.cloud.gateway.routes[0].filters[1].name", "RequestRateLimiter");
      System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.redis-rate-limiter.replenishRate", "1");
      System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.redis-rate-limiter.burstCapacity", "60");
      System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.redis-rate-limiter.requestedTokens", "15");
      System.setProperty("spring.redis.host", redis.getHost());
      System.setProperty("spring.redis.port", "" + redis.getMappedPort(6379));
      new MockServerClient(mockServer.getContainerIpAddress(), mockServer.getServerPort())
            .when(HttpRequest.request()
                    .withPath("/1"))
            .respond(response()
                    .withBody("{\"id\":1,\"number\":\"1234567890\"}")
                    .withHeader("Content-Type", "application/json"));
   }

   @Test
   @BenchmarkOptions(warmupRounds = 0, concurrency = 1, benchmarkRounds = 20)
   public void testAccountService() {
      String username = "user" + (random.nextInt(3) + 1);
      HttpHeaders headers = createHttpHeaders(username,"1234");
      HttpEntity<String> entity = new HttpEntity<String>(headers);
      ResponseEntity<Account> r = template
         .exchange("/account/{id}", HttpMethod.GET, entity, Account.class, 1);
      LOGGER.info("Received({}): status->{}, payload->{}, remaining->{}",
            username, r.getStatusCodeValue(), r.getBody(), r.getHeaders().get("X-RateLimit-Remaining"));
    }

   private HttpHeaders createHttpHeaders(String user, String password) {
      String notEncoded = user + ":" + password;
      String encodedAuth = Base64.getEncoder().encodeToString(notEncoded.getBytes());
      HttpHeaders headers = new HttpHeaders();
      headers.setContentType(MediaType.APPLICATION_JSON);
      headers.add("Authorization", "Basic " + encodedAuth);
      return headers;
   }

}

Let’s run the test. Thanks to the junit-benchmarks library we may configure the number of rounds for the test. Each time I’m logging the response from the gateway that includes username, HTTP status, payload, and a header X-RateLimit-Remaining that shows a number of remaining tokens. Here’s the result.

The post Secure Rate Limiting with Spring Cloud Gateway appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2021/05/21/secure-rate-limiting-with-spring-cloud-gateway/feed/ 7 9733
Micronaut Tutorial: Security https://piotrminkowski.com/2019/04/25/micronaut-tutorial-security/ https://piotrminkowski.com/2019/04/25/micronaut-tutorial-security/#respond Thu, 25 Apr 2019 14:34:16 +0000 https://piotrminkowski.wordpress.com/?p=7122 This is the third part of my tutorial to Micronaut Framework. This time we will discuss the most interesting Micronaut security features. I have already described core mechanisms for IoC and dependency injection in the first part of my tutorial, and I have also created a guide to building a simple REST server-side application in […]

The post Micronaut Tutorial: Security appeared first on Piotr's TechBlog.

]]>
This is the third part of my tutorial to Micronaut Framework. This time we will discuss the most interesting Micronaut security features. I have already described core mechanisms for IoC and dependency injection in the first part of my tutorial, and I have also created a guide to building a simple REST server-side application in the second part.

For more details you may refer to:

Security is an essential part of every web application. Easily configurable, built-in web security mechanisms is something that every single modern micro-framework must have. It is no different with Micronaut. In this part of my tutorial you will learn how to:

  • Build custom authentication provider
  • Configure and test basic authentication for your HTTP API
  • Secure your HTTP API using JSON Web Tokens
  • Enable communication over HTTPS

Enabling security

To enable security for Micronaut application you should first include the following dependency into your pom.xml:

<dependency>
   <groupId>io.micronaut</groupId>
   <artifactId>micronaut-security</artifactId>
</dependency>

The next step is to enable security feature through application properties:

micronaut:
  security:
    enabled: true

Setting the property micronaut.security.enabled to true causes enabling security for all the existing controllers. Because we already have the controller that has been used as an example for the previous part of the tutorial, we should disable security for it. To do that I have annotated with @Secured(SecurityRule.IS_ANONYMOUS). It allows anonymous access to all endpoints implemented inside the controller.

@Controller("/persons")
@Secured(SecurityRule.IS_ANONYMOUS)
@Validated
public class PersonController { ... }

Basic Authentication Provider

Once you enable Micronaut security, Basic Auth is enabled by default. All you need to do is to implement your custom authentication provider. It has to implement an AuthenticationProvider interface. In fact, you just need to verify your username and password, which are both passed inside HTTP Authorization header. Our sample authentication provider uses configuration properties as a user repository. Here’s the fragment of application.yml file that contains list of user passwords and assigned roles:

credentials:
  users:
    smith: smith123
    scott: scott123
    piomin: piomin123
    test: test123
  roles:
    smith: ADMIN
    scott: VIEW
    piomin: VIEW
    test: ADMIN

The configuration properties are injected into UsersStore configuration bean which is annotated with @ConfigurationProperties. User passwords are stored inside users map, while roles inside roles map. They are both annotated with @MapFormat and have username as a key.

@ConfigurationProperties("credentials")
public class UsersStore {

   @MapFormat
   Map<String, String> users;
   @MapFormat
   Map<String, String> roles;

   public String getUserPassword(String username) {
      return users.get(username);
   }

   public String getUserRole(String username) {
      return roles.get(username);
   }
}

Finally, we may proceed to the authentication provider implementation. It injects a UsersStore bean that contains a list of users with passwords and roles. The overridden method should return UserDetails object. The username and password are automatically decoded from base64 taken from Authentication header and bind to fields identity and secret in AuthenticationRequest method parameter. If input password is the same as stored password it returns UserDetails object with roles, otherwise throws an exception.

@Singleton
public class UserPasswordAuthProvider implements AuthenticationProvider {

    @Inject
    UsersStore store;

    @Override
    public Publisher<AuthenticationResponse> authenticate(AuthenticationRequest req) {
        String username = req.getIdentity().toString();
        String password = req.getSecret().toString();
        if (password.equals(store.getUserPassword(username))) {
            UserDetails details = new UserDetails(username, Collections.singletonList(store.getUserRole(username)));
            return Flowable.just(details);
        } else {
            return Flowable.just(new AuthenticationFailed());
        }
    }
}

Secured Controller

Now, we may create our sample secure REST controller. The following controller is just a copy of previously described controller PersonController, but it also contains some Micronaut Security annotation. Through @Secured(SecurityRule.IS_AUTHENTICATED) used on the whole controller it is available only for succesfully authenticated users. This annotation may be overridden on the method level. The method for adding a new person is available only for users having ADMIN role.

@Controller("/secure/persons")
@Secured(SecurityRule.IS_AUTHENTICATED)
public class SecurePersonController {

   List<Person> persons = new ArrayList<>();

   @Post
   @Secured("ADMIN")
   public Person add(@Body @Valid Person person) {
      person.setId(persons.size() + 1);
      persons.add(person);
      return person;
   }

   @Get("/{id:4}")
   public Optional<Person> findById(@NotNull Integer id) {
      return persons.stream()
            .filter(it -> it.getId().equals(id))
            .findFirst();
   }

   @Version("1")
   @Get("{?max,offset}")
   public List<Person> findAll(@Nullable Integer max, @Nullable Integer offset) {
      return persons.stream()
            .skip(offset == null ? 0 : offset)
            .limit(max == null ? 10000 : max)
            .collect(Collectors.toList());
   }

   @Version("2")
   @Get("?max,offset")
   public List<Person> findAllV2(@NotNull Integer max, @NotNull Integer offset) {
      return persons.stream()
            .skip(offset == null ? 0 : offset)
            .limit(max == null ? 10000 : max)
            .collect(Collectors.toList());
   }

}

To test Micronaut security features used in our controller we will create a JUnit test class containing three methods. All these methods use Micronaut HTTP client for calling target endpoints. It provides basicAuth method, that allows you to easily pass user credentials. The first test method testAdd verifies a positive scenario of adding a new person. The test user smith has ADMIN role, which is required for calling this HTTP endpoint. In contrast, method testAddFailed calls the same HTTP endpoint, but with different user scott, which has VIEW role. We expect that HTTP 401 is returned by the endpoint. The same user scott has an access to GET endpoints, so we expect that test method testFindById is finished with success.

@MicronautTest
public class SecurePersonControllerTests {

   @Inject
   EmbeddedServer server;

   @Test
   public void testAdd() throws MalformedURLException {
      HttpClient client = HttpClient.create(new URL("http://" + server.getHost() + ":" + server.getPort()));
      Person person = new Person();
      person.setFirstName("John");
      person.setLastName("Smith");
      person.setAge(33);
      person.setGender(Gender.MALE);
      person = client.toBlocking()
            .retrieve(HttpRequest.POST("/secure/persons", person).basicAuth("smith", "smith123"), Person.class);
      Assertions.assertNotNull(person);
      Assertions.assertEquals(Integer.valueOf(1), person.getId());
   }

   @Test
   public void testAddFailed() throws MalformedURLException {
      HttpClient client = HttpClient.create(new URL("http://" + server.getHost() + ":" + server.getPort()));
      Person person = new Person();
      person.setFirstName("John");
      person.setLastName("Smith");
      person.setAge(33);
      person.setGender(Gender.MALE);
      Assertions.assertThrows(HttpClientResponseException.class,
            () -> client.toBlocking().retrieve(HttpRequest.POST("/secure/persons", person).basicAuth("scott", "scott123"), Person.class),
            "Forbidden");
   }

   @Test
   public void testFindById() throws MalformedURLException {
      HttpClient client = HttpClient.create(new URL("http://" + server.getHost() + ":" + server.getPort()));
      Person person = client.toBlocking()
            .retrieve(HttpRequest.GET("/secure/persons/1").basicAuth("scott", "scott123"), Person.class);
      Assertions.assertNotNull(person);
   }
}

Enable HTTPS

Our controller is secured, but not the HTTP server. Micronaut by default starts the server with disabled SSL. However, it supports HTTPS out of the box. To enable HTTPS support you should first set property micronaut.ssl.enabled to true. By default Micronaut with HTTPS enabled starts on port 8443, but you can override it using property micronaut.ssl.port.
We will enable HTTPS only for the single JUnit test class. To do that we first create file src/test/resources/ssl.yml with the following configuration:

micronaut:
  ssl:
    enabled: true
    buildSelfSigned: true

Micronaut simplifies SSL configuration build for test purposes. It turns out, we don’t have to generate any keystore or certificate if we use property micronaut.ssl.buildSelfSigned. Otherwise you would have to generate a keystore by yourself. It is not difficult, if you are creating a self-signed certificate. You may use openssl or keytool for that. Here’s the appropriate keytool command for generating keystore, however you should point out that it is recommended tool by Micronaut, which recommend using openssl:

$ keytool -genkey -alias server -keystore server.jks

If you decide to generate self-signed certificate by yourself you have configure them:

micronaut:
  ssl:
    enabled: true
    keyStore:
      path: classpath:server.keystore
      password: 123456
      type: JKS

The last step is to create a JUnit test that uses configuration provided in file ssl.yml.

@MicronautTest(propertySources = "classpath:ssl.yml")
public class SecureSSLPersonControllerTests {

   @Inject
   EmbeddedServer server;
   
   @Test
   public void testFindById() throws MalformedURLException {
      HttpClient client = HttpClient.create(new URL(server.getScheme() + "://" + server.getHost() + ":" + server.getPort()));
      Person person = client.toBlocking()
            .retrieve(HttpRequest.GET("/secure/persons/1").basicAuth("scott", "scott123"), Person.class);
      Assertions.assertNotNull(person);
   }
   
   // other tests ...

}

JWT Authentication

To enable JWT token based authentication we first need to include the following dependency into pom.xml:

<dependency>
   <groupId>io.micronaut</groupId>
   <artifactId>micronaut-security-jwt</artifactId>
</dependency>

Token authentication is enabled by default through TokenConfigurationProperties properties (micronaut.security.token.enabled). However, we should enable JWT based authentication by setting property micronaut.security.token.jwt.enabled to true. This change allows us to use JWT authentication for our sample application. We also need to be able to generate an authentication token used for authorization. To do that we should enable /login endpoint and set some configuration properties for the JWT token generator. In the following fragment of application.yml I set HMAC with SHA-256 as hash algorithm for JWT signature generator:

micronaut:
  security:
    enabled: true
    endpoints:
      login:
        enabled: true
    token:
      jwt:
        enabled: true
        signatures:
          secret:
            generator:
              secret: pleaseChangeThisSecretForANewOne
              jws-algorithm: HS256

Now, we can call endpoint POST /login with username and password in JSON body as shown below:

$ curl -X "POST" "http://localhost:8100/login" -H 'Content-Type: application/json; charset=utf-8' -d '{"username":"smith","password":"smith123"}'
{
   "username": "smith",
   "roles": [
      "ADMIN"
   ],
   "access_token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzbWl0aCIsIm5iZiI6MTU1NjE5ODAyMCwicm9sZXMiOlsiQURNSU4iXSwiaXNzIjoic2FtcGxlLW1pY3JvbmF1dC1hcHBsaWNhdGlvbiIsImV4cCI6MTU1NjIwMTYyMCwiaWF0IjoxNTU2MTk4MDIwfQ.by0Dx73QIZeF4MDM4A5nHgw8xm4haPJjsu9z45psQrY",
   "refresh_token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzbWl0aCIsIm5iZiI6MTU1NjE5ODAyMCwicm9sZXMiOlsiQURNSU4iXSwiaXNzIjoic2FtcGxlLW1pY3JvbmF1dC1hcHBsaWNhdGlvbiIsImlhdCI6MTU1NjE5ODAyMH0.2BrdZzuvJNymZlOv56YpUPHYLDdnVAW5UXXNuz3a7xU",
   "token_type": "Bearer",
   "expires_in": 3600
}

The value of field access_token returned in the response should be passed as bearer token in the Authorization header of requests sent to HTTP endpoints. We can any endpoint, for example GET /persons

$ curl -X "GET" "http://localhost:8100/persons" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzbWl0aCIsIm5iZiI6MTU1NjE5ODAyMCwicm9sZXMiOlsiQURNSU4iXSwiaXNzIjoic2FtcGxlLW1pY3JvbmF1dC1hcHBsaWNhdGlvbiIsImV4cCI6MTU1NjIwMTYyMCwiaWF0IjoxNTU2MTk4MDIwfQ.by0Dx73QIZeF4MDM4A5nHgw8xm4haPJjsu9z45psQrY"

We can easily test automatically the scenario described above. I have created UserCredentials and UserToken objects for serializing request and deserializing response from /login endpoint. The token retrieved from response is then passed as bearer token by calling bearerAuth method on Micronaut HTTP client instance.

@MicronautTest
public class SecurePersonControllerTests {

   @Inject
   EmbeddedServer server;
   
   @Test
   public void testFindByIdUsingJWTToken() throws MalformedURLException {
      HttpClient client = HttpClient.create(new URL("http://" + server.getHost() + ":" + server.getPort()));
      UserToken token = client.toBlocking().retrieve(HttpRequest.POST("/login", new User Credentials("scott", "scott123")), UserToken.class);
      Person person = client.toBlocking()
            .retrieve(HttpRequest.GET("/secure/persons/1").bearerAuth(token.getAccessToken()), Person.class);
      Assertions.assertNotNull(person);
   }
}

Source Code

We were using the same repository as for two previous parts of my Micronaut tutorial: https://github.com/piomin/sample-micronaut-applications.git.

The post Micronaut Tutorial: Security appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2019/04/25/micronaut-tutorial-security/feed/ 0 7122