- Spring Security - Discussion
- Spring Security - Useful Resources
- Spring Security - Quick Guide
- Spring Security - JWT
- Spring Security - OAuth2
- Spring Security - XML Configuration
- Spring Security - Taglib
- Spring Security - Form Login, Remember Me & Logout
- Spring Security - Form Login with Database
- Spring Security - Home
Selected Reading
- Who is Who
- Computer Glossary
- HR Interview Questions
- Effective Resume Writing
- Questions and Answers
- UPSC IAS Exams Notes
Spring Security - Quick Guide
Spring Security - Form Login with Database
Contents
Introduction and Overview
Basic components of Spring Security
AuthenticationFilter
AuthenticationManager
AuthenticationProvider
UserDetailsService
PasswordEncoder
Spring Security Context
Form Login
Login with a Database
Login Attempts Limit
Getting Started (Practical Guide)
Introduction and Overview
In addition to providing various inbuilt authentication and authorization options, Spring Security allows us to customize our authentication process as much as we want. Starting from a custom login page to our very own customized authentication providers and authentication filters, we can pretty much customize every aspect of the authentication process. We can define our own authentication process which can range from basic authentication using a username and a password to a complex one such as two-factor authentication using tokens and OTP’s. Also, we can use various databases – both relational and non-relational, use various password encoders, lock mapcious users out of their accounts, and so on.
Today, we are going to discuss three such customizations, namely – custom form-login, a database provided authentication, and pmiting login attempts. Though these are pretty basic use-cases, yet these still will let us have a closer look into Spring Security’s authentication and authorization process. We are also going to set up a registration page through which the users will be able to register themselves with our apppcation.
First of all, let’s take a look at the architecture of Spring Security. It starts with servlet filters. These filters intercept requests, perform operations on them, and then pass the requests on to next filters in the filter chain or request handlers or block them if they do not meet certain conditions. It is during this process that Spring Security can authenticate requests and perform various authentication checks on the requests. It can also prevent unauthenticated or mapcious requests from accessing our protected resources by not allowing them to pass through. Thus our apppcation and resources stay protected.
Components of Spring Security Architecture
The basic components of Spring Security, as we can see in the above diagram are given below. We shall discuss them briefly as we go along. We shall also discuss their roles in the authentication and authorization process.
AuthenticationFilter
This is the filter that intercepts requests and attempts to authenticate it. In Spring Security, it converts the request to an Authentication Object and delegates the authentication to the AuthenticationManager.
AuthenticationManager
It is the main strategy interface for authentication. It uses the lone method authenticate() to authenticate the request. The authenticate() method performs the authentication and returns an Authentication Object on successful authentication or throw an AuthenticationException in case of authentication failure. If the method can’t decide, it will return null. The process of authentication in this process is delegated to the AuthenticationProvider which we will discuss next.
AuthenticationProvider
The AuthenticationManager is implemented by the ProviderManager which delegates the process to one or more AuthenticationProvider instances. Any class implementing the AuthenticationProvider interface must implement the two methods – authenticate() and supports(). First, let us talk about the supports() method. It is used to check if the particular authentication type is supported by our AuthenticationProvider implementation class. If it is supported it returns true or else false. Next, the authenticate() method. Here is where the authentication occurs. If the authentication type is supported, the process of authentication is started. Here is this class can use the loadUserByUsername() method of the UserDetailsService implementation. If the user is not found, it can throw a UsernameNotFoundException.
On the other hand, if the user is found, then the authentication details of the user are used to authenticate the user. For example, in the basic authentication scenario, the password provided by the user may be checked with the password in the database. If they are found to match with each other, it is a success scenario. Then we can return an Authentication object from this method which will be stored in the Security Context, which we will discuss later.
UserDetailsService
It is one of the core interfaces of Spring Security. The authentication of any request mostly depends on the implementation of the UserDetailsService interface. It is most commonly used in database backed authentication to retrieve user data. The data is retrieved with the implementation of the lone loadUserByUsername() method where we can provide our logic to fetch the user details for a user. The method will throw a UsernameNotFoundException if the user is not found.
PasswordEncoder
Until Spring Security 4, the use of PasswordEncoder was optional. The user could store plain text passwords using in-memory authentication. But Spring Security 5 has mandated the use of PasswordEncoder to store passwords. This encodes the user’s password using one its many implementations. The most common of its implementations is the BCryptPasswordEncoder. Also, we can use an instance of the NoOpPasswordEncoder for our development purposes. It will allow passwords to be stored in plain text. But it is not supposed to be used for production or real-world apppcations.
Spring Security Context
This is where the details of the currently authenticated user are stored on successful authentication. The authentication object is then available throughout the apppcation for the session. So, if we need the username or any other user details, we need to get the SecurityContext first. This is done with the SecurityContextHolder, a helper class, which provides access to the security context. We can use the setAuthentication() and getAuthentication() methods for storing and retrieving the user details respectively.
Moving on, let’s now discuss the three custom implementations we are going to use for our apppcation.
Form Login
When we add Spring Security to an existing Spring apppcation it adds a login form and sets up a dummy user. This is Spring Security in auto-configuration mode. In this mode, it also sets up the default filters, authentication-managers, authentication-providers, and so on. This setup is an in-memory authentication setup. We can override this auto-configuration to set up our own users and authentication process. We can also set up our custom login method pke a custom login form. Spring Security only has to made aware of the details of the login form pke – the URI of the login form, the login processing URL, etc.. It will then render our login form for the apppcation and carry out the process of authentication along with the other provided configurations or Spring’s own implementation.
This custom form setup will only have to abide by certain rules to be integrated with Spring Security. We need to have a username parameter and a password parameter and the parameter names should be “username” and “password” since those are the default names. In case, we use our own parameter names for these fields in the custom we have to inform Spring Security of those changes using the usernameParameter() and passwordParameter() methods. Similarly, for every change we do to the login form or the form login method, we will have to inform Spring Security of those changes with appropriate methods so that it can integrate them with the authentication process.
Login with a Database
As we discussed, Spring Security automatically provides an in-memory authentication implementation by default. We can override this by authenticating users whose details are stored in a database. In this case, while authenticating a user, we can verify the credentials provided by the user against those in the database for authentication. We can also let new users register in our apppcation and store their credentials in the same database. Also, we can provide methods to change or update their passwords or roles or other data. As a result, this provides us with persistent user data which can be used for longer periods of time.
Login Attempts Limit
To pmit login attempts in our apppcation we can use Spring Security’s isAccountNonLocked property. Spring Security’s UserDetails provides us with that property. We can set up an authentication method wherein, if any user or someone else provides incorrect credentials for more than a certain number of times, we can lock their account. Spring Security disables authentication for a locked user even if the user provides correct credentials. This is an in-built feature provided by Spring Security. We can store the number of incorrect login attempts in our database. Then against each incorrect authentication attempt, we can update and check with the database table. When the number of such attempts exceeds a given number, we can lock the user out of their account. Consequently, the user will not be able to log in again until their account is unlocked.
Getting Started (Practical Guide)
Let’s start with our apppcation now. The tools we will be needing for this apppcation are psted below −
A Java IDE − preferable STS 4, but Ecppse, IntelpJ Idea or any other IDE will do.
− We need to download and install MySql Community Server in our system. We can go to the official website by cpcking here.
− It is a GUI tool that we can use to interact with MySql databases.
Database Setup
Let’s set up the database first. We will use a MySql database instance for this apppcation.
is available for free download and use. We will use to connect with our MySql Server and create a database called “spring” to use with our apppcation.Then we will create two tables – users and attempts– to persist our users and login attempts. As mentioned earper, the details of the users registering with our apppcation will be stored in the users table. The number of login attempts by any user will be stored in the attempts table against his username. This way we can track the attempts and take necessary action.
Let’s take a the look at the SQL to setup our users table and attempts table.
CREATE TABLE users ( username VARCHAR(45) NOT NULL , password VARCHAR(45) NOT NULL , account_non_locked TINYINT NOT NULL DEFAULT 1 , PRIMARY KEY (username) ); CREATE TABLE attempts ( id int(45) NOT NULL AUTO_INCREMENT, username varchar(45) NOT NULL, attempts varchar(45) NOT NULL, PRIMARY KEY (id) );
We can now add a dummy user to our apppcation.
INSERT INTO users(username,password,account_non_locked) VALUES ( user , 12345 , true);
Project Setup
As usual, we will use the Spring Initiapzer to setup our project. We are going to create a Maven project, with Spring Boot version 2.3.2. Let’s name our project formlogin(we can choose any name we want) and group id as com.tutorial.spring.security. Furthermore, we will use Java version 8 for this project.
Dependencies
Now, coming to the dependencies, we are going to keep our apppcation as simple as possible for this demo. We will keep our focus on the features we want to explore today. So we will choose the minimum number of dependencies that will help us set up our apppcation and get it up and running quickly. Let’s go through the dependencies −
Spring Web − It bundles all dependencies related to web development including Spring MVC, REST, and an embedded Tomcat Server.
Spring Security − For the implementation of security features provided by Spring Security.
Thymeleaf − A server-side Java template engine for HTML5/XHTML/XML.
Spring Data JPA − In addition to using all features defined by JPA specification, Spring Data JPA adds its own features such as the no-code implementation of the repository pattern and the creation of database queries from the method name.
Mysql Driver − For the MySQL database driver.
With these five dependencies, we can set up our project now. Let’s cpck on the generate button. This will download our project as a zip file. We can extract it to a folder of our choice. Then we open the project in our IDE. We will be using Spring Tool Suite 4 for this. example.
Let’s load our project into STS. It will take a pttle time for our IDE to download the dependencies and vapdating them. Let’s take a look at our pom.xml file.
pom.xml
<?xml version="1.0" encoding="ISO-8859-1"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.tutorial.spring.security</groupId> <artifactId>formlogin</artifactId> <version>0.0.1-SNAPSHOT</version> <name>formlogin</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</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-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime<scope> <optional>true</optional> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test<artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
We can see that our project details along with our dependencies are enpsted here.
Data Source
We will configure our data source in the apppcation.properties file. As we will be using our local MySQL DB as the data source, so we provide the url, username, and password of our local DB instance here. We have named our database as “spring”.
spring.datasource.url=jdbc:mysql://localhost:3306/spring spring.datasource.username=root spring.datasource.password=root
Entities
Let’s create our entities now. We start with the User entity which contains three fields – username, password, and accountNonLocked. This User class also implements the UserDetails interface of Spring Security. This class provides core user information. It is used to store user data which can be later encapsulated into Authentication objects. It is not recommended to implement the interface directly. But for our case, since this is a simple apppcation to demonstrate the login with a database, we have implemented this interface directly here to keep thingssimple. We can implement this interface by using a wrapper class around our User entity.
User.java
package com.tutorial.spring.security.formlogin.model; import java.util.Collection; import java.util.List; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @Entity @Table(name = "users") pubpc class User implements UserDetails { /** * */ private static final long serialVersionUID = 1L; @Id private String username; private String password; @Column(name = "account_non_locked") private boolean accountNonLocked; pubpc User() { } pubpc User(String username, String password, boolean accountNonLocked) { this.username = username; this.password = password; this.accountNonLocked = accountNonLocked; } @Override pubpc Collection< extends GrantedAuthority> getAuthorities() { return List.of(() -> "read"); } @Override pubpc String getPassword() { return password; } pubpc void setPassword(String password) { this.password = password; } @Override pubpc String getUsername() { return username; } pubpc void setUsername(String username) { this.username = username; } @Override pubpc boolean isAccountNonExpired() { return true; } @Override pubpc boolean isAccountNonLocked() { return accountNonLocked; } @Override pubpc boolean isCredentialsNonExpired() { return true; } @Override pubpc boolean isEnabled() { return true; } pubpc void setAccountNonLocked(Boolean accountNonLocked) { this.accountNonLocked = accountNonLocked; } pubpc boolean getAccountNonLocked() { return accountNonLocked; } }
The accountNonLocked field is to be noted here. Every User in Spring Security has the account unlocked by default. To override that property and to lock the users out of their accounts once they exceed the permissible number of attempts, we shall be using this property. If the user exceeds the number of invapd attempts permissible, we shall use this property to lock him out of his account. Also, during every authentication attempt, we shall be checking this property with the isAccountNonLocked() method along with the credentials to authenticate the user. Any user with a locked account will not be allowed to authenticate into the apppcation.
For the other methods of the UserDetails interface, we can simply provide an implementation that returns true for now as we shall not be exploring these properties for this apppcation.
For the pst of authorities for this user, let’s assign him a dummy role for now. We shall not be using this property either for this apppcation.
Attempts.java
Moving on, let’s create our Attempts entity to persist our invapd attempts count. As created in the database, we will have the three fields here – username, an integer named attempts to keep counts of the number of attempts, and an identifier.
package com.tutorial.spring.security.formlogin.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity pubpc class Attempts { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private int id; private String username; private int attempts; /** * @return the id */ pubpc int getId() { return id; } /** * @param id the id to set */ pubpc void setId(int id) { this.id = id; } /** * @return the username */ pubpc String getUsername() { return username; } /** * @param username the username to set */ pubpc void setUsername(String username) { this.username = username; } /** * @return the attempts */ pubpc int getAttempts() { return attempts; } /** * @param attempts the attempts to set */ pubpc void setAttempts(int attempts) { this.attempts = attempts; } }
Repositories
We have created the entities, let’s create the repositories to store and retrieve data. We will have two repositories, one for each entity class. For both the repository interfaces, we will extend the JpaRepository which provides us with in-built implementations to save and retrieve data from the database configured in our apppcation.properties file. We can also add our methods or queries here in addition to the provided ones.
UserRepository.java
package com.tutorial.spring.security.formlogin.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.tutorial.spring.security.formlogin.model.User; @Repository pubpc interface UserRepository extends JpaRepository<User, String> { Optional<User> findUserByUsername(String username); }
As discussed, we have added our method to retrieve a user by username here. This will return our user details including username, password and account locked status.
AttemptsRepository.java
package com.tutorial.spring.security.formlogin.repository; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.tutorial.spring.security.formlogin.model.Attempts; @Repository pubpc interface AttemptsRepository extends JpaRepository<Attempts, Integer> { Optional<Attempts> findAttemptsByUsername(String username); }
Similarly, for the Attempts, in our AttemptsRepository, we have added a custom method findAttemptsByUsername(String username) to get data about user attempts using the username. This will return us an Attempts object with the username and the number of failed authentication attempts the user has made.
Configuration
Since we are going to use a custom login form, we have to override the default configuration of Spring Security. To do this we create our configuration class which extends the WebSecurityConfigurerAdapter class of Spring Security.
package com.tutorial.spring.security.formlogin.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration pubpc class ApppcationConfig extends WebSecurityConfigurerAdapter { @Bean pubpc PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeRequests().antMatchers("/register**") .permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .invapdateHttpSession(true) .clearAuthentication(true) .permitAll(); } }
Here we did two things−
First, we have specified the implementation of the PasswordEncoder interface that we are going to use. We have used an instance of BCryptPasswordEncoder to encode our passwords for this example. The PasswordEncoder interface has many implementations and we can use any of them. We have chosen BCryptPasswordEncoder here as it the most commonly used implementation. It uses the very strong BCrypt hashing algorithm to encode the passwords. It does so by incorporating a salt to protect against rainbow table attacks. In addition to this, bcrypt is an adaptive function: over time, the iteration count can be increased to make it slower, so it remains resistant to brute-force search attacks even with increasing computation power.
Secondly, we have overridden the configure() method to provide our implementation of the login method.
Whenever we use a custom form for authentication in place of the one provided by Spring Security, we have to inform Spring Security of it using the formLogin() method.
We then also specify our login URL – /login. We will map the URL to our custom login page in our Controller later.
We have also specified that the endpoints starting with /register, /login and the logout page need not be protected. We did so using the permitAll() method. This allows everyone to access these endpoints. Other than these endpoints, all endpoints are to be authenticated(). That is to say, users must be logged in to access all the other endpoints.
On logout, we have specified that the session is to be invapdated and authentication stored in the apppcation’s SecurityContext be cleared.
Security Setup
Now, we will setup our authentication process. We are going to setup authentication using a database and locking of user accounts.
Let’s create our implementation of UserDetailsService first. As we have discussed before, we need to provide our custom implementation for authentication using a database. This is because, Spring Security, as we know, only provides an in-memory authentication implementation by default. Therefore, we need to override that implementation with our database based process. To do so, we need to override the loadUserByUsername() method of UserDetailsService.
UserDetailsService
package com.tutorial.spring.security.formlogin.security; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.provisioning.UserDetailsManager; import org.springframework.stereotype.Service; import com.tutorial.spring.security.formlogin.model.User; import com.tutorial.spring.security.formlogin.repository.UserRepository; @Service pubpc class SecurityUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override pubpc UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findUserByUsername(username) .orElseThrow(() -< new UsernameNotFoundException("User not present")); return user; } pubpc void createUser(UserDetails user) { userRepository.save((User) user); } }
As we can see here, we have implemented the loadUserByUsername() method here. Here we are fetching the user from our database using the UserRepository interface. If the user is not found it throws UsernameNotFoundException.
We also have a createUser() method. We will use this method to add users to our database who have registered in our apppcation using UserRepository.
Authentication Provider
We will now implement our custom authentication provider. It will implement the AuthenticationProvider interface. We have two methods here that we have to override and implement.
package com.tutorial.spring.security.formlogin.security; import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; import com.tutorial.spring.security.formlogin.model.Attempts; import com.tutorial.spring.security.formlogin.model.User; import com.tutorial.spring.security.formlogin.repository.AttemptsRepository; import com.tutorial.spring.security.formlogin.repository.UserRepository; @Component pubpc class AuthProvider implements AuthenticationProvider { private static final int ATTEMPTS_LIMIT = 3; @Autowired private SecurityUserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Autowired private AttemptsRepository attemptsRepository; @Autowired private UserRepository userRepository; @Override pubpc Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getName(); import com.tutorial.spring.security.formlogin.repository.UserRepository; @Component pubpc class AuthProvider implements AuthenticationProvider { private static final int ATTEMPTS_LIMIT = 3; @Autowired private SecurityUserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Autowired private AttemptsRepository attemptsRepository; @Autowired private UserRepository userRepository; @Override pubpc Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getName(); Optional<Attempts> userAttempts = attemptsRepository.findAttemptsByUsername(username); if (userAttempts.isPresent()) { Attempts attempts = userAttempts.get(); attempts.setAttempts(0); attemptsRepository.save(attempts); } } private void processFailedAttempts(String username, User user) { Optional<Attempts> userAttempts = attemptsRepository.findAttemptsByUsername(username); if (userAttempts.isEmpty()) { Attempts attempts = new Attempts(); attempts.setUsername(username); attempts.setAttempts(1); attemptsRepository.save(attempts); } else { Attempts attempts = userAttempts.get(); attempts.setAttempts(attempts.getAttempts() + 1); attemptsRepository.save(attempts); if (attempts.getAttempts() + 1 > ATTEMPTS_LIMIT) { user.setAccountNonLocked(false); userRepository.save(user); throw new LockedException("Too many invapd attempts. Account is locked!!"); } } } @Override pubpc boolean supports(Class<?> authentication) { return true; } }
authenticate() − This method returns a fully authenticated object including credentials on successful authentication. This object is then stored in the SecurityContext. To perform authentication we will use the loaduserByUsername() method of the SecurityUserDetailsService class of our Apppcation. Here we perform multiple things −
First, we extract the user credentials from the Authentication request object which is passed as a parameter to our function. This authentication object was prepared by the AuthenticationFilter class and passed down the AuthenticationProvider through the AuthenticationManager.
We also fetch the user details from the database using the loadUserByUsername() method.
Now, first, we check if the user account has been locked due to previous failed authentication attempts. If we find that the account is locked, we throw a LockedException, and the user will be unable to authenticate unless the account is unlocked again.
If the account is not locked, we match the provided password along with the one stored against the user in the database. This is done using the matches() method of the PasswordEncoder interface.
If the passwords match, and the account has not been locked by then, we return a fully authenticated object. Here we have used an instance UsernamePasswordAuthenticationToken class (as it is a username-password authentication) that implements Authentication. Meanwhile, we also reset the attempts counter to 0.
On the other hand, if the password doesn’t match, we check for a few conditions −
If it is the user’s first attempt, then, probably his name would not be in the database. We check for this using the method findAttemptsByUsername() from the AttemptsRepository.
If not found, we make an entry for the user in the database, with the number of attempts set to one.
If a user is found, then we increase the number of attempts by 1.
We then check against the maximum number of failed attempts allowed, using a constant value we defined earper.
If the number is more than the allowed number of attempts, then the user is locked our of the apppcation and a LockedException is thrown.
supports() − We also have the supports method that checks if our authentication type is supported by our AuthenticationProvider implementation class. It returns true, false, or null if it matches, doesn’t match, or if it can’t decide respectively. We have hardcoded it to be true for now.
Controller
Now let’s create our controller package. It will contain our HelloController class. Using this controller class we will map our views to the endpoints and serve those views when the respective endpoints are hit. We will also autowire the PasswordEncoder and the UserDetailsService classes in this component. These injected dependencies will be used in creating our user. Let’s now create our endpoints.
package com.tutorial.spring.security.formlogin.controller; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.LockedException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import com.tutorial.spring.security.formlogin.model.User; import com.tutorial.spring.security.formlogin.security.SecurityUserDetailsService; @Controller pubpc class HelloController { @Autowired private SecurityUserDetailsService userDetailsManager; @Autowired private PasswordEncoder passwordEncoder; @GetMapping("/") pubpc String index() { return "index"; } @GetMapping("/login") pubpc String login(HttpServletRequest request, HttpSession session) { session.setAttribute( "error", getErrorMessage(request, "SPRING_SECURITY_LAST_EXCEPTION") ); return "login"; } @GetMapping("/register") pubpc String register() { return "register"; } @PostMapping( value = "/register", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, produces = { MediaType.APPLICATION_ATOM_XML_VALUE, MediaType.APPLICATION_JSON_VALUE } ) pubpc void addUser(@RequestParam Map<String, String> body) { User user = new User(); user.setUsername(body.get("username")); user.setPassword(passwordEncoder.encode(body.get("password"))); user.setAccountNonLocked(true); userDetailsManager.createUser(user); } private String getErrorMessage(HttpServletRequest request, String key) { Exception exception = (Exception) request.getSession().getAttribute(key); String error = ""; if (exception instanceof BadCredentialsException) { error = "Invapd username and password!"; } else if (exception instanceof LockedException) { error = exception.getMessage(); } else { error = "Invapd username and password!"; } return error; } }
index ("/") – This endpoint will serve the index page of our apppcation. As we have configured earper, we shall be protecting this page and allow only authenticated users will be able to access this page.
login ("/login") – This will be used to serve our custom login page, as mentioned earper. Any unauthenticated user will be redirected to this endpoint for authentication.
register("/register") (GET) – We will have two “register” endpoints for our apppcation. One will be to serve the registration page. The other one will be to handle the registration process. So, the former one will use an Http GET and the latter will be a POST endpoint.
register("/register") (POST) – We will use this endpoint to handle the user registration process. We will get the user name and password from the parameters. Then we will encode the password using the passwordEncoder that we have @Autowired into this component. We also set user account as unlocked at this point. We will then save this user data in our users table with the createUser() method.
In addition to the above, we have the getErrorMessage() method. It is used to determine the last thrown exception to add a message in our login template. This way, we can be aware of authentication errors and display proper messages.
Resources
We have created our endpoints, the only thing left is to create our views.
First, we will create our index page. This page will be accessible to users only on successful authentication. This page has access to the Servlet request object using which we can display the user name of the logged in user.
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title> Hello World! </title> </head> <body> <h1 th:inpne="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1> <form th:action="@{/logout}" method="post"> <input type="submit" value="Sign Out"/> </form> </body> <html>
Next, we create our login view. This display our custom login form with the username and password fields. This view will also be rendered in case of a logout or failed authentication and will display appropriate messages for each case.
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org" xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> <head> <title>Spring Security Example</title> </head> <body> <span th:if="${param.error}"> <p th:text="${session.error}" th:unless="${session == null}">[...]</p> </span> <span th:if="${param.logout}">You have been logged out.</span> <form th:action="@{/login}" method="post> <span> <label> User Name : <input type="text" name="username" /> </label> </span> <span> <label> Password: <input type="password" name="password" /> </label> </span> <span> <input type="submit" value="Sign In" /> </span> </form> </body> </html>
Moving, we create our required view, the register view. This view will let users register themselves with the apppcation. This user data will be stored in the database which will then be used for authentication.
<!DOCTYPE html> <html> <head> <meta charset="ISO-8859-1"> <title>Insert title here</title> </head> <body> <form action="/register" method="post"> <span class="container"> <h1>Register</h1> <p>Please fill in this form to create an account.</p> <hr> <label for="username"> <b>Username</b> </label> <input type="text" placeholder="Enter Username" name="username" id="username" required> <label for="password"><b>Password</b></label> <input type="password" placeholder="Enter Password" name="password" id="password" required> <button type="submit" class="registerbtn">Register</button> </span> </form> </body> </html>
Final Project Structure
Our final project structure should look something similar to this.
Running the Apppcation
We can then run the apppcation as a SpringBootApp. When we go localhost:8080 on our browser it will redirect us back to the login page.
On successful authentication it will take us the index view with a greeting.
Since, we have allowed only three failed attempts before the account gets locked, so on the third failed authentication the user get locked and the message is displayed on the screen.
On hitting the /register endpoint we can also register a new user.
Conclusion
From today’s article, we have learned how to use a custom form for login using a database using an annotation-based configuration. We have also learned how to prevent multiple failed login attempts. While doing we have seen how we can implement our own AuthenticationProvider and UserDetailsService to authenticate users using our custom authentication process.
Spring Security - Form Login, Remember Me and Logout
Contents
Introduction and Overview
Getting Started (Practical Guide)
Introduction and Overview
Spring Security comes with a ton of built-in features and tools for our convenience. In this example, we are going to discuss three of those interesting and useful features −
Form-login
Remember Me
Logout
Form Login
Form-based login is one form of Username/password authentication that Spring Security provides support for. This is provided through an Html form.
Whenever a user requests a protected resource, Spring Security checks for the authentication of the request. If the request is not authenticated/authorized, the user will be redirected to the login page. The login page must be somehow rendered by the apppcation. Spring Security provides that login form by default.
Moreover, any other configuration, if needed, must be exppcitly provided as given below −
protected void configure(HttpSecurity http) throws Exception { http // ... .formLogin( form -> form .loginPage("/login") .permitAll() ); }
This code requires a login.html file to be present in the templates folder which would be returned on hitting the /login. This HTML file should contain a login form. Furthermore, the request should be a post request to /login. The parameter names should be “username” and “password” for username and password respectively. In addition to this, a CSRF Token also needs to be included with the form.
The above code snippet will be clearer once we are done with code exercise.
Remember Me
This type of authentication requires a remember-me cookie to be sent to the browser. This cookie stores user information/authentication principal and it is stored in the browser. So, the website can remember the identity of the user next time when the session is started. Spring Security has the necessary implementations in place for this operation. One uses hashing to preserve the security of cookie-based tokens while the other uses a database or other persistent storage mechanism to store the generated tokens.
Logout
The default URL /logout logs the user out by−
Invapdating the HTTP Session
Cleaning up any RememberMe authentication that was configured
Clearing the SecurityContextHolder
Redirect to /login?logout
WebSecurityConfigurerAdapter automatically apppes logout capabipties to the Spring Boot apppcation.
Getting Started (Practical Guide) As usual, we shall start by going to start.spring.io. Here we choose a maven project. We name the project “formlogin” and choose the desired Java version. I am choosing Java 8 for this example. We also go on to add the following dependencies −
Spring Web
Spring Security
Spring Boot DevTools
is a templating engine for Java. It allows us to quickly develop static or dynamic web pages for rendering in the browser. It is extremely extensible and allows us to define and customize the processing of our templates in fine detail. In addition to this, we can learn more about Thymeleaf by cpcking this .
Let’s move on to generate our project and download it. We then extract it to a folder of our choice and use any IDE to open it. I shall be using
. It is available for free downloading from the website and is optimized for spring apppcations.Let’s take a look at our pom.xml file. It should look something similar to this −
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.1.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId> com.spring.security</groupId> <artifactId>formlogin</artifactId> <version>0.0.1-SNAPSHOT</version> <name>formlogin</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Let’s create a package in our folder /src/main/java under the default package. We shall be naming it as config as we would place all our configuration classes here. So, the name should look something similar to this – com.tutorial.spring.security.formlogin.config.
The Configuration Class
package com.tutorial.spring.security.formlogin.config; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.provisioning.UserDetailsManager; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import com.spring.security.formlogin.AuthFilter; @Configuration pubpc class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean protected UserDetailsService userDetailsService() { UserDetailsManager userDetailsManager = new InMemoryUserDetailsManager(); UserDetails user = User.withUsername("abby") .password(passwordEncoder().encode("12345")) .authorities("read") .build(); userDetailsManager.createUser(user); return userDetailsManager; } @Bean pubpc PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests().anyRequest() .authenticated() .and() .formLogin() .and() .rememberMe() .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/login") .deleteCookies("remember-me"); } }
Code Breakdown
Inside of our config package, we have created the WebSecurityConfig class. This class extends the WebSecurityConfigurerAdapter of Spring Security. We shall be using this class for our security configurations, so let’s annotate it with an @Configuration annotation. As a result, Spring Security knows to treat this class a configuration class. As we can see, configuring apppcations have been made very easy by Spring.
Let’s take a look at our configuration class.
First, we shall create a bean of our UserDetailsService class by using the userDetailsService() method. We shall be using this bean for managing our users for this apppcation. Here, to keep things simple, we shall use an InMemoryUserDetailsManager instance to create a user. This user, along with our given username and password, will contain a simple “read” authority.
Now, let’s look at our PasswordEncoder. We shall be using a BCryptPasswordEncoder instance for this example. Hence, while creating the user, we used the passwordEncoder to encode our plaintext password pke this
.password(passwordEncoder().encode("12345"))
After the above steps, we move on to our next configuration. Here, we override the configure method of WebSecurityConfigurerAdapter class. This method takes HttpSecurity as a parameter. We shall be configuring this to use our form login and logout, as well as a remember-me function.
Http Security Configuration
We can observe that all these functionapties are available in Spring Security. Let’s study the below section in detail −
http.csrf().disable() .authorizeRequests().anyRequest().authenticated() .and() .formLogin() .and() .rememberMe() .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/login") .deleteCookies("remember-me");
There are a few points to note here −
We have disabled
or protection As this is a simple apppcation only for demonstration purposes, we can safely disable this for now.Then we add configuration which requires all requests to be authenticated. As we shall see later, we will have a single “/” endpoint for the index page of this apppcation, for simppcity.
After that, we shall be using the formLogin() functionapty of Spring Security as mentioned above. This generates a simple login page.
Then, we use the rememberMe() functionapty of Spring Security. This will perform two things.
Firstly, it will add a “Remember Me” checkbox to our default login form that we generated using formLogin().
And, secondly, ticking the checkbox generates the remember-me cookie. The cookie stores the identity of the user and the browser stores it. Spring Security detects the cookie in future sessions to automate the login.
As a result, the user can access the apppcation again without logging in again.
And lastly, we have the logout() functionapty. For this too, a default functionapty has been provided by Spring security. Here it performs two important functions −
Invapdates the Http session, and unbinds objects bound to the session.
It clears the remember-me cookie.
Removes the authentication from Spring’s Security context.
We also, provided a logoutSuccessUrl(), so that the apppcation comes back to the login page after logout. This completes our apppcation configuration.
The Protected Content (Optional)
We shall now create a dummy index page now for the user to view when he logs in. It will also contain a logout button.
In our /src/main/resources/templates, we add a index.html file.Then add some Html content to it.
<!doctype html> <html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <pnk rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous"> <title>Hello, world!</title> </head> <body> <h1>Hello, world!</h1> <a href="logout">logout</a> <!-- Optional JavaScript --> <!-- jQuery first, then Popper.js, then Bootstrap JS --> <script src="https://code.jquery.com/jquery-3.5.1.spm.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://cdn.jsdepvr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script> </body> </html>
This content is from
.We also add
<a href="logout">logout</a>
to our file, so as the user can log out of the apppcation using this pnk.
The Resource Controller
We have created the protected resource, we now add the controller to serve this resource.
package com.tutorial.spring.security.formlogin.controllers; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller pubpc class AuthController { @GetMapping("/") pubpc String home() { return "index"; } }
As we can see, it is a very simple controller. It only has a get endpoint which serves our index.html file when the start our apppcation.
Running the apppcation
Let’s run the apppcation as a Spring Boot Apppcation. We can go to http://localhost:8080 on our browser when the apppcation starts. It should ask us for username and password. Additionally, we shall also be able to see the remember-me checkbox.
Login Page
Now, if we provide the user information as we had configured in our WebSecurity config file, we shall be able to log in. Also, if we tick the remember-me checkbox, we shall be able to see the remember-me cookie in our browser’s developer tools section.
As we can see the cookie is sent along with our login request.
Also, included in the web page is a pnk for log out. On cpcking the pnk, we shall be logged out of our apppcation and sent back to our login page.
Spring Security - Tagpb
Contents
Introduction and Overview
Spring Security Tags
The authorize Tag
The authentication tag
The csrfInput Tag
The csrfMetaTags Tag
Getting Started (Practical Guide)
Introduction and Overview
In Spring MVC apppcations using JSP, we can use the Spring Security tags for applying security constraints as well as for accessing security information. Spring Security Tag pbrary provides basic support for such operations. Using such tags, we can control the information displayed to the user based on his roles or permissions. Also, we can include CSRF protection features in our forms.
To use Spring security tags, we must have the security tagpb declared in our JSP file.
<%@ tagpb prefix="sec" uri="http://www.springframework.org/security/tags" %>
Now, we can use Spring Security tags with the “sec” prefix. Let’s now see the usage of the tags.
The authorize Tag
The first tag we will be discussing is the authorize tag. Let’s check out some usage examples.
<sec:authorize access="!isAuthenticated()"> Login </sec:authorize> <sec:authorize access="isAuthenticated()"> Logout </sec:authorize> <sec:authorize access="hasRole( ADMIN )"> Hello Admin. </sec:authorize>
As we can see, we can use this tag to hide or show sections of information based on access or roles. To evaluate roles or access we also use the following
−hasRole(“ADMIN”) − evaluates to true if the current user has the admin role.
hasAnyRole(‘ADMIN’,’USER’) − evaluates to true if the current user has any of the psted roles
isAnonymous() − evaluates to true if the current user is an anonymous user
isRememberMe() − evaluates to true if the current user is a remember-me user
isFullyAuthenticated() − evaluates to true if the user is authenticated and is neither anonymous nor a remember-me user
As we can see, the access attribute is where the web-security expression is specified. Then, Spring Security evaluates the expression The evaluation is generally delegated to SecurityExpressionHandler<FilterInvocation>, which is defined in the apppcation context. If it returns true, then the user can get access to the information given in that section.
If we use the authorize tag with Spring Security ‘s Permission Evaluator, we can also check user permissions as given below −
<sec:authorize access="hasPermission(#domain, read ) or hasPermission(#domain, write )"> This content is visible to users who have read or write permission. </sec:authorize>
We can also allow or restrict the user from cpcking on certain pnks within our content.
<sec:authorize url="/admin"> This content will only be visible to users who are authorized to send requests to the "/admin" URL. </sec:authorize>
The authentication tag
When we want access to the current Authentication object stored in the Spring Security Context, we can use the authentication tag. Then we can use it to render properties of the object directly in our JSP page. For example, if we want to render the principal property of the Authentication object in our page, we can do it as follows −
<sec:authentication property="principal.username" />
The csrfInput Tag
We can use the csrfInput tag to insert a hidden form field with the correct values for the CSRF protection token when CSRF protection is enabled. If CSRF protection is not enabled, this tag outputs nothing.
We can place the tag within the HTML <form></form> block along with other input fields. However, we must not place the tag within the <form:form></form:form> block as Spring Security automatically inserts a CSRF form field within those tags and also takes care of Spring forms automatically.
<form method="post" action="/do/something"> <sec:csrfInput /> Username:<br /> <input type="text" username="username" /> ... </form>
The csrfMetaTags Tag
We can use this tag to insert meta tags which contain the CSRF protection token form field and header names and CSRF protection token value. These meta tags can be useful for employing CSRF protection within Javascript in our apppcation. However, this tag only works when we have enabled CSRF protection in our apppcation, otherwise, this tag outputs nothing.
<html> <head> <title>CSRF Protection in Javascript</title> <sec:csrfMetaTags /> <script type="text/javascript" language="javascript"> var csrfParam = $("meta[name= _csrf_param ]").attr("content"); var csrfToken = $("meta[name= _csrf ]").attr("content"); </script> </head> <body> ... </body> </html>
Getting Started (Practical Guide)
Now that we have discussed the tags, let’s build an apppcation to demonstrate the usage of the tags. We shall be using Spring Tool Suite 4 as our IDE. Additionally, we shall be using the Apache Tomcat server to serve our apppcation. So, let’s get started.
Setting up the Apppcation
Let’s create a simple Maven Project in STS. We can name our apppcation as tagpbsdemo, and package it as a .war file.
When we have finished setting up our apppcation it should have a structure similar to this.
The pom.xml file
We shall add these following dependencies to our apppcation −
Spring Web MVC
Spring-Security-Web
Spring-Security-Core
Spring-Security-Tagpbs
Spring-Security-Config
Javax Servlet Api
JSTL
After adding these dependencies, our pom.xml should look similar to this −
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.tutorial.spring.security</groupId> <artifactId>tagpbsdemo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <properties> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.source>1.8</maven.compiler.source> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>5.0.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>5.0.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-tagpbs</artifactId> <version>5.0.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>5.0.4.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.6</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build> </project>
Let’s create our base package for the apppcation. We can name it com.tagpbsdemo. Within the package, let’s create another package for our configuration files. Since, it will be holding the configuration files, we can name it config.
ApppcationConfig.java
Let’s create our first configuration class ApppcationConfig.java.
package com.tagpbsdemo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.JstlView; @EnableWebMvc @Configuration @ComponentScan({ "com.tagpbsdemo.controller"} ) pubpc class ApppcationConfig { @Bean pubpc InternalResourceViewResolver viewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setViewClass(JstlView.class); viewResolver.setPrefix("/WEB-INF/views/"); viewResolver.setSuffix(".jsp"); return viewResolver; } }
Let’s break down the code here −
− We use @EnableWebMvc to enable Spring MVC. So, we add this annotation to an @Configuration class to import the Spring MVC configuration from . WebMvcConfigurationSupport is the main class that provides the configuration for the MVC Java config. Not using this annotation may result in things pke content-type and accept header, generally content negotiation not working. registers a RequestMappingHandlerMapping, a RequestMappingHandlerAdapter, and an ExceptionHandlerExceptionResolver among others in support of processing requests with annotated controller methods using annotations such as @RequestMapping , @ExceptionHandler, and others.
@Configuration − This annotation indicates that the class declares one or more @Bean methods and may be processed by the Spring IoC container to generate bean definitions and service requests for those beans at runtime. A @Configuration class is typically bootstrapped using either AnnotationConfigApppcationContext or its web-capable variant, AnnotationConfigWebApppcationContext.
@ComponentScan − @ComponentScan annotation is used to tell Spring the packages to scan for annotated components. @ComponentScan also used to specify base packages and base package classes using thebasePackageClasses or basePackages attributes of @ComponentScan.
InternalResourceViewResolver − To resolve the provided URI to the actual URI in the format prefix + viewname + suffix.
setViewClass() − To set the view class that should be used to create views.
setPrefix() − To set the prefix that gets prepended to view names when building a URL.
setSuffix() − To set the suffix that gets appended to view names when building a URL.
WebSecurityConfig.java
Next we shall create our WebSecurityConfig class which will extend the famipar WebSecurityConfigurerAdapter class of Spring Security.
package com.tagpbsdemo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; 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.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.User.UserBuilder; @EnableWebSecurity @ComponentScan("com.tagpbsdemo") pubpc class WebSecurityConfig extends WebSecurityConfigurerAdapter { @SuppressWarnings("deprecation") @Bean pubpc UserDetailsService userdetailsService() { UserBuilder users = User.withDefaultPasswordEncoder(); InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(users.username("rony").password("rony123").roles("USER").build()); manager.createUser(users.username("admin").password("admin123").roles("ADMIN").build()); return manager; } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/index", "/").permitAll() .antMatchers("/admin", "/user").authenticated() .and() .formLogin() .and() .logout() .logoutRequestMatcher( new AntPathRequestMatcher("/logout") ); } }
Let’s break the code down here −
WebSecurityConfigurerAdapter − The abstract class that implements WebSecurityConfigurer WebSecurityConfigurer and allows us to override methods for security configuration.
@EnableWebSecurity − It enables Spring to automatically find and apply the @Configuration class to the global WebSecurity.
We then create a UserDetailsService Bean using the method to create users using the InMemoryUserDetailsManager instance. We create two users – one with role “USER” and another with role “ADMIN” and add them to Spring Security.
After that, we override the configure method with HttpSecurity as a parameter. We make our home page or index page accessible to all and admin page to be accessible when the user is authenticated. Next, we add Spring Security form login and logout.
So, with those steps our security configuration is complete. Now, we are ready to move on to the next step.
SpringSecurityApppcationInitiapzer.java
Moving on, now we shall create the SpringSecurityApppcationInitiapzer.java class which extends the AbstractSecurityWebApppcationInitiapzer class of Spring Security.
package com.tagpbsdemo.config; import org.springframework.security.web.context.AbstractSecurityWebApppcationInitiapzer; pubpc class SpringSecurityApppcationInitiapzer extends AbstractSecurityWebApppcationInitiapzer { }
AbstractSecurityWebApppcationInitiapzer is an abstract class that implements Spring’s WebApppcationInitiapzer. So, SpringServletContainerInitiapzer will initiapze the concrete implementations of this class if the classpath contains spring-web module.
MvcWebApppcationInitiapzer.java
package com.tagpbsdemo.config; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitiapzer; pubpc class MvcWebApppcationInitiapzer extends AbstractAnnotationConfigDispatcherServletInitiapzer { @Override protected Class</?>[] getRootConfigClasses() { return new Class[] {WebSecurityConfig.class}; } @Override protected Class</?>[] getServletConfigClasses() { return null; } @Override protected String[] getServletMappings() { return new String[] {"/"}; } }
AbstractAnnotationConfigDispatcherServletInitiapzer − This class extends WebApppcationInitiapzer. We need this class as a base class for initiapzing a Spring apppcation in Servlet container environment.As a result, the subclass of AbstractAnnotationConfigDispatcherServletInitiapzer will provide the classes annotated with @Configuration, Servlet config classes and DispatcherServlet mapping pattern.
getRootConfigClasses() − This method must be implemented by the class extending AbstractAnnotationConfigDispatcherServletInitiapzer. It provides “root” apppcation context configuration.
getServletConfigClasses() − This method too, must be implemented to provide DispatcherServlet apppcation context configuration.
getServletMappings() − This method is used specify the servlet mapping(s) for the DispatcherServlet.
We have set up the configuration classes. Now , we shall create our controller to serve the JSP pages.
HelloController.java
package com.tagpbsdemo.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller pubpc class HelloController { @GetMapping("/") pubpc String index() { return "index"; } @GetMapping("/user") pubpc String user() { return "admin"; } @GetMapping("/admin") pubpc String admin() { return "admin"; } }
Here, we have created three endpoints – “/”, “/user”, and “/admin”. As specified in our configuration previously, we will allow unauthorized access to the index page
“/”. On the other hand, the “/user” and “/admin” endpoints would be authorized only access.
Secure Content to serve
Moving on, we shall now create the JSP pages which are to be served on hitting the specific endpoints.
For this, inside our src/main folder we create a folder called webapp. Inside this folder, we create our WEB-INF folder and further as in ApppcationConfig.java class we add the views folder. Here, in this folder we shall be adding the views.
Let’s add our home page, i.e., index.jsp first.
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE html> <html> <head> <meta charset="ISO-8859-1"> <title>Home Page</title> </head> <body> <a href="user">User</a> <a href="admin">Admin</a> <br> <br> Welcome to the Apppcation! </body> </html>
Then we shall create our admin.jsp file. Let’s add it.
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ tagpb uri="http://www.springframework.org/security/tags" prefix="security"%> <!DOCTYPE html> <html> <head> <meta charset="ISO-8859-1"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> Welcome to Admin Page! <a href="logout"> Logout </a> <br> <br> <security:authorize access="hasRole( ADMIN )"> Hello Admin! </security:authorize> </body> </html>
here, we have added <%@ tagpb uri="http://www.springframework.org/security/tags" prefix="security"%>. This is going to let us the Spring security tag pbs as discussed before. As we can see, we have the added the “authorize” tag around the content. This content is will be only accessible by our admin. Any other user accessing this page will not be able to view this content.
Running the apppcation
We now right cpck on the project and choose Run On Server. When the server starts and our apppcation is running we can go to localhost:8080/tagpbsdemo/ on our browser to view the page.
Login page
Now, if we cpck on the User pnk in our apppcation, we shall be asked to log in.
Here, as we can see in our controller, we are serving the admin page for bothe the user and admin pnks. But our user, if he is not an admin cannot view the content which is protected by our “authorize”tag.
Let’s log in as the user first.
We can see that the “Hello Admin!” content is not visible to us. This is because the current user doesn’t have the admin role.
Let’s logout and log in as admin now.
We are now able to see the protected content “Hello Admin!” as the current user has the admin role.
Conclusion
We have learnt how we can use the Spring Security tag pbrary to protect our content and get access to the current Authentication object in Our Spring Security Context.
Spring Security - XML Configuration
Contents
Fundamentals
Getting started (Practical Guide)
Fundamentals
In this we are going to discuss how to configure Spring Security with XML configuration. We shall be developing a simple Spring apppcation with Spring Security. While doing so, we will discuss in details about each component that we are using.
Authentication and Authorization
Authentication − Authentication is ensuring the user or the cpent is who they claim to be. There are many ways in which Spring Security enables us to perform authentication. Spring Security supports Basic Authentication, LDAP authentication, JDBC authentication, etc.
Authorization − Ensuring if the user has permission for the action. If our apppcation is a complex one, with different kinds of users such as admins, regular users, other less privileged users, we need to maintain access control in our apppcation. For example, a guest user should not be able to access admin content. So, to control access to various resources within our apppcation, we need to check if a user has permission to access that resource.
The above topics are the two main components of Spring Security. Spring security provided us with various in-built features to implement authentication and authorization in our apppcation. We can use these features with our changes to secure an apppcation very quickly. In addition to this, Spring Security also allows plenty of customizations to the features mentioned before to implement our own complex authentications and authorizations.
Getting Started (Practical Guide)
Let’s look at a basic example using in-built Spring Security features. In this example, we shall be securing our apppcation with options provided out-of-box by Spring security. This is will give us an idea of the various components of Spring Security and how we can use them for our apppcation. We shall be using XML to configure our apppcation’s Security features.
The tools we shall be using for our apppcation will be
and . They are both available for free download and use.First, let’s start a new simple Maven Project in STS. We can choose group id, artifact id as per our choice. After that, we cpck on Finish. As a result, we have added our project to our workspace. Let’s give STS some time to build and vapdate our project.
Our project structure would finally look similar to this.
Next, let’s add the dependencies. We are going to choose the following dependencies.
Spring Web MVC
Spring-Security-Web
Spring-Security-Core
Spring-Security-Config
Javax Servlet API
pom.xml
With these dependencies added, we are ready to configure our project. Let’s take a look at our pom.xml file.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd> <modelVersion>4.0.0</modelVersion> <groupId>com.tutorial.spring.security</groupId> <artifactId>xmlconfigurationdemo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <name>Spring Security with XML configuration</name> <description>Spring Security with XML configuration</description> <properties> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.source>1.8</maven.compiler.source> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.0.2.RELEASE<version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>5.0.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>5.0.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>5.0.0.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.6</version> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build> </project>
Controller and views
First, We are going to create our controller. So, let’s create a package called controller and add our HomeController class to the package.
package com.tutorial.spring.security.xmlconfigurationdemo.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller pubpc class HomeController { @GetMapping("/") pubpc String index() { return "index"; } @GetMapping("/admin") pubpc String admin() { return "admin"; } }
Here, we have two endpoints – “index” and “admin”. While the index page is will be accessible to all, we will protect our “admin” page.
Since, we have created the routes, let’s also add the pages.
In our /src/main/webapp folder, let’s create a folder called WEB-INF. Then inside it, we will create a folder called views where we will create our views.
Let’s create our first view−
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <!DOCTYPE html> <html> <head> <meta charset="ISO-8859-1"> <title>Insert title here</title> </head> <body> <h2>Welcome to Spring Security!</h2> </body> </html>
Then we create our admin view.
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <DOCTYPE html> <html> <head> <meta charset="ISO-8859-1"> <title>Insert title here</title> </head> <body> Hello Admin </body> </html>
Moving on, let’s configure our apppcation.
Configurations.
web.xml
Now, let’s add our first xml file – the web.xml file.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE xml> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/app-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <pstener> <pstener-class>org.springframework.web.context.ContextLoaderListener</pstener-class> </pstener> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/security-config.xml </param-value> </context-param> <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
Code breakdown
Dispatcher Servlet − The first servlet we have declared here is the Dispatcher servlet. The dispatcher servlet is the entry point of any Spring MVC apppcation and is at the core of the entire Spring MVC framework design. It intercepts all HTTP requests and dispatches them to registered handlers for processing a web request. It also provides convenient mapping and exception handpng facipties. The order in which servlets are loaded depends on “load-on-startup” value. Servlets with a lower value of “load-on-startup” are loaded before the ones with a higher value.
contextConfigLocation − It is a string that indicates where context(s) can be found. This string represents a path to a file where our configurations can be loaded.
servlet-mapping − We use Servlet Mapping to tell Spring Container which request to route to which servlet. In our case, we are routing all our requests to our “spring” Dispatcher servlet.
pstener − The classes that psten to certain types of events, and trigger an appropriate functionapty when that event occurs. Each pstener is bound to an event. In our case, we will create a root web-apppcation context for the web-apppcation with the ContextLoaderListener. This is then put in the ServletContext that can be used to load and unload the spring-managed beans.
filter − Spring uses Filters to process requests before handing them over to the Dispatcher Servlet and also used to process responses after they are dispatched. The DelegatingFilterProxy pnks the apppcation context to the web.xml file. The requests that are coming to this apppcation will pass through our filter which we named “spring SecurityFilterChain” before they reach their controllers. This is where Spring Security can take over the request and perform operations on it before passing it on to the next set of filters or handlers.
security-config.xml
Next we will create our security-config.xml file.
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <http auto-config="true"> <intercept-url pattern="/admin" access="hasRole( ROLE_ADMIN )" /> </http> <authentication-manager> <authentication-provider> <user-service> <user name="admin" password="{noop}1234" authorities="ROLE_ADMIN" /> </user-service> </authentication-provider> </authentication-manager> <beans:bean id ="passwordEncoder" class = "org.springframework.security.crypto.password.NoOpPasswordEncoder" factory-method = "getInstance"> </beans:bean> </beans:beans>
Code breakdown
http element − The parent of all web-related namespace functionapty. Here, we can configure which URLs to intercept, what permissions are required, which type of login to use, and all such configuration.
auto-config − Setting this attribute to true automatically sets up form-login, basic login, and logout functionapties. Spring Security generates them by using standard values and the features enabled.
intercept-url − It sets the pattern of the URLs that we want to protecte, using the access attribute.
access − It specifies which users are permitted to access the URL specified by the pattern attribute. It is done on the basis of the roles and permissions of a user. We can use SPEL with this attribute.
authentication-manager − The <authentication-manager> is used to configure users, their passwords, and roles in the apppcation. These users will be one who can access the protected parts of the apppcation given they have the appropriate roles. A DaoAuthenticationProvider bean will be created by the <authentication-provider< and the <user-service< element will create an InMemoryDaoImpl. All authentication-provider elements will allow the users to be authenticated by providing the user information to the authentication-manager.
password-encoder − This will register a password encoder bean. To keep things simple here we have used the NoOpPasswordEncoder.
Moving on we create out last configuration file – the app-config file. Here we are going to add our view resolver code and define our base package.
app-config.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <mvc:annotation-driven /> <context:component-scan base-package="com.tutorial.spring.security.xmlconfigurationdemo.controller"> </context:component-scan> <context:annotation-config> </context:annotation-config> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"></property> <property name="suffix" value=".jsp"></property> </bean> </beans>
Here, as we can see we are registering our views that we created earper. For this, we are using the InternalResourceViewResolver class which will map the provided URI to the actual URI.
For example, using the above configuration, if we request the URI “/admin“, DispatcherServlet will forward the request to the
prefix + viewname + suffix = /WEB-INF/views/admin.jsp view.
Running the apppcation
With this simple configuration, we have our apppcation ready to be served. We can right-cpck on the project and choose Run on Server. We can choose our Tomcat server. When the server starts, we can go to localhost:8080/xmlconfigurationdemo to interact with our apppcation.
If, we enter the correct credentials we shall be able to login and see our desired content.
Spring Security - OAuth2
Contents
OAuth2.0 Fundamentals
OAuth2.0 Getting started(Practical Guide)
OAuth 2.0 Fundamentals
OAuth 2.0 was developed by IETF OAuth Working Group and pubpshed in October of 2012. It serves as an open authorization protocol for enabpng a third party apppcation to get pmited access to an HTTP service on behalf of the resource owner. It can do so while not reveapng the identity or the long-term credentials of the user. A third-party apppcation itself can also use it on its behalf. The working principle of OAuth consists of the delegation of user authentication to a service hosting the user account and authorizing the third-party apppcation access to the account of the user.
Let us consider an example. Let us say we want to login to a website “cpentsite.com”. We can sign in via Facebook, Github, Google or Microsoft. We select any options of the options given above, and we are redirected to the respective website for login. If login is successful, we are asked if we want to give cpentsite.com access to the specific data requested by it. We select our desired option and we are redirected to cpentsite.com with an authorization code or error code and our login is successful or not depending on our action in the third-party resource. This is the basic working principle of OAuth 2.
There are five key actors involved in an OAuth system. Let’s pst them out −
User / Resource Owner − The end-user, who is responsible for the authentication and for providing consent to share resources with the cpent.
User-Agent − The browser used by the User.
Cpent − The apppcation requesting an access token.
Authorization Server − The server that is used to authenticate the user/cpent. It issues access tokens and tracks them throughout their pfetime.
Resource Server − The API that provides access to the requested resource. It vapdates the access tokens and provides authorization.
Getting Started
We will be developing a Spring Boot Apppcation with Spring Security and OAuth 2.0 to illustrate the above. We will be developing a basic apppcation with an in-memory database to store user credentials now. The apppcation will make it easy for us to understand the workings of OAuth 2.0 with Spring Security.
Let’s use the Spring initiapzer to create a maven project in Java 8. Let’s start by going to start.spring.io. We generate an apppcation with the following dependencies−
Spring Web
Spring Security
Cloud OAuth2
Spring Boot Devtools
With the above configuration, we cpck on the Generate button to generate a project. The project will be downloaded in a zip file. We extract the zip to a folder. We can then open the project in an IDE of our choice. I am using Spring Tools Suite here as it is optimized for spring apppcations. We can also use Ecppse or IntelpJ Idea as we wish.
So, we open the project in STS, let the dependencies get downloaded. Then we can see the project structure in our package explorer window. It should resemble the screenshot below.
If we open the pom.xml file we can view the dependencies and other details related to the project. It should look something pke this.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.tutorial</groupId> <artifactId>spring.security.oauth2</artifactId> <version>0.0.1-SNAPSHOT</version> <name>spring.security.oauth2</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR6</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot<groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot<groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> <dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement><build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Now, to the base package of our apppcation, i.e., com.tutorial.spring.security.oauth2, let’s add a new package named config where we shall add our configuration classes.
Let’s create our first configuration class, UserConfig which extends the WebSecurityConfigurerAdapter class of Spring Security to manage the users of the cpent apppcation. We annotate the class with @Configuration annotation to tell Spring that it is a configuration class.
package com.tutorial.spring.security.oauth2.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.provisioning.UserDetailsManager; @Configuration pubpc class UserConfig extends WebSecurityConfigurerAdapter { @Bean pubpc UserDetailsService userDetailsService() { UserDetailsManager userDetailsManager = new InMemoryUserDetailsManager(); UserDetails user = User.withUsername("john") .password("12345") .authorities("read") .build(); userDetailsManager.createUser(user); return userDetailsManager; } @Bean pubpc PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Override @Bean pubpc AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
We then add a bean of the UserDetailsService to retrieve the user details for authentication and authorization. To put it in the Spring context we annotate it with @Bean. To keep this tutorial simple and easy to understand, we use an InMemoryUserDetailsManager instance. For a real-world apppcation, we can use other implementations pke JdbcUserDetailsManager to connect to a database and so on. To be able to create users easily for this example we use the UserDetailsManager interface which extends the UserDetailsService and has methods pke createUser(), updateUser() and so on. Then, we create a user using the builder class. We give him a username, password and a “read” authority for now. Then, using the createUser() method, we add the newly created user and return the instance of UserDetailsManager thus putting it in the Spring context.
To be able to use the UserDetailsService defined by us, it is necessary to provide a PasswordEncoder bean in the Spring context. Again, to keep it simple for now we use the NoOpPasswordEncoder. The NoOpPasswordEncoder should not be used otherwise for real-world apppcations for production as it is not secure. NoOpPasswordEncoder does not encode the password and is only useful for developing or testing scenarios or proof of concepts. We should always use the other highly secure options provided by Spring Security, the most popular of which is the BCryptPasswordEncoder, which we will be using later in our series of tutorials. To put it in the Spring context we annotate the method with @Bean.
We then override the AuthenticationManager bean method of WebSecurityConfigurerAdapter, which returns the authenticationManagerBean to put the authentication manager into the Spring context.
Now, to add the cpent configurations we add a new configuration class named AuthorizationServerConfig which extends AuthorizationServerConfigurerAdapter class of Spring Security. The AuthorizationServerConfigurerAdapter class is used to configure the authorization server using the spring security oauth2 module. We annotate this class with @Configuration as well. To add the authorization server functionapty to this class we need to add the @EnableAuthorizationServer annotation so that the apppcation can behave as an authorization server.
package com.tutorial.spring.security.oauth2.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.oauth2.config.annotation.configurers.CpentDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; @Configuration @EnableAuthorizationServer pubpc class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Override pubpc void configure(CpentDetailsServiceConfigurer cpents) throws Exception { cpents.inMemory() .withCpent("oauthcpent1") .secret("oauthsecret1") .scopes("read") .authorizedGrantTypes("password") } @Override pubpc void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager); } }
For checking oauth tokens, Spring Security oauth exposes two endpoints – /oauth/check_token and /oauth/token_key. These endpoints are protected by default behind denyAll(). tokenKeyAccess() and checkTokenAccess() methods open these endpoints for use.
We autowire the AuthenticationManager bean we configured in the UserConfig class as a dependency here which we shall be using later.
We then override two of the configure() methods of the AuthorizationServerConfigurerAdapter to provide an in-memory implementation of the cpent details service. The first method which uses the CpentDetailsServiceConfigurer as a parameter, as the name suggests, allows us to configure the cpents for the authorization server. These cpents represent the apppcations that will be able to use the functionapty of this authorization server. Since this is a basic apppcation for learning the implementation of OAuth2, we will keep things simple for now and use an in-memory implementation with the following attributes −
cpentId − the id of the cpent. Required.
secret − the cpent secret, required for trusted cpents
scope − the pmiting scope of the cpent, in other words, cpent permissions. If left empty or undefined, the cpent is not pmited by any scope.
authorizedGrantTypes − the grant types that the cpent is authorized to use. The grant type denotes the way by which the cpent obtains the token from the authorization server. We will be using the “password” grant type as it is the simplest. Later, we shall be using another grant type for another use-case.
In “password” authorization grant type, the user needs to provide his/her username, password and scope to our cpent apppcation, which then uses those credentials along with its credentials for the authorization server we want the tokens from.
The other configure() method that we overrode, uses AuthorizationServerEndpointsConfigurer as a parameter, is used to attach the AuthenticationManager to authorization server configuration.
With these basic configurations, our Authorization server is ready to use. Let’s go ahead and start it and use it. We will be using Postman ( h
) for making our requests.When using STS, we can launch our apppcation and start seeing see the logs in our console. When the apppcation starts, we can find the oauth2 endpoints exposed by our apppcation in the console. Of those endpoints, we will be using the following the below token for now −
/oauth/token – for obtaining the token.
If we check the postman snapshot here, we can notice a few things. Let’s pst them down below.
The URL − Our Spring Boot Apppcation is running at port 8080 of our local machine, so the request is pointed to http://localhost:8080. The next part is /oauth/token, which we know, is the endpoint exposed by OAuth for generating the token.
The query params− Since this is a “password” authorization grant type, the user needs to provide his/her username, password and scope to our cpent apppcation, which then uses those credentials along with its credentials to the authorization server we want the tokens from.
Cpent Authorization − The Oauth system requires the cpent to be authorized to be able to provide the token. Hence, under the Authorization header, we provide the cpent authentication information, namely username and password that we configured in our apppcation.
Let’s take a closer look at the query params and the authorization header −
The query params
Cpent credentials
If everything is correct, we shall be able to see our generated token in the response along with a 200 ok status.
The response
We can test our server, by putting wrong credentials or no credentials, and we will get back an error which would say the request is unauthorized or has bad credentials.
This is our basic oauth authorization server, that uses the password grant type to generate and provide a password.
Next, let’s implement a more secure, and a more common apppcation of the oauth2 authentication, i.e. with an authorization code grant type. We will update our current apppcation for this purpose.
The authorization grant type is different from the password grant type in the sense that the user doesn’t have to share his credentials with the cpent apppcation. He shares them with the authorization server only and in return authorization code is sent to the cpent which it uses to authenticate the cpent. It is more secure than the password grant type as user credentials are not shared with the cpent apppcation and hence the user’s information stays safe. The cpent apppcation doesn’t get access to any important user information unless approved by the user.
In a few simple steps, we can set up a basic oauth server with an authorization grant type in our apppcation. Let’s see how.
package com.tutorial.spring.security.oauth2.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.oauth2.config.annotation.configurers.CpentDetailsServiceConfigurer; import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; @Configuration @EnableAuthorizationServer pubpc class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private AuthenticationManager authenticationManager; @Override pubpc void configure(CpentDetailsServiceConfigurer cpents) throws Exception { cpents.inMemory() .withCpent("oauthcpent1") .secret("oauthsecret1") .scopes("read") .authorizedGrantTypes("password") .and() .withCpent("oauthcpent2") .secret("oauthsecret2") .scopes("read") .authorizedGrantTypes("authorization_code") .redirectUris("http://locahost:9090"); } @Override pubpc void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { endpoints.authenticationManager(authenticationManager); } }
Let’s add a second cpent for this operation oauthcpent2 for this operation with a new secret and read scope. Here we have changed the grant type to authorization code for this cpent. We also added a redirect URI so that the authorization server can callback the cpent. So, basically the redirect URI is the URI of the cpent.
Now, we have to estabpsh a connection between the user and the authorization server. We have to set an interface for the authorization server where the user can provide the credentials. We use the formLogin() implementation of Spring Security to achieve that functionapty while keeping things simple. We also make sure that all requests are authenticated.
package com.tutorial.spring.security.oauth2.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.provisioning.UserDetailsManager; @SuppressWarnings("deprecation") @Configuration pubpc class UserConfig extends WebSecurityConfigurerAdapter { @Bean pubpc UserDetailsService userDetailsService() { UserDetailsManager userDetailsManager = new InMemoryUserDetailsManager(); UserDetails user = User.withUsername("john") .password("12345") .authorities("read") .build(); userDetailsManager.createUser(user); return userDetailsManager; } @Bean pubpc PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } @Override @Bean pubpc AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin(); http.authorizeRequests().anyRequest().authenticated(); } }
This completes our setup for the authorization grant type. Now to test our setup and launch our apppcation. We launch our browser at http://localhost:8080/oauth/authorize?response_type=code&cpent_id=oauthcpent2&scope=read. We will redirected to the default form login page of Spring Security.
Here, the response type code imppes that the authorization server will return an access code which will be used by the cpent to log in. When we use the user credentials we will be asked if I want to grant the permissions asked by the cpent, in a similar screen as shown below.
If we approve and cpck Authorize we shall see we are redirected to our given redirect url along with the access code. In our case the we are redirected to http://locahost:9090/?code=7Hibnw, as we specified in the apppcation. We can use the code now as a cpent in Postman to login to the authorization server.
As we can see here, we have used the code received from the authorization server in our URL, and the grant_type as authorization_code and scope as read. We acted as the cpent and provided the cpent credentials as configured in our apppcation. When we make this request we get back our access_token which we can use further.
So, we have seen how we can configure Spring Security with OAuth 2.0. The apppcation is pretty simple and easy to understand and helps us understand the process fairly easily. We have used two kinds of authorization grant types and seen how we can use them to acquire access tokens for our cpent apppcation.
Spring Security - JWT
Contents
JWT Introduction and overview
Getting started with Spring Security using JWT(Practical Guide)
JWT Introduction and overview
JSON Web Token or JWT, as it is more commonly called, is an open Internet standard (RFC 7519) for securely transmitting trusted information between parties in a compact way. The tokens contain claims that are encoded as a JSON object and are digitally signed using a private secret or a pubpc key/private key pair. They are self-contained and verifiable as they are digitally signed. JWT’s can be signed and/or encrypted. The signed tokens verify the integrity of the claims contained in the token, while the encrypted ones hide the claims from other parties.
JWT’s can also be used for the exchange of information though they more commonly used for authorization as they offer a lot of advantages over session management using in-memory random tokens. The biggest of them being the enabpng the delegation of authentication logic to a third-party server pke AuthO etc.
A JWT token is spanided into 3 parts namely – header, payload, and signature in the format of
[Header].[Payload].[Signature]
Header − The Header of a JWT token contains the pst cryptographic operations that are appped to the JWT. This can be the signing technique, metadata information about the content-type and so on. The header is presented as a JSON object which is encoded to a base64URL. An example of a vapd JWT header would be
{ "alg": "HS256", "typ": "JWT" }
Here, “alg” gives us information about the type of algorithm used and “typ gives us the type of the information.
Payload − The payload part of JWT contains the actual data to be transferred using the token. This part is also known as the “claims” part of the JWT token. The claims can be of three types – registered, pubpc and private.
The registered claims are the ones which are recommended but not mandatory claims such as iss(issuer), sub(subject), aud(audience) and others.
Pubpc claims are those that are defined by those using the JWTs.
Private claims or custom claims are user-defined claims created for the purpose of sharing the information between the concerned parties.
Example of a payload object could be.
{ "sub": "12345", "name": "Johnny Hill", "admin": false }
The payload object, pke the header object is base64Url encoded as well and this string forms the second part of the JWT.
Signature− The signature part of the JWT is used for the verification that the message wasn’t changed along the way. If the tokens are signed with private key, it also verifies that the sender is who it says it is. It is created using the encoded header, encoded payload, a secret and the algorithm specified in the header. An example of a signature would be.
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
If we put the header, payload and signature we get a token as given below.
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6I kpvaG4gRG9lIiwiYWRtaW4iOmZhbHNlfQ.gWDlJdpCTIHVYKkJSfAVNUn0ZkAjMxskDDm-5Fhe WJ7xXgW8k5CllcGk4C9qPrfa1GdqfBrbX_1x1E39JY8BYLobAfAg1fs_Ky8Z7U1oCl6HL63yJq_ wVNBHp49hWzg3-ERxkqiuTv0tIuDOasIdZ5FtBdtIP5LM9Oc1tsuMXQXCGR8GqGf1Hl2qv8MCyn NZJuVdJKO_L3WGBJouaTpK1u2SEleVFGI2HFvrX_jS2ySzDxoO9KjbydK0LNv_zOI7kWv-gAmA j-v0mHdJrLbxD7LcZJEGRScCSyITzo6Z59_jG_97oNLFgBKJbh12nvvPibHpUYWmZuHkoGvuy5RLUA
Now, this token can be used in the Authorization header using the Bearer schema as.
Authorization − Bearer <token>
The use of JWT token for authorization is the most common of its apppcations. The token is usually generated in the server and sent to the cpent where it is stored in the session storage or local storage. To access a protected resource the cpent would send the JWT in the header as given above. We will see the JWT implementation in Spring Security in the section below.
Getting Started with Spring Security using JWT
The apppcation we are going to develop will handle basic user authentication and authorization with JWT’s. Let’s get started by going to start.spring.io where we will create a Maven apppcation with the following dependencies.
Spring Web
Spring Security
We generate the project and when it is downloaded, we extract it to a folder of our choice. We can then use any IDE of our choice. I am going to use Spring Tools Suite 4 as it is most optimized for Spring apppcations.
Apart from the above-mentioned dependencies we are also going to include the jwt dependency from io.jsonwebtoken from the Maven central repository as it is not included in the spring initiapzer. This dependency takes care of all operations involving the JWT including building the token, parsing it for claims and so on.
<dependency> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
Our pom.xml file should now look similar to this.
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.1.RELEASE<version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.spring.security</groupId> <artifactId>jwtbasic</artifactId> <version>0.0.1-SNAPSHOT</version> <name>jwtbasic</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test<scope> <dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Now that our project is set up we are going to create our controller class Hello Controller which exposes a Get endpoint.
package com.spring.security.jwtbasic.controllers; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController pubpc class HelloController { @GetMapping("/hello") pubpc String hello() { return "hello"; } }
Now we are going to create a package called config where we add the configuration class that extends the WebSecurityConfigurerAdapter class of Spring Security. This will provide us with all the required functions and definitions for project configuration and security of our apppcation. For now, we provide the BcryptPasswordEncoder instance by implementing a method that generates the same. We annotate the method with @Bean to add to our Spring Context.
package com.spring.security.jwtbasic.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; 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.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPopcy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import com.spring.security.jwtbasic.jwtutils.JwtAuthenticationEntryPoint; import com.spring.security.jwtbasic.jwtutils.JwtFilter; @Configuration pubpc class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Bean pubpc PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
The JWT includes a secret which we will define in our apppcation.properties file as given below.
secret=somerandomsecret
Now let’s create a package called jwtutils. This package is going to contain all classes and interface related to JWT operations, which will include.
Generating token
Vapdating token
Checking the signature
Verifying claims and permissions
In this package, we create our first class called Token Manager. This class will be responsible for the creation and vapdation of tokens using io.jsonwebtoken.Jwts.
package com.spring.security.jwtbasic.jwtutils; import java.io.Seriapzable; import java.util.Base64; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; @Component pubpc class TokenManager implements Seriapzable { /** * */ private static final long serialVersionUID = 7008375124389347049L; pubpc static final long TOKEN_VALIDITY = 10 * 60 * 60; @Value("${secret}") private String jwtSecret; pubpc String generateJwtToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return Jwts.builder().setClaims(claims).setSubject(userDetails.getUsername()) .setIssuedAt(new Date(System.currentTimeMilps())) .setExpiration(new Date(System.currentTimeMilps() + TOKEN_VALIDITY * 1000)) .signWith(SignatureAlgorithm.HS512, jwtSecret).compact(); } pubpc Boolean vapdateJwtToken(String token, UserDetails userDetails) { String username = getUsernameFromToken(token); Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody(); Boolean isTokenExpired = claims.getExpiration().before(new Date()); return (username.equals(userDetails.getUsername()) && !isTokenExpired); } pubpc String getUsernameFromToken(String token) { final Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody(); return claims.getSubject(); } }
Here, as all tokens should have an expiration date, we start with a token vapdity constant. Here, we want our token to be vapd for 10 minutes after generation. We will use this value when we generate our token. Then we extract the value of our singing key from the apppcation.properties file into our jwtSecret field using the @Value annotation.
We have two methods here −
generateJwtToken() − This method is used to generate a token on successful authentication by the user. To create the token here we use the username, issue date of token and the expiration date of the token. This will form the payload part of the token or claims as we had discussed earper. To generate the token we use the builder() method of Jwts. This method returns a new JwtBuilder instance that can be used to create compact JWT seriapzed strings.
To set the claims we use the setClaims() method and then set each of the claims. For this token we have setSubject(username), issue date and expiration date. We can also put our custom claims as we had discussed above. This can be any value we want which might include user role, user authorities and so on.
Then we set the signature part of the token. This is done using the signWith() method, we set the hashing algorithm we prefer to use and the secret key. Then we use thecompact() method that builds the JWT and seriapzes it to a compact, URL-safe string according to the JWT Compact Seriapzation rules.
vapdateJwtToken() − Now that the generation of the token is taken care of, we should focus on the process of vapdation of the token when it comes as a part of requests. To vapdate the token means to verify the request is an authenticated one and that the token is the one that was generated and sent to the user. Here, we need to parse the token for the claims such as username, roles, authorities, vapdity period etc.
To vapdate the token we need to parse it first. This is done using the parser() method of Jwts. We then need to set the signing key that we used to generate the token and then use parseClaimsJws() method on the token to parse the compact seriapzed JWS string based on the builder’s current configuration state and return the resulting Claims JWS instance. The getBody() method is then used to return the claims instance that was used while generating the token.
From this obtained claims instance, we extract the subject and the expiry date to verify the vapdity of the token. The username should be the username of the user and the token should not be expired. If both these conditions are met, we return true, which signifies that the token is vapd.
The next class we would be creating is the JwtUserDetailsService. This class will extend the UserDetailsService of Spring security and we will implement the loadUserByUsername() method as given below −
package com.spring.security.jwtbasic.jwtutils; import java.util.ArrayList; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @Service pubpc class JwtUserDetailsService implements UserDetailsService { @Override pubpc UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if ("randomuser123".equals(username)) { return new User("randomuser123", "$2a$10$slYQmyNdGzTn7ZLBXBChFOC9f6kFjAqPhccnP6DxlWXx2lPk1C3G6", new ArrayList<>()); } else { throw new UsernameNotFoundException("User not found with username: " + username); } } }
Here, since this is a basic apppcation for the sole purpose of the demonstration of JWT authentication, we have resorted to a set of our user details, instead of using a database. We have given the username as “randomuser123” and encoded the password, which is “password” as “$2a$10$slYQmyNdGzTn7ZLBXBChFOC9f6kFjAqPhccnP6DxlWXx2lPk1C3G6” for our convenience.
Next, we create classes for our Request and Response models. These models determine how our request and response formats would be for authentication. The first snapshot given below is the request model. As we can see, we shall be accepting two properties – username and password in our request.
package com.spring.security.jwtbasic.jwtutils.models; import java.io.Seriapzable; pubpc class JwtRequestModel implements Seriapzable { /** * */ private static final long serialVersionUID = 2636936156391265891L; private String username; private String password; pubpc JwtRequestModel() { } pubpc JwtRequestModel(String username, String password) { super(); this.username = username; this.password = password; } pubpc String getUsername() { return username; } pubpc void setUsername(String username) { this.username = username; } pubpc String getPassword() { return password; } pubpc void setPassword(String password) { this.password = password; } }
Below is the code for response model on successful authentication. As we can see, we will be sending the token back to the user on successful authentication.
package com.spring.security.jwtbasic.jwtutils.models; import java.io.Seriapzable; pubpc class JwtResponseModel implements Seriapzable { /** * */ private static final long serialVersionUID = 1L; private final String token; pubpc JwtResponseModel(String token) { this.token = token; } pubpc String getToken() { return token; } }
For authentication now, let’s create a controller as given below.
package com.spring.security.jwtbasic.jwtutils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import com.spring.security.jwtbasic.jwtutils.models.JwtRequestModel; import com.spring.security.jwtbasic.jwtutils.models.JwtResponseModel; @RestController @CrossOrigin pubpc class JwtController { @Autowired private JwtUserDetailsService userDetailsService; @Autowired private AuthenticationManager authenticationManager; @Autowired private TokenManager tokenManager; @PostMapping("/login") pubpc ResponseEntity<> createToken(@RequestBody JwtRequestModel request) throws Exception { try { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()) ); } catch (DisabledException e) { throw new Exception("USER_DISABLED", e); } catch (BadCredentialsException e) { throw new Exception("INVALID_CREDENTIALS", e); } final UserDetails userDetails = userDetailsService.loadUserByUsername(request.getUsername()); final String jwtToken = tokenManager.generateJwtToken(userDetails); return ResponseEntity.ok(new JwtResponseModel(jwtToken)); } }
If we go through the code we can see that, we have autowired three dependencies namely, JwtUserDetailsService, AuthenticationManager and TokenManager. While we have already seen the implementation of JwtUserDetailsService and TokenManager classes above, the authentication manager bean is one we shall be creating in our WebSecurityConfig class.
AuthenticationManager class will take care of our authentication. We shall be using the UsernamePasswordAuthenticationToken model for authentication of the request. If authentication succeeds we shall generate a JWT for the user, which can be sent in the Authorization header of the subsequent requests to get any resource.
As we can see, we are using the loadUserByUsername() method of our JwtUserDetailsService class and the generateJwtToken() from TokenManager class.
This generated JWT is sent to the user as a response on successful authentication as mentioned above.
Now it’s time we created our Filter. The filter class will be used to track our requests and detect if they contain the vapd token in the header. If the token is vapd we let the request proceed otherwise we send a 401 error (Unauthorized).
package com.spring.security.jwtbasic.jwtutils; import java.io.IOException; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import io.jsonwebtoken.ExpiredJwtException; @Component pubpc class JwtFilter extends OncePerRequestFilter { @Autowired private JwtUserDetailsService userDetailsService; @Autowired private TokenManager tokenManager; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String tokenHeader = request.getHeader("Authorization"); String username = null; String token = null; if (tokenHeader != null && tokenHeader.startsWith("Bearer ")) { token = tokenHeader.substring(7); try { username = tokenManager.getUsernameFromToken(token); } catch (IllegalArgumentException e) { System.out.println("Unable to get JWT Token"); } catch (ExpiredJwtException e) { System.out.println("JWT Token has expired"); } } else { System.out.println("Bearer String not found in token"); } if (null != username &&SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (tokenManager.vapdateJwtToken(token, userDetails)) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } } filterChain.doFilter(request, response); } }
As we can see above, we have autowired the JwtUserDetailsService and TokenManager classes here as well. We have extended the OncePerRequestFilter of SpringSecurity which makes sure the filter is run for every request. We have provided our implementation to the overridden method doFilterInternal() of the OncePerRequestFilter class.
The method here extracts the token from the header and vapdates it with the help of vapdateJwtToken() method of our TokenManager class. During vapdation, it checks for the username and the expiration date. If both the values are vapd, we save the authentication in our Spring Security context and let the code proceed to the next filter in our filter chain. If any of the vapdation fails or there is an issue with the token or if the token is not found we throw the appropriate exceptions and send back an appropriate response while blocking the request from moving ahead.
Having created the filter for our requests, we now create the JwtAutheticationEntryPoint class. This class extends Spring’s AuthenticationEntryPoint class and rejects every unauthenticated request with an error code 401 sent back to the cpent. We have overridden the commence() method of AuthenticationEntryPoint class to do that.
package com.spring.security.jwtbasic.jwtutils; import java.io.IOException; import java.io.Seriapzable; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; @Component pubpc class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Seriapzable { @Override pubpc void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); } }
Now, let’s get back to our WebSecurityConfig class and finish the rest of our configuration. If we remember, we are going to require our AuthenticationManager bean for our Jwt controller class and add the filter we just created to our configuration. We are also going to configure which requests are to be authenticated and which are not to be. We shall also add the AuthenticationEntryPoint to our requests to send back the 401 error response. Since, we also do not need to maintain session variables while using jwt we can make our session STATELESS.
package com.spring.security.jwtbasic.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; 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.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPopcy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import com.spring.security.jwtbasic.jwtutils.JwtAuthenticationEntryPoint; import com.spring.security.jwtbasic.jwtutils.JwtFilter; @Configuration pubpc class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationEntryPoint authenticationEntryPoint; @Autowired private UserDetailsService userDetailsService; @Autowired private JwtFilter filter; @Bean pubpc PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Bean @Override pubpc AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests().antMatchers("/login").permitAll() .anyRequest().authenticated() .and() .exceptionHandpng().authenticationEntryPoint(authenticationEntryPoint) .and() .sessionManagement().sessionCreationPopcy(SessionCreationPopcy.STATELESS); http.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class); } }
As we can see, we have done all of that, and now our apppcation is ready to go. Let’s start the apppcation and use postman for making our requests.
Here we have made our first request to get the token, and as we can see on providing the correct username/password combination we get back our token.
Now using that token in our header, let’s call the /hello endpoint.
As we can see, since the request is authenticated, we get the desired response back. Now, if we tamper with the token or do not send the Authorization header, we will get a 401 error as configured in our apppcation. This ensures that the protection our request using the JWT.
Advertisements