Pages

February 03, 2020

#SpringSecurity Part 2 : Securing Web-services with Spring (Basic, In-Memory and JDBC Authentication)

How can we implement basic authentication in Spring?
For this let us create a Spring Starter Project titled 'BasicAuthentication' from STS, this project has only Spring Web dependency.

Now we will create a controller classes AuthenticationController.java and UserController.java



Once we run our Spring boot project, we can access below get rest webservices directly. 
  • http://localhost:8080/user/helloUser
  • http://localhost:8080/user/userMsg/Himaanshu
  • http://localhost:8080/auth/sayHello
  • http://localhost:8080/auth/echo/Himaanshu
Now let's say we don't want anyone to directly access these services, instead we want our users to enter user name and password. For this we need to make a small modification in pom.xml and add 'spring-boot-starter-security' dependency:

Now, if you restart the spring boot project after adding this dependency and try to access any web-service you will get a prompt to enter user name and password ( form-based authentication), this is the default behavior for spring security.

The user name, by default is 'user' and we can get the password from the console for our application. When you check console you will see a user-generated security password.


Let's say you do not want to use autogenerated password and user name as 'user', in that case we can configure it via application.properties.

spring.security.user.name=khs
spring.security.user.password=passkhs

When you add above two lines in the application.properties and restart the application, you won't see 'generated security password' on the console. And you can access the web-services with user name as 'khs' and password as 'passkhs'.

When Spring Security is on the classpath, the auto-configuration secures all endpoints by default. However, when it comes to complex applications, we need different security policies per endpoints. We also need to configure which endpoints should be secured, what type of users should be able to access the endpoints, and which endpoints should be public.

Now let's say we want our users to access all the web-services directly, but when they try to access Authentication webservices they need to enter credentials. For this we are going to implement our own security model instead of the default one. 

Since our application is already secured, we need to provide public access to that endpoint. In order to do so, Spring provides us with the HttpSecurity class WebSecurityConfigurerAdapter, by extending this class, we can configure the endpoints that should be secured and the endpoint that should be public.

Lets create a new class ApplicationSecurityConfiguration.java which extends WebSecurityConfigurerAdapter.



We need to override configure() method and add all the configurations inside it. By calling the function antMatchers, we can pass an array of ant patterns. The function applied would create a rule for each endpoint specified in the  antMatchers.

The next function is the anyRequest function. The authenticated rule will be applied to any request received.

Spring comes with the default login form and the default logout endpoint. In order to make it feasible to login and logout, we must allow access to these endpoints. So, the end result should have the welcome endpoint as publicly accessible, a preconfigured form for login, and the log out endpoint.

When you restart the application and try to access web-services present in AuthenticationController, you need to enter the credentials. But you can directly access UserController web-services.

How can we implement In-memory authentication in Spring?
In in-Memory Authentication, we hard-code the authentication credentials that's why this should never be used for production systems.

To implement In-memory authentication, we need to modify our existing ApplicationSecurityConfiguration.java.

To add the users, we will override userDetailsService() and add @Bean annotation.

We created two users with usernames 'admin' and 'user'.

If you notice withDefaultPasswordEncoder method is deprecated because this should never be used in a production system.

FYI, UserDetailsService interface is used to load user-specific data. It has a method loadUserByUsername(), which locates the user by the username. Spring internally uses the returned non-null UserDetails object to verify the password and roles against the client's entered values.


Now, if we restart our application and try to access any web-service we will get a prompt to enter credentials. We can either enter admin and admin123 or user and user123 as user name and password to access the services.

How to configure Spring Security in memory Authentication using AuthenticationManager?
  • First of all let us understand, what is the purpose of AuthenticationManager.
  • We can say AuthenticationManager is something which manages security in a Spring application.
  • org.springframework.security.authentication.AuthenticationManager is an interface with only one method.
public interface AuthenticationManager {
  Authentication authenticate(Authentication authentication)
    throws AuthenticationException;
}

The authenticate() method of AuthenticationManager:
  • return an Authentication (normally with authenticated=true), if it can verify that the input represents a valid principal ( a logged in user is known as principal).
  • throw a runtime exception AuthenticationException, if it believes that the input represents an invalid principal.
  • return null if it can’t decide.
We don't need to implement our own AuthenticationManager, we need to configure what AuthenticationManager can do by using a builder pattern. For this we need to use AuthenticationManagerBuilder. It is the most commonly used helper, which is used in setting up in-memory, JDBC or LDAP user details, or for adding a custom UserDetailsService.

We need AuthenticationManagerBuilder to configure what the authentication should actually do. We can do it in two steps: 1). Get hold of AuthenticationManagerBuilder. 2). Set the configuration on it.

To get hold of AuthenticationManagerBuilder we need to override configure() method. Inside this method we will add user names, password and roles.

Lets create ApplicationSecurityConfiguration which extends WebSecurityConfigurerAdapter. We need to annotate the class with @EnableWebSecurity, this tells spring that this class contains web-security information.

We can add user name, password and roles inside the configure() method. Since we should not store password in clear text we will expose a bean with name PasswordEncoder. To do this we will add a method getPasswordEncoder(), which will return PasswordEncoder and annotate it with @Bean. For the testing purpose we will return NoOpPasswordEncoder from getPasswordEncoder(). NoOpPasswordEncoder actually does not do anything, here we are dealing with a clear text password. Since using the password in clear text is not advisable that's why NoOpPasswordEncoder is deprecated.


If we restart our application. To access our web-services we need to enter username as 'builder1' or 'builder2' and password as 'builder1pass' or 'builder2pass'.

What is the purpose of ProviderManager in Spring Security?
  • ProviderManager is the most commonly used implementation of AuthenticationManager, which delegates to a chain of AuthenticationProvider instances. 
  • An AuthenticationProvider is a bit like an AuthenticationManager but it has an extra method to allow the caller to query if it supports a given Authentication type.

  • The Class < ? > argument in the supports() method is really Class < ? extends Authentication >. 
  • A ProviderManager can support multiple different authentication mechanisms in the same application by delegating to a chain of AuthenticationProviders. 
  • If a ProviderManager doesn’t recognize a particular Authentication instance type it will be skipped.
  • A ProviderManager has an optional parent, which it can consult if all providers return null. If the parent is not available then a null Authentication results in an AuthenticationException.
When the application is huge, we sometimes divide it into a logical groups of protected resources (e.g. all web resources that match a path pattern /user/** or /admin/**).  In this case each group can have its own dedicated AuthenticationManager. Often, each of those is a ProviderManager, and they share a parent. The parent is then a kind of 'global' resource, acting as a fallback for all providers.



How to configure Spring Security Authorization?
Let's say we have 3 kind of web-services and depending on user role we will authorize the our users to access them. All the web-series with url "\user" can be accessed by users with either 'user' or 'admin' roles. All the web-series with url "\admin" can be accessed only by admin role users. And rest all services can be accessed without login.

For this we need to create a class ApplicationSecurityConfiguration, which extends WebSecurityConfigurerAdapter. We need to override configure() methods.

The user name and roles will be added in the configure() method which takes AuthenticationManagerBuilder.

In another configure() which takes HttpSecurity, we will mention the configurations.

Now if you want to access web-services with '\admin' url you need to enter username as 'builder2' and password as 'builder2pass'.

For '\user', we need to enter username as 'builder1' and password as 'builder1pass'. Rest of the services can be accessed directly.

How can we implement Spring Security JDBC Authentication?
Let us create a sprint boot project with name JdbcAuth with spring-boot-starter-jdbc, spring-boot-starter-security, h2 dependencies.

Now we will create a controller class with name HomeResource, which has three rest services. All the users with 'admin' role can access  all the API's including '/admin' web-service, all the users with 'user' role can access  all the API's including '/user' web-service and excluding '/admin' API. Other web-services can be accessed directly.

Now we will create a security configuration class with name SecurityConfiguration which extends WebSecurityConfigurerAdapter.

For Authorization let us override configure() method which takes HttpSecurity.

We need to inform Spring Security, that we have got user information in the database, go and look up the database whenever somebody is trying to do the authentication. This is done by configuring DataSource bean.

We need to override another configure() method, this will give us opportunity to get hold of AuthenticationManagerBuilder. This help us what kind of authentication we need, here in our case we will use jdbcAuthentication(). We need to annotate our SecurityConfiguration class with @EnableWebSecurity, this let spring security to give us AuthenticationManagerBuilder object.

In this configure we will set the dataSource. Then how does spring security know, what exactly database configuration is? We have added H2 as an embedded database, spring boot will smarty add dataSource for us.

We will request Spring to create user and authority tables for us, for this we will add withDefaultSchema() inside configure() method. We can then create a bunch of user (with user name, password and role) .

Default User Schema from documentation: https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#user-schema

Default Schema
Lastly we will add getPasswordEncoder().
Start the spring boot application and now you can access below API's with correct user credentials:
  • http://localhost:8080/admin
  • http://localhost:8080/user
  • http://localhost:8080/
But in real life scenario, we are not going to populate default schema and user details. We will update the configure() method which takes AuthenticationManagerBuilder.

But before this we need to add some sql files to create users and authorities tables. Inside our resources folder we will create a file wuth name schema.sql and add below DDL commands. FYI, Spring boot uses schema.sql to setup the schema.

create table users(
    username varchar_ignorecase(50) not null primary key,
    password varchar_ignorecase(50) not null,
    enabled boolean not null
);

create table authorities (
    username varchar_ignorecase(50) not null,
    authority varchar_ignorecase(50) not null,
    constraint fk_authorities_users foreign key(username) references users(username)
);

create unique index ix_auth_username on authorities (username,authority);

After doing this we will setup 2 users, for this we will create another file data.sql inside resources folder.

INSERT INTO users (username, password, enabled) VALUES ('dbuser', 'pass', true);
INSERT INTO users (username, password, enabled) VALUES ('dbadmin', 'pass', true);

INSERT INTO authorities (username, authority) VALUES ('dbuser', 'ROLE_USER');
INSERT INTO authorities (username, authority) VALUES ('dbadmin', 'ROLE_ADMIN');

Now we will restart out application to access the API's with user name as 'dbuser' or 'dbadmin' and password as 'pass'.

Now let's say we don't want to use default schema and we want to store user differently. We need to tell spring security to look at our schema instead of checking the default one. For this we need to make small modification in our configure method.

We will add usersByUsernameQuery() and authoritiesByUsernameQuery() to tell Spring security to run our custom queries in order to get users and authorities.

-K Himaanshu Shuklaa..

No comments:

Post a Comment