Master Spring Security & JWT for Seamless Single Sign-On (SSO)

This comprehensive guide walks you through the concepts of Single Sign-On, the mechanics of JWT, RSA asymmetric encryption, and step‑by‑step integration of Spring Security with JWT, providing full Maven project setup, configuration files, utility classes, custom filters, and testing instructions for a robust distributed authentication system.

Java High-Performance Architecture
Java High-Performance Architecture
Java High-Performance Architecture
Master Spring Security & JWT for Seamless Single Sign-On (SSO)

What is Single Sign‑On (SSO)

Single Sign‑On (SSO) allows a user to log in once and gain access to multiple trusted applications without re‑authenticating.

Simple Working Mechanism

Imagine a park with many attractions; buying a single ticket at the gate lets visitors enter all attractions without purchasing separate tickets at each entrance. SSO works the same way.

SSO flow diagram
SSO flow diagram

JWT Introduction

Concept

JWT (JSON Web Token) is a widely used distributed identity verification solution that can generate and validate tokens.

Token Structure

Header – contains metadata such as the signing algorithm.

Payload – stores claims like username, roles, and expiration time (never store passwords).

Signature – Base64‑encoded header and payload are concatenated, salted, and signed according to the algorithm declared in the header.

Security Analysis

The signature part is the only secure element; the header and payload are merely Base64‑encoded. Therefore, the salt (or private key) must be kept secret, preferably using asymmetric encryption.

RSA Asymmetric Encryption

RSA generates a public‑private key pair; the private key is kept secret while the public key can be distributed to trusted clients.

Private‑key encryption – only the holder of the private key can decrypt.

Public‑key encryption – only the holder of the private key can decrypt.

Advantages: high security; Disadvantages: computationally intensive.

Spring Security Integration with JWT

1. Authentication Flow Analysis

Spring Security relies on filters. The default authentication filter is UsernamePasswordAuthenticationFilter, which invokes attemptAuthentication and successfulAuthentication.

In a distributed setup, the authentication filter must accept JSON payloads and generate a JWT instead of storing a session.

Review of Centralized Authentication

Authentication uses UsernamePasswordAuthenticationFilter.attemptAuthentication and successfulAuthentication. Authorization checks are performed by BasicAuthenticationFilter.doFilterInternal.

Distributed Authentication Flow

Modify UsernamePasswordAuthenticationFilter to read the request body, and after successful authentication generate a JWT with a private key.

Modify BasicAuthenticationFilter to validate the JWT using the public key and set the authentication context.

2. Concrete Implementation

2.1 Create Parent Maven Project

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.3.RELEASE</version>
    <relativePath/>
</parent>

2.2 Create Common Module and Add JWT Dependencies

<dependencies>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.10.7</version>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.10.7</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>
        <version>0.10.7</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.9</version>
    </dependency>
    <!-- other utility dependencies omitted for brevity -->
</dependencies>

Utility Classes

Payload class:

@Data
public class Payload<T>{
    private String id;
    private T userInfo;
    private Date expiration;
}

JsonUtils, JwtUtils, and RsaUtils provide JSON conversion, JWT generation/verification, and RSA key handling respectively (code omitted for brevity).

3. Authentication Service Creation

Define UserService implementing UserDetailsService to load user data from MyBatis mapper.

4. Custom Authentication Filter

public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
    private AuthenticationManager authenticationManager;
    private RsaKeyProperties prop;
    public TokenLoginFilter(AuthenticationManager authenticationManager, RsaKeyProperties prop) {
        this.authenticationManager = authenticationManager;
        this.prop = prop;
    }
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            UserPojo sysUser = new ObjectMapper().readValue(request.getInputStream(), UserPojo.class);
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword());
            return authenticationManager.authenticate(authRequest);
        } catch (Exception e) {
            // error handling omitted for brevity
            throw new RuntimeException(e);
        }
    }
    @Override
    public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        UserPojo user = new UserPojo();
        user.setUsername(authResult.getName());
        user.setRoles((List<RolePojo>)authResult.getAuthorities());
        String token = JwtUtils.generateTokenExpireInMinutes(user, prop.getPrivateKey(), 24 * 60);
        response.addHeader("Authorization", "Bearer " + token);
        // write success JSON response (omitted)
    }
}

5. JWT Verification Filter

public class TokenVerifyFilter extends BasicAuthenticationFilter {
    private RsaKeyProperties prop;
    public TokenVerifyFilter(AuthenticationManager authenticationManager, RsaKeyProperties prop) {
        super(authenticationManager);
        this.prop = prop;
    }
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String header = request.getHeader("Authorization");
        if (header == null || !header.startsWith("Bearer ")) {
            chain.doFilter(request, response);
            return;
        }
        String token = header.replace("Bearer ", "");
        Payload<UserPojo> payload = JwtUtils.getInfoFromToken(token, prop.getPublicKey(), UserPojo.class);
        UserPojo user = payload.getUserInfo();
        if (user != null) {
            UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        chain.doFilter(request, response);
    }
}

6. Spring Security Configuration

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserService userService;
    @Autowired
    private RsaKeyProperties prop;
    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .addFilter(new TokenLoginFilter(authenticationManager(), prop))
            .addFilter(new TokenVerifyFilter(authenticationManager(), prop))
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
}

7. Resource Service (Stateless Validation Only)

The resource service imports only the public key, adds TokenVerifyFilter, and defines simple REST endpoints that require authentication.

Testing

Run the authentication service, obtain a JWT via POST /login, then include the token in the Authorization header when calling protected endpoints on both the authentication and resource services. Use tools like Postman to verify the flow.

Postman test screenshot
Postman test screenshot
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaBackend DevelopmentAuthenticationJWTSpring SecuritySingle Sign-On
Java High-Performance Architecture
Written by

Java High-Performance Architecture

Sharing Java development articles and resources, including SSM architecture and the Spring ecosystem (Spring Boot, Spring Cloud, MyBatis, Dubbo, Docker), Zookeeper, Redis, architecture design, microservices, message queues, Git, etc.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.