SoFunction
Updated on 2025-03-03

The concept of single sign-on and the operation method of SpringBoot to implement single sign-on

In modern web applications, single sign-on (Single Sign-On) has become increasingly popular. In this article, we will use Spring Boot to build a basic single sign-on system. If you need it, please refer to it.

Preface

Single Sign-On has become increasingly popular in modern web applications. Single sign-on allows users to access multiple applications with only one authentication, while also improving application security. As a widely used web development framework, Spring Boot also provides good support for single sign-on.

In this article, we will use Spring Boot to build a basic single sign-on system. We will explain how to use Spring Security and JSON Web Tokens (JWTs) to implement single sign-on functionality. This article assumes that you are already familiar with Spring Boot and Spring Security.

What is JWT

Before introducing the implementation of single sign-on, let's take a look at JWT first. JWT is an open standard based on JSON format (RFC 7519) for safely transmitting information between different applications. It consists of three parts:

  • Header: Contains the type of JWT and the signature algorithm used.
  • Payload: Contains actual information.
  • Signature: A signature generated using a private key to verify the authenticity of the JWT.

JWT is usually used during the authentication process to verify the user's identity without storing user information. Since JWT is built on the standardized JSON format, it can be implemented and parsed easily in a variety of programming languages.

Implement single sign-on

Below we will introduce how to use JWT to implement a basic single sign-on system. This system consists of two applications: a certified application and a resource application. After a user authenticates on the authenticated application, he or she can access the resource application.

Certified Application

We first need to build an authentication application that authenticates user information and generates JWT.

Add dependencies

First, we need to add the following dependencies:

<dependency>
  <groupId></groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
  <groupId></groupId>
  <artifactId>jjwt</artifactId>
  <version>0.9.1</version>
</dependency>
<dependency>
  <groupId></groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
  • spring-boot-starter-security: Used to provide basic security support.
  • jjwt: Java implementation of JSON Web Token.
  • spring-boot-starter-web: used to provide web application support.

Configuring Spring Security

Next, we need to configure Spring Security. We will use Spring Security's default configuration and add a custom UserDetailsService to load user information from the database.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  @Autowired
  private UserDetailsService userDetailsService;
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .csrf().disable()
      .authorizeRequests()
        .antMatchers("/auth/**").permitAll()
        .anyRequest().authenticated()
      .and().formLogin()
        .loginPage("/auth/login")
        .successHandler(authenticationSuccessHandler())
        .failureHandler(authenticationFailureHandler())
        .permitAll()
      .and().logout()
        .logoutUrl("/auth/logout")
        .logoutSuccessUrl("/auth/login?logout")
        .invalidateHttpSession(true)
        .deleteCookies("JSESSIONID");
  }
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    (userDetailsService).passwordEncoder(passwordEncoder());
  }
  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }
  @Bean
  public AuthenticationSuccessHandler authenticationSuccessHandler() {
    return new JWTAuthenticationSuccessHandler();
  }
  @Bean
  public AuthenticationFailureHandler authenticationFailureHandler() {
    return new JWTAuthenticationFailureHandler();
  }
}

In the above configuration, we define a routing expression "/auth/**" to allow anonymous access, which means that the login and registration pages of the authenticated application can be accessed by unauthenticated users. We also define custom AuthenticationSuccessHandler and AuthenticationFailureHandler to generate and return JWT to the user when user authentication succeeds or fails. These handlers will be implemented in the next step.

Implement custom AuthenticationSuccessHandler and AuthenticationFailureHandler

In the above configuration, we use the custom AuthenticationSuccessHandler and AuthenticationFailureHandler. Let's implement them.

public class JWTAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
  private static final String JWT_SECRET = "secret";
  private static final long JWT_EXPIRATION_TIME = 864000000; // 10 days
  @Override
  public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
    Authentication authentication) throws IOException, ServletException {
    String username = ();
    String token = ()
      .setSubject(username)
      .setExpiration(new Date(() + JWT_EXPIRATION_TIME))
      .signWith(SignatureAlgorithm.HS512, JWT_SECRET.getBytes())
      .compact();
    ("Authorization", "Bearer " + token);
    ().write("{\"token\":\"Bearer " + token + "\"}");
    ("application/json");
  }
}

In the above code, we use the JJWT library to generate the JWT. In the onAuthenticationSuccess method, we first get the username from the Authentication object, and then create the JWT with the username. We set the validity period of JWT to 10 days, and use the HS512 signature algorithm to sign the JWT, using a string as the key. Finally, we add JWT to the response message header as a Bearer token and encapsulate it in the JSON format response body to return to the client.

public class JWTAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
  @Override
  public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
    AuthenticationException exception) throws IOException, ServletException {
    (HttpServletResponse.SC_UNAUTHORIZED);
    ().write("{\"error\":\"Bad credentials\"}");
    ("application/json");
  }
}

In the above code, we set the response code to 401 (unauthorized) and add an error message to the response body to notify the client of authentication failed.

Implement authorization controller

Now that we have created the basic security of authenticated applications, let's build the resource application and implement the authorization controller to ensure that only authenticated users can access protected resources. We will use JWT to verify the user's identity.

@RestController
public class ResourceController {
  private static final String JWT_SECRET = "secret";
  @GetMapping("/resource")
  public ResponseEntity<String> getResource(HttpServletRequest request) {
    String token = ("Authorization").replace("Bearer ", "");
    if (isValidJWT(token)) {
      return ("Protected resource");
    } else {
      return ().build();
    }
  }
  private boolean isValidJWT(String jwt) {
    try {
      ().setSigningKey(JWT_SECRET.getBytes()).parseClaimsJws(jwt);
      return true;
    } catch (JwtException e) {
      return false;
    }
  }
}

In the above code, we use an example protected resource path "/resource" that allows only authenticated users to access. We extract the Bearer token from the request header and use the isValidJWT method to verify the authenticity of the token. If JWT is valid, return 200 response code and protected resources; otherwise return 401 (unauthorized) response code.

Resource Application

Now that we have created the authentication application, let's create a resource application so that users can access it after verification. The resource application will verify that the user has permission to access protected resources.

Configuring Spring Security

We first need to configure Spring Security to enable the resource application to authenticate the JWT and grant users access.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http
      .csrf().disable()
      .authorizeRequests()
      .antMatchers("/resource").authenticated()
      .and().sessionManagement().sessionCreationPolicy()
      .and().addFilterBefore(new JWTAuthorizationFilter(), );
  }
}

In the above configuration, we define a routing expression "/resource", which can only be accessed by authenticated users. We also set the session management policy to STATELESS to avoid using HTTP sessions.

Implement JWTAuthorizationFilter

Next, we need to implement the JWTAuthorizationFilter to verify the JWT from the client and associate it with the user information. This will allow us to check if the user has permission to access the resource.

public class JWTAuthorizationFilter extends OncePerRequestFilter {
  private static final String JWT_SECRET = "secret";
  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
    FilterChain filterChain) throws ServletException, IOException {
    String authorizationHeader = ("Authorization");
    if (authorizationHeader == null || !("Bearer ")) {
      (request, response);
      return;
    }
    String jwt = ("Bearer ", "");
    try {
      Jws<Claims> claimsJws = ().setSigningKey(JWT_SECRET.getBytes()).parseClaimsJws(jwt);
      String username = ().getSubject();
      List<GrantedAuthority> authorities = new ArrayList<>();
      UserDetails userDetails = new User(username, "", authorities);
      UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
        userDetails, null, ());
      ().setAuthentication(authentication);
    } catch (JwtException e) {
      throw new ServletException("Invalid JWT");
    }
    (request, response);
  }
}

In the above code, we query the Authorization header to find the Bearer token. If the token does not exist or is incorrect, the request will continue to be passed. Otherwise, we use the JJWT library to verify the authenticity of the JWT and get the user's username. We create the username as the UserDetails object of Spring Security and create a UsernamePasswordAuthenticationToken that sets the authentication information as part of the current Spring Security context. Once completed, the request will continue to be passed and authorized to access the resource.

test

Now that we have created certified applications and resource applications, let's test them. First, we register and log in on the authenticated application to get the JWT token. We will then use that token to access the protected resources of the resource application and verify that we can access it successfully.

# Register and login to authentication application
$ curl -s -X POST -H "Content-Type: application/json" -d '{"username":"user","password":"password"}' http://localhost:8080/auth/signup
$ curl -s -X POST -H "Content-Type: application/json" -d '{"username":"user","password":"password"}' http://localhost:8080/auth/login

# Get JWT token
$ TOKEN=$(curl -si -X POST -H "Content-Type: application/json" -d '{"username":"user","password":"password"}' http://localhost:8080/auth/login | grep 'Authorization:' | awk '{print $2}')

# Access protected resource in resource application
$ curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8090/resource
Protected resource

Through testing, we can see that we have successfully accessed the protected resource of the resource application and returned the correct response.

Summarize

In this article, we implement a basic single sign-on system using Spring Boot, Spring Security and JWT. We introduce the concept of JWT and demonstrate how to use it to verify user identity. We also created an authentication application and a resource application to demonstrate how to share user authentication information among multiple applications. You can use this paradigm as a base template to further extend it to suit your own application needs.

This is the article about the concept of single sign-on and how to implement single sign-on in SpringBoot. For more related springboot single sign-on content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!