Skip to content

Commit facdbe3

Browse files
committed
readme
1 parent 72ca905 commit facdbe3

1 file changed

Lines changed: 193 additions & 1 deletion

File tree

README.md

Lines changed: 193 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,195 @@
11
# JWT-Authentication
22

3-
Springboot JWT Authentication
3+
This service issues short-lived access tokens and long-lived refresh tokens using RSA. You can use the access tokens to protect other services (resource servers).
4+
5+
Quick facts:
6+
- Signing algorithm: RSA (asymmetric); downstream services validate with the public key only.
7+
- Issuer: `myApp` (embedded in the `iss` claim).
8+
- Access token lifetime: 5 minutes.
9+
- Refresh token lifetime: 30 days.
10+
- JWKS endpoint for public key discovery: `GET /.well-known/jwks.json`
11+
12+
## Endpoints in this service
13+
- TODO
14+
15+
## Using these JWTs to protect another service (Spring Boot Resource Server)
16+
If you have another Spring Boot application that you want to protect using tokens from this service, configure it as an OAuth2 Resource Server.
17+
18+
Option A: Validate via JWKS endpoint (recommended)
19+
1. Expose this auth service to your network (e.g., http://auth.local:8080).
20+
2. In the resource server application, add Spring Security OAuth2 Resource Server dependency.
21+
3. Configure `spring.security.oauth2.resourceserver.jwt.jwk-set-uri` to point to this service's JWKS.
22+
23+
Example `application.yml` in the protected service:
24+
25+
```
26+
spring:
27+
security:
28+
oauth2:
29+
resourceserver:
30+
jwt:
31+
jwk-set-uri: http://auth.local:8080/.well-known/jwks.json
32+
```
33+
34+
And a basic security configuration plus issuer validation:
35+
36+
```
37+
@Configuration
38+
@EnableMethodSecurity
39+
public class SecurityConfig {
40+
@Bean
41+
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
42+
http
43+
.authorizeHttpRequests(reg -> reg
44+
.requestMatchers("/actuator/health").permitAll()
45+
.anyRequest().authenticated()
46+
)
47+
.oauth2ResourceServer(oauth -> oauth.jwt())
48+
.csrf(AbstractHttpConfigurer::disable)
49+
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
50+
return http.build();
51+
}
52+
53+
@Bean
54+
JwtDecoder jwtDecoder() {
55+
NimbusJwtDecoder decoder = NimbusJwtDecoder
56+
.withJwkSetUri("http://auth.local:8080/.well-known/jwks.json")
57+
.build();
58+
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer("myApp");
59+
decoder.setJwtValidator(withIssuer);
60+
return decoder;
61+
}
62+
}
63+
```
64+
65+
Option B: Validate with a static public key
66+
1. Download the public JWK from `/.well-known/jwks.json` and convert to PEM if desired, or embed the JWK.
67+
2. Configure your resource server with the public key instead of JWKS URI.
68+
69+
## Node.js/Express example
70+
```
71+
import express from 'express';
72+
import jwt from 'jsonwebtoken';
73+
import jwksRsa from 'jwks-rsa';
74+
75+
const app = express();
76+
const jwksClient = jwksRsa({
77+
jwksUri: 'http://auth.local:8080/.well-known/jwks.json',
78+
cache: true,
79+
cacheMaxEntries: 5,
80+
cacheMaxAge: 10 * 60 * 1000,
81+
});
82+
83+
function getKey(header, callback) {
84+
jwksClient.getSigningKey(header.kid, (err, key) => {
85+
if (err) return callback(err);
86+
const signingKey = key.getPublicKey();
87+
callback(null, signingKey);
88+
});
89+
}
90+
91+
app.use((req, res, next) => {
92+
const auth = req.headers.authorization || '';
93+
const token = auth.startsWith('Bearer ') ? auth.substring(7) : null;
94+
if (!token) return res.status(401).send('Missing token');
95+
96+
jwt.verify(token, getKey, { algorithms: ['RS256'], issuer: 'myApp' }, (err, decoded) => {
97+
if (err) return res.status(401).send('Invalid token');
98+
req.user = decoded;
99+
next();
100+
});
101+
});
102+
103+
app.get('/protected', (req, res) => res.json({ ok: true, sub: req.user.sub }));
104+
app.listen(3000);
105+
```
106+
107+
## Protecting controllers by authorities/roles in your resource server
108+
This auth service now includes an `authorities` claim in the access token, containing the user's Spring Security authorities (for example: `ROLE_ADMIN`, `ROLE_USER`).
109+
110+
In your downstream Spring Boot resource server, configure a JwtAuthenticationConverter to read that claim and convert it to GrantedAuthorities:
111+
112+
```
113+
@Configuration
114+
@EnableMethodSecurity
115+
public class SecurityConfig {
116+
@Bean
117+
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
118+
http
119+
.authorizeHttpRequests(reg -> reg
120+
.requestMatchers("/actuator/health").permitAll()
121+
.requestMatchers(HttpMethod.GET, "/admin/**").hasRole("ADMIN")
122+
.anyRequest().authenticated()
123+
)
124+
.oauth2ResourceServer(oauth -> oauth.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())))
125+
.csrf(AbstractHttpConfigurer::disable)
126+
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
127+
return http.build();
128+
}
129+
130+
@Bean
131+
Converter<Jwt, ? extends AbstractAuthenticationToken> jwtAuthenticationConverter() {
132+
JwtGrantedAuthoritiesConverter delegate = new JwtGrantedAuthoritiesConverter();
133+
delegate.setAuthoritiesClaimName("authorities");
134+
// Our authorities already include the full value (e.g., ROLE_ADMIN), so no extra prefix
135+
delegate.setAuthorityPrefix("");
136+
137+
return jwt -> {
138+
Collection<GrantedAuthority> authorities = delegate.convert(jwt);
139+
return new JwtAuthenticationToken(jwt, authorities);
140+
};
141+
}
142+
}
143+
```
144+
145+
You can then protect controllers or methods using annotations:
146+
- `@PreAuthorize("hasRole('ADMIN')")`
147+
- `@PreAuthorize("hasAuthority('ROLE_ADMIN')")`
148+
149+
If you prefer to use scopes instead, add a `scope` or `scp` claim in the token and configure `JwtGrantedAuthoritiesConverter` accordingly.
150+
151+
## Accessing roles from JWT in your downstream controllers
152+
Below are simple patterns you can use inside your protected service to pull roles from the JWT and pass them to your own service methods.
153+
154+
Option A: Inject Authentication and read GrantedAuthorities
155+
156+
```
157+
@RestController
158+
@RequestMapping("/api/example")
159+
public class ExampleController {
160+
private final MyService myService;
161+
public ExampleController(MyService myService) { this.myService = myService; }
162+
163+
@GetMapping("/do")
164+
public ResponseEntity<?> doSomething(Authentication authentication) {
165+
// If you've configured JwtGrantedAuthoritiesConverter as in the README,
166+
// authorities already come from the JWT "authorities" claim.
167+
List<String> roles = authentication.getAuthorities()
168+
.stream()
169+
.map(GrantedAuthority::getAuthority)
170+
.toList();
171+
172+
Object result = myService.doSomethingWithRoles(roles);
173+
return ResponseEntity.ok(result);
174+
}
175+
}
176+
```
177+
178+
Option B: Inject Jwt directly with @AuthenticationPrincipal
179+
180+
```
181+
@RestController
182+
@RequestMapping("/api/example2")
183+
public class Example2Controller {
184+
private final MyService myService;
185+
public Example2Controller(MyService myService) { this.myService = myService; }
186+
187+
@GetMapping("/do")
188+
public ResponseEntity<?> doSomething(@AuthenticationPrincipal Jwt jwt) {
189+
// Read raw claim from token
190+
List<String> roles = jwt.getClaimAsStringList("authorities");
191+
Object result = myService.doSomethingWithRoles(roles);
192+
return ResponseEntity.ok(result);
193+
}
194+
}
195+
```

0 commit comments

Comments
 (0)