Configuring spring security Authentication

  • The way to configure spring security is by affecting AuthenticationManager.
  • AuthenticationManager manages the authentication of the spring application.
  • AuthenticationManager has a method called authenticate() which either returns a successful authentication or it throws an exception when it comes to authentication.
  • The way to affect authentication manager is not creating your own AuthenticationManager but instead to configure what AuthenticatioManager does using a builder pattern.
  • We don’t work with AuthenticationManager directly but with the builder class AuthenticationManagerBuilder.
  • We use AuthenticationManagerBuilder to configure what the authentication manager should actually do.

Steps of handling AuthenticationManagerBuilder :

  • What type of authentication do you need?
    • InMemoryAuthentication,JDBC Authentication
  • Tell me the user name, password and the role
  • At the end AuthenticationManagerBuilder creates an AuthenticationManager with all the properties added above.

How to get hold of AuthenticationManagerBuilder?

  • We do that by leveraging the hook, this hook is a configure(AuthMgrBlr auth) method in a class and we extend it to get the hold of AuthenticationManagerBuilder object

InMemoryAuthentication

package SpringSecurityAuthentication;

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@EnableWebSecurity //inform spring that this is a spring security configuration class
public class SecurityConfig extends WebSecurityConfigurerAdapter{

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//		super.configure(auth);
		auth.inMemoryAuthentication()
			.withUser("tyson")
			.password("tyson")
			.roles("USER")
			.and()//returns the object of inMemoryAuthentication
			.withUser("justin")
			.password("justin")
			.roles("ADMIN");
	}
	
	@Bean
	public PasswordEncoder getPasswordEncoder() {
		return NoOpPasswordEncoder.getInstance();
		//return new BCryptPasswordEncoder();
	}
	public static void main(String[] args) {
		String hasedPass = new BCryptPasswordEncoder().encode("tyson");
		System.out.println(hasedPass);
	}
}

How spring security starter JAR does bootstraps the application?(Changes everything just by adding a JAR into the application)

  • Internally adds a filter, same which is used in web.xml file of servlet
  • When the JAR is added into the spring boot project, it intercepts all the request (/**) and maps it to spring securities own filter called DelegatingFilterProxy.(Internally delegates the request to bunch of other filters to do the job)
  • Then you tell SpringSecurity that authentication and authorization is required per URL
  • One such filter that DelegatingFilterProxy forwards request to is “AuthenticationFilter

How internally does the spring security performs Authentication?

  • Authentication takes input of the user credentials
  • Authentication gives output as a Princial Object(Information about the logged in user)
  • When spring security performs authentication it stores both input and output in Authentication object. Before authentication it stores the credentials and after authentication it stores the principal.
  • You can consider Authentication Object as the Data Transfer Object for Authentication

What does the actual Authentication?

  • There are many ways in which actual Authentication can happen but the most common way is “Providers“. In out context AuthenticationProvider.
  • AuthenticationProvider Interface has a method called as “authenticate()”, we need to have implementation of authenticate method in your application for authentication
  • authenticate() method take Authentication(Interface implementation needs to be done) Object as an input parameter which holds credentials and returns an object of Authentication which holds principal.
  • Authentication Interface has methods like below
  • We can have multiple ways of authenticating in an application, and for each one of the authentication mechanism must have their own implementation of AuthenticationProvider
  • An application can have multiple AuthenticationProvider, one for login in login id and password, one with SSO AuthenticationProvider, LDAP Authentication provider etc.
  • AuthenticationManager is the class that decides which AuthenticationProvider has to be called for which type of Authentication.

How does AuthenticationProvider internally works?

  • So if some user comes to the application with credentials and ask to authenticate him/her then what should be the AuthenticationProvider doing?
  • Its should be checking the credentials give to it which the data in the IdentityStore(location where all the credentials can be verified mostly a database or a file where user credentials are stored).
  • So no matter what types of authentication basically what the AuthenticationProvider needs to do is to fetch the data from the identify store and validate it. Since we are doing the same thing for all the type of authentication Spring Security has removed that part into its own entity called as ‘UserDetailsService’.
  • UserDetailsSevice, this entity takes an username as input and returns an Object of type UserDetails with details of that username in that returned object.
  • Once the authentication is completed either as failed or success
  • If Authentication failed at any stage then an exception is throwned which is cascaded into AuthenticationFilter
  • If Authentication is successful then the Authentication object is saved in the ThreadLocal.
  • There is a different filter in SpringSecurity that maps that user session with the Authentication object in the Thread local so that it need no authenticate every time the URL is hit and it is available in the framework.

JDBC Authentication

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.2.5.RELEASE'
    id 'io.spring.dependency-management' version '1.0.7.RELEASE'
}

repositories {
    jcenter()
}

dependencies {
	implementation 'mysql:mysql-connector-java:8.0.19'
	implementation 'org.springframework:spring-jdbc:5.2.4.RELEASE'
    implementation 'com.google.guava:guava:28.0-jre'
    testImplementation 'junit:junit:4.12'
    implementation 'org.springframework.boot:spring-boot-dependencies:2.0.5.RELEASE'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
	implementation 'org.springframework.boot:spring-boot-starter-security:2.2.5.RELEASE'

    components {
        withModule('org.springframework:spring-beans') {
            allVariants {
                withDependencyConstraints {
                    it.findAll { it.name == 'snakeyaml' }.each { it.version { strictly '1.19' } }
                }
            }
        }
    }
}

task runJar{
	dependsOn 'assemble'
	dependsOn 'jar'
	doLast{
  		javaexec { 
    		main="-jar";
    		args = [
            	"build/libs/"+rootProject.name+".jar"
           	]
		} 
	}
}

App.java

package JDBCAuthentication;

import java.sql.SQLException;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;


@SpringBootApplication
public class App {

	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}

	@Bean
	public SimpleDriverDataSource dataSource() throws SQLException {
		SimpleDriverDataSource ds = new SimpleDriverDataSource();
        ds.setDriver(new com.mysql.cj.jdbc.Driver());
        ds.setUrl("jdbc:mysql://localhost:3306/mysql");
        ds.setUsername("tyson");
        ds.setPassword("tyson");
        return ds;
	}
}

AppController.java

package JDBCAuthentication;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AppController {
	
	@GetMapping("/")
	public String home() {
		return "Welcome home";
	}
	
	@GetMapping("/user")
	public String user() {
		return "Welcome User";
	}
	
	@GetMapping("/admin")
	public String admin() {
		return "Welcome Admin";
	}
	
	@GetMapping("/ops")
	public String ops() {
		return "Welcome Ops";
	}
}

SecurityConfig.java

package JDBCAuthentication;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@EnableWebSecurity //inform spring that this is a spring security configuration class
public class SecurityConfig extends WebSecurityConfigurerAdapter{

	@Autowired
	DataSource dataSource;
	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.jdbcAuthentication().dataSource(dataSource)
			.usersByUsernameQuery("select username,password,enabled from users where username=?")
			.authoritiesByUsernameQuery("select username,authority from authorities where username=?");
	}
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		//super.configure(http);
		http.authorizeRequests()
			.antMatchers("/admin").hasRole("ADMIN")
			.antMatchers("/user").hasAnyRole("USER","ADMIN")
			.antMatchers("/ops").hasAnyRole("OPS")
			.antMatchers("/","static/css","static/js").permitAll()
			//.antMatchers("/**").hasAnyRole("USER","ADMIN")//matches all paths in the current level and nested levels below it
			.and().formLogin();//oauth2Login,openidLogin,httpBasic
	}

	@Bean
	public PasswordEncoder getPasswordEncoder() {
		return NoOpPasswordEncoder.getInstance();
		//return new BCryptPasswordEncoder();
	}
	
	public static void main(String[] args) {
		String hasedPass = new BCryptPasswordEncoder().encode("tyson");
		System.out.println(hasedPass);
	}
}

MySQL Queries :

CREATE TABLE users (
    USERNAME VARCHAR(128),
    PASSWORD VARCHAR(128),
    ENABLED boolean
);
insert into users values ('user','pass',true);
insert into users values ('admin','pass',true);

delete from users;
drop table users;

CREATE TABLE authorities (
    USERNAME VARCHAR(128) NOT NULL,
    AUTHORITY VARCHAR(128) NOT NULL
);
insert into authorities values ('user','ROLE_USER');
insert into authorities values ('user','ROLE_OPS');
insert into authorities values ('admin','ROLE_ADMIN');

delete from authorities;
drop table authorities;

Leave a Comment