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.
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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
