Home >Java >javaTutorial >Understanding Spring Security and OAuth

Understanding Spring Security and OAuth

Susan Sarandon
Susan SarandonOriginal
2025-01-14 16:05:46690browse

In this article we will explore Spring security and will build a authentication system with OAuth 2.0.

Spring Security is a powerful, highly customizable framework for implementing robust authentication and access control mechanisms in Java-based applications. It is a core component of the Spring ecosystem, widely used to secure web applications, REST APIs, and other backend services. With Spring Security, you gain a solid foundation to build and enforce secure practices in your application.


How Spring Security Works

Before diving into how Spring Security operates, it's crucial to understand the request-handling lifecycle in a Java-based web server. Spring Security seamlessly integrates into this lifecycle to secure incoming requests.


Request-Handling Lifecycle with Spring Security

The lifecycle of handling an HTTP request in a Spring-based application with Spring Security involves several stages, each playing a critical role in processing, validating, and securing the request.


1. Client Request

The lifecycle begins when a client (e.g., browser, mobile app, or API tool like Postman) sends an HTTP request to the server.

Example:

GET /api/admin/dashboard HTTP/1.1


2. Servlet Container

The servlet container (e.g., Tomcat) receives the request and delegates it to the DispatcherServlet, the front controller in a Spring application. This is where the application’s processing pipeline starts.


3. Spring Security Filter Chain

Before the DispatcherServlet processes the request, Spring Security's Filter Chain intercepts it. The filter chain is a sequence of filters, each responsible for handling specific security tasks. These filters ensure the request meets authentication and authorization requirements before it reaches the application logic.

Key Filters in the Chain:

  1. Authentication Filters:

    These filters verify if the request contains valid credentials, such as a username/password, a JWT, or session cookies.

  2. Authorization Filters:

    After authentication, these filters ensure the authenticated user has the necessary roles or permissions to access the requested resource.

  3. Other Filters:

* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks.

* **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains.

* **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.

4. Security Context

If authentication is successful, Spring Security creates an Authentication object and stores it in the SecurityContext. This object, often stored in a thread-local storage, is accessible throughout the request lifecycle.

The Authentication Object:

  • Principal: Represents the authenticated user (e.g., username).

  • Credentials: Includes authentication details like JWT tokens or passwords.

  • Authorities: Contains roles and permissions assigned to the user.

Example Flow in the Filter Chain:

  • A request passes through the authentication filters.

  • If the credentials are valid, the Authentication object is created and added to the SecurityContext.

  • If the credentials are invalid, the ExceptionTranslationFilter sends a 401 Unauthorized response to the client.


5. DispatcherServlet

Once the request successfully passes through the Spring Security Filter Chain, the DispatcherServlet takes over:

  1. Handler Mapping:

    It maps the incoming request to the appropriate controller method based on the URL and HTTP method.

  2. Controller Invocation:

    The mapped controller processes the request and returns the appropriate response, often with help from other Spring components like services and repositories.

How Spring Security Fits Into the Lifecycle

Spring Security integrates itself into this lifecycle through its filters, intercepting requests at the earliest stage. By the time a request reaches the application logic, it has already been authenticated and authorized, ensuring only legitimate traffic gets processed by the core application.


Spring Security’s design ensures that authentication, authorization, and other security measures are handled declaratively, giving developers the flexibility to customize or extend its behavior as needed. It not only enforces best practices but also simplifies the implementation of complex security requirements in modern applications.

Understanding Spring Security and OAuth

Spring Security Components: Beyond the Filter Chain

Having explored the Filter Chain in Spring Security, let’s delve into some other key components that play a pivotal role in the authentication and authorization process.

AuthenticationManager

AuthenticationManager is an interface that defines a single method , authenticate(Authentication authentication) , which is used to verify the credentials of a user and determine if they are valid. You can think of AuthenticationManager as a coordinator where you can register multiple providers, and based on the request type, it will deliver an authentication request to the correct provider.

AuthenticationProvider

An AuthenticationProvider is an interface that defines a contract for authenticating users based on their credentials. It represents a specific authentication mechanism, such as username/password, OAuth, or LDAP. Multiple AuthenticationProvider implementations can coexist, allowing the application to support various authentication strategies.

Core Concepts:

  1. Authentication Object:

    The AuthenticationProvider processes an Authentication object, which encapsulates the user’s credentials (e.g., username and password).

  2. authenticate Method:

    Each AuthenticationProvider implements the authenticate(Authentication authentication) method, where the actual authentication logic resides. This method:

* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks.

* **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains.

* **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.
  1. supports Method: The supports(Class authentication) method indicates whether the AuthenticationProvider can handle the given type of Authentication. This allows Spring Security to determine the correct provider to handle specific authentication requests.

Example:

  • A database-backed AuthenticationProvider validates usernames and passwords.

  • An OAuth-based AuthenticationProvider validates tokens issued by an external identity provider.

UserDetailsService

UserDetailsService is described as a core interface that loads user-specific data in the Spring documentation It contains a single method loadUserByUsername which accepts username as parameter and returns the ==User== identity object . Basically we create and implementation class of UserDetailsService in which we override the loadUserByUsername method.

* Validates the user’s credentials.

* Returns an authenticated `Authentication` object upon success.

* Throws an `AuthenticationException` if authentication fails.

Now how all these three works together is AuthenticationManager will ask AuthenticationProvider to carry on the authentication according to the type of Provider specified and the UserDetailsService implementation will help the AuthenticationProvider in proving the userdetails .

Now before moving to the configuration and all stuff here's a concise flow of Spring Security for JWT-based authentication:

1. User Request

  • The user sends a request to the authenticated endpoint with their credentials (username and password) or a JWT token (in the header) and the request is passed to the Authentication Filter

  • AuthenticationFilter (e.g., UsernamePasswordAuthenticationFilter):

    • Handles user authentication based on credentials submitted (typically in the form of a username and password). This is where the UsernamePasswordAuthenticationFilter comes into play.
    • It listens for the request , extracts the username and password, and passes them to the AuthenticationManager.
    • But we are not passing the username and password , we are giving only the token hence there should be a filter before this AuthenticationFilter which will tell the Authentication Process that the user is authenticated and no need to check for Username and password and this is done by creating a JWTFilter

2. JWTFilter

This custom filter extends OncePerRequestFilter and is placed before the UsernamePasswordAuthenticationFilter , and what it do is it extracts the token from request and validates it.

If the token is valid it creates a UsernamePasswordAuthenticationToken and sets that token into the Security Context which tells the spring security that the request is authenticated and when this request passes to the UsernamePasswordAuthenticationFilter it just passes as it has the UsernamePasswordAuthenticationToken

* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks.

* **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains.

* **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.

this UsernamePasswordAuthenticationToken is generated with the help of the AuthenticationManager and AuthenticationProvider if we have passed username and password instead of the token after authenticating the username and password with the help of out UserDetails class.

3. AuthenticationManager

  • AuthenticationManager: This receives the authentication request and delegates it to the appropriate AuthenticationProvider which we configure.
* Validates the user’s credentials.

* Returns an authenticated `Authentication` object upon success.

* Throws an `AuthenticationException` if authentication fails.

4. AuthenticationProvider

  • UserDetailsService: The AuthenticationProvider uses UserDetailsService to load user details based on the username. And we provide this with a implementation of UserDetailsService

  • Credential Validation: It compares the provided password with the one stored in the user details (typically using a PasswordEncoder).

package com.oauth.backend.services;

import com.oauth.backend.entities.User;
import com.oauth.backend.repositories.UserRepository;
import lombok.RequiredArgsConstructor;
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.Component;


@Component
public class CustomUserDetailsService implements UserDetailsService {
    private final UserRepository userRepository;

    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User user = userRepository.findByUsername(username);
        if(user==null){
            throw new UsernameNotFoundException(username);
        }
        return new UserDetailsImpl(user);
    }
    public UserDetails loadUserByEmail(String email) throws UsernameNotFoundException {
        User user = userRepository.findByEmail(email);
        if(user==null){
            throw new UsernameNotFoundException(email);
        }
        return new UserDetailsImpl(user);
    }
}

Now all these different filters and beans are needed to be configured so that Spring security knows what to do hence we create a configuration class where we specify all the configuration.

@Component
public class JWTFilter extends OncePerRequestFilter {

    private final JWTService jwtService;
    private final UserDetailsService userDetailsService;
    public JWTFilter(JWTService jwtService,UserDetailsService userDetailsService) {
        this.jwtService = jwtService;
        this.userDetailsService = userDetailsService;
    }
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        final String authHeader = request.getHeader("Authorization");

        if(authHeader == null || !authHeader.startsWith("Bearer")) {
            filterChain.doFilter(request,response);
            return;
        }

        final String jwt = authHeader.substring(7);
        final String userName = jwtService.extractUserName(jwt);

        Authentication authentication
                = SecurityContextHolder.getContext().getAuthentication();

        if(userName !=null  && authentication == null) {
            //Authenticate
            UserDetails userDetails
                    = userDetailsService.loadUserByUsername(userName);

            if(jwtService.isTokenValid(jwt,userDetails)) {
                UsernamePasswordAuthenticationToken authenticationToken
                        = new UsernamePasswordAuthenticationToken(
                        userDetails,
                        null,
                        userDetails.getAuthorities()
                );

                SecurityContextHolder.getContext()
                        .setAuthentication(authenticationToken);
            }
        }

        filterChain.doFilter(request,response);
    }



}

till now we have understand and configured our authentication with the help of spring security now it’s time to test it.

We will create a simple app with two controllers AuthController (handles login and register) and ProductController (dummy protected controller)

* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks.

* **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains.

* **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.
* Validates the user’s credentials.

* Returns an authenticated `Authentication` object upon success.

* Throws an `AuthenticationException` if authentication fails.
package com.oauth.backend.services;

import com.oauth.backend.entities.User;
import com.oauth.backend.repositories.UserRepository;
import lombok.RequiredArgsConstructor;
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.Component;


@Component
public class CustomUserDetailsService implements UserDetailsService {
    private final UserRepository userRepository;

    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User user = userRepository.findByUsername(username);
        if(user==null){
            throw new UsernameNotFoundException(username);
        }
        return new UserDetailsImpl(user);
    }
    public UserDetails loadUserByEmail(String email) throws UsernameNotFoundException {
        User user = userRepository.findByEmail(email);
        if(user==null){
            throw new UsernameNotFoundException(email);
        }
        return new UserDetailsImpl(user);
    }
}
@Component
public class JWTFilter extends OncePerRequestFilter {

    private final JWTService jwtService;
    private final UserDetailsService userDetailsService;
    public JWTFilter(JWTService jwtService,UserDetailsService userDetailsService) {
        this.jwtService = jwtService;
        this.userDetailsService = userDetailsService;
    }
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        final String authHeader = request.getHeader("Authorization");

        if(authHeader == null || !authHeader.startsWith("Bearer")) {
            filterChain.doFilter(request,response);
            return;
        }

        final String jwt = authHeader.substring(7);
        final String userName = jwtService.extractUserName(jwt);

        Authentication authentication
                = SecurityContextHolder.getContext().getAuthentication();

        if(userName !=null  && authentication == null) {
            //Authenticate
            UserDetails userDetails
                    = userDetailsService.loadUserByUsername(userName);

            if(jwtService.isTokenValid(jwt,userDetails)) {
                UsernamePasswordAuthenticationToken authenticationToken
                        = new UsernamePasswordAuthenticationToken(
                        userDetails,
                        null,
                        userDetails.getAuthorities()
                );

                SecurityContextHolder.getContext()
                        .setAuthentication(authenticationToken);
            }
        }

        filterChain.doFilter(request,response);
    }



}
@Bean  
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception{  
return config.getAuthenticationManager();  
}
@Bean  
public AuthenticationProvider authenticationProvider(){  
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();  
authenticationProvider.setUserDetailsService(userDetailsServiceImpl);  
authenticationProvider.setPasswordEncoder(passwordEncoder);  
return authenticationProvider;  
}

till now we have implemented a registration , login and verification but what if i also want to add Login With Google/Github fuctionality then we can do it with the help of OAuth2.0

OAuth 2.0

OAuth 2.0 is a protocol made for authorization through which enables users to grant third party applications access to resources stored on other platforms (e.g Google Drive , Github) without sharing the credentials of those platforms.

It is mostly used in enabling social logins like “Login with google” , “Login with github ” .

Platforms like Google , Facebook , Github provides Authorization server which implements OAuth 2.0 protocol for this social sign in or authorizing access .

Key Concepts of OAuth 2.0

  • Resource Owner

  • Client

  • Authorization Server

  • Resource Server

  • Access Token

  • Scopes

  • Grants

Now we will look into each concept one by one

Resource Owner

Resource owner is the user who wants to authorize the third party application (Your Application).

Client

It is your (third party) application which wants to access the data or resource from the resource server.

Resource Server

It is the server where the user’s data is stored and is to be accessed by the third party application.

Authorization Server

The server that authenticates the resource owner and issues access tokens to the client (e.g., Google Accounts).

Access Token

A credential issued by the authorization server to the client, allowing it to access the resource server on behalf of the user. It is generally short lived that is expires very soon so a refresh token is also provided in order to refresh this access token so that user doesn’t need to authorize again.

Scopes

Specific permissions granted by the user, defining what the client can and cannot do with the user's data. For example for authorization we only need user info like profile , name etc. but for file access different scope is required.

Grants

It refers to the methods by which Client application can obtain the access token from the Authorization Server. A grant defines the process and conditions under which the client application is authorized to access a resource owner's protected data.

It is secure as we do not need to expose our client secret and other credentials to the browser

There are two mostly used Grant types provided by OAuth 2.0

  1. Authorization Code Grant

    It is the most used type of grant/method , most secure and is for server-side applications

    In this a authorization code is given by the client to the backend and the backend gives the access token to the client.

    Process:

    1. The client redirects the user to the authorization server.
    2. The user logs in and consents.
    3. The authorization server issues an authorization code.
    4. The client exchanges the authorization code with the backend for an access token.
  2. Implicit Grant

    Used by single-page apps (SPAs) or applications without a backend. In this the access token is directly generated and issued in the browser itself.

    Process:

    1. The client redirects the user to the authorization server.
    2. The user logs in and consents.
    3. The authorization server directly issues an access token.

We will implement both separately for complete understanding , but first we will implement the Authorization Code Grant for that we will need

  1. Authorization Server

    It can be either of a platform (like google , github) or you can create your own also using KeyCloak or can also build your own adhering to OAuth 2.0 standards ( we may do this in next blog ?)

  2. Spring Boot Application

    This will be our main backend application/service which will handle all the operations like code exchange, verification , saving user details and assigning JWT tokens

  3. React Application (Frontend)

    This will be our client which will redirect the user to Authorization Server for authorization.

So in our implementation what we will be doing is the frontend(web/app) will redirect our user to google login with redirect uri to our backend endpoint which will take control further we will talki about it later and along with the redirect_url we also be passing the client id of our app all these will be sent in the query parameters.

No when the user will successfully login in the google the authrization server(google’s) will redirect our request to the backend enpoint and there what we will be doing is exchanging Authorization code with authorization server to get access token and refresh token and then we can handle the auth as we want and finally we will send a response back to our frontend which will have a cookie and redirect to our dashboard or may be a protected page.

Now we will look into the code , but make sure that you add the url of your backend endpoint in authorized redirect urls in Google console dashboard for OAuth client.

* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks.

* **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains.

* **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.

and that’s it this will work fine and for testing you can made a simple frontend application which will nothing but having a context and yout know login and registration functions.

Thanks for reading till this long , and if you have any suggestion , please drop it in the comments

The above is the detailed content of Understanding Spring Security and OAuth. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Previous article:How to index in SolrJNext article:How to index in SolrJ