OAuth2 & JWT Dependencies
Cover spring-boot-starter-oauth2-client for social login, spring-boot-starter-oauth2-resource-server for validating JWTs, and jjwt for issuing your own tokens.
Introduction
OAuth2 and JWT solve two related but different problems: OAuth2 client support handles "Login with Google" style flows where a third party proves who your user is, OAuth2 resource server support handles verifying tokens that arrive on incoming API requests, and JWT libraries let your own application issue tokens when you are the one doing the authenticating. This lesson covers all three.
- How spring-boot-starter-oauth2-client enables social login with providers like Google and GitHub.
- How spring-boot-starter-oauth2-resource-server validates JWT bearer tokens on incoming requests.
- How to issue your own JWTs with io.jsonwebtoken (jjwt).
- When each of these three dependencies is the right tool.
OAuth2 Client: Login with Google/GitHub
spring-boot-starter-oauth2-client is for the "Sign in with Google" button. Your application redirects the user to Google, Google authenticates them and asks for consent, and Google redirects back with proof of identity — your app never sees the user's Google password.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-client</artifactId></dependency>spring.security.oauth2.client.registration.google.client-id=YOUR_CLIENT_IDspring.security.oauth2.client.registration.google.client-secret=YOUR_CLIENT_SECRETspring.security.oauth2.client.registration.google.scope=openid,profile,email@Configuration@EnableWebSecuritypublic class OAuth2LoginConfig {
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/", "/login/**").permitAll() .anyRequest().authenticated() ) .oauth2Login(Customizer.withDefaults());
return http.build(); }}Click Run to see what this code prints.
OAuth2 Resource Server: Validating JWTs
spring-boot-starter-oauth2-resource-server is for the other side of the conversation: your API receives a request with an Authorization: Bearer <token> header, and this starter validates that token — checking its signature and expiry — before letting the request through.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId></dependency>spring.security.oauth2.resourceserver.jwt.issuer-uri=https://accounts.google.com@Configuration@EnableWebSecuritypublic class ResourceServerConfig {
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/api/public/**").permitAll() .anyRequest().authenticated() ) .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
return http.build(); }}Click Run to see what this code prints.
jjwt: Issuing Your Own Tokens
If you are not delegating to Google or another identity provider and instead want your own login endpoint to hand back a JWT, io.jsonwebtoken (jjwt) is the standard library for creating and parsing tokens. It ships as three artifacts: the API, the implementation, and a Jackson module for JSON serialization.
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.12.6</version></dependency><dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.12.6</version> <scope>runtime</scope></dependency><dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.12.6</version> <scope>runtime</scope></dependency>@Servicepublic class JwtService {
private final SecretKey key = Jwts.SIG.HS256.key().build();
public String generateToken(String username) { return Jwts.builder() .subject(username) .issuedAt(new Date()) .expiration(new Date(System.currentTimeMillis() + 3_600_000)) .signWith(key) .compact(); }
public String extractUsername(String token) { return Jwts.parser() .verifyWith(key) .build() .parseSignedClaims(token) .getPayload() .getSubject(); }}Click Run to see what this code prints.
The example above generates a new signing key every time the application starts — every previously issued token becomes invalid on restart. In a real application, load the signing key from a fixed secret in configuration (or a secrets manager), never generate it inline.
Which One Do You Need?
| Dependency | Role | Typical Use Case |
|---|---|---|
| spring-boot-starter-oauth2-client | Consumes another provider's login | "Login with Google/GitHub" buttons |
| spring-boot-starter-oauth2-resource-server | Validates incoming tokens | Protecting an API with Bearer tokens |
| io.jsonwebtoken (jjwt) | Issues your own tokens | A custom login endpoint that returns a JWT |
It is common to combine the resource-server starter with jjwt: your own /login endpoint issues a JWT using jjwt, and every other endpoint is protected by the resource-server starter, which validates that same token on the way back in.
Common Mistakes
- Confusing OAuth2 client (logging your app in via Google) with OAuth2 resource server (validating tokens on your own API) — they are opposite ends of very different flows.
- Storing JWTs in localStorage where they are exposed to XSS, instead of an HttpOnly cookie.
- Setting no expiration on issued tokens, so a stolen token remains valid forever.
- Hardcoding a signing key as a literal string instead of loading it from configuration.
Frequently Asked Questions
Yes, jjwt is a plain Java library with no dependency on Spring Security — it works in any Java application that needs to create or read JWTs.
Only if your app both logs users in via an external provider and also exposes an API that other services call with tokens. Many apps only need one.
No — by default a JWT is signed, not encrypted. Anyone can decode and read its payload; the signature only proves it has not been tampered with. Never put secrets in the claims.
Summary
OAuth2 client handles logging your users in through someone else, OAuth2 resource server handles verifying tokens on the way into your API, and jjwt lets you issue tokens yourself when you are the identity provider. Next, we look at validating request data before it ever reaches your business logic.