This repository demonstrates stateless JWT authentication and role-based authorization in a Spring Boot application. This README focuses on what security is implemented, how it works, how to test it, and quick fixes for common issues (especially around /signin and the H2 console).
- Authentication: POST
/signinaccepts credentials and returns a JWT (Bearer token). - Authorization: protected endpoints require a valid JWT in the
Authorization: Bearer <token>header. - Passwords: stored and verified with BCrypt using a shared
PasswordEncoderbean. - Filters: custom
AuthTokenFilterinspects requests for JWTs and sets authentication when valid. - Error handling:
AuthEntryPointreturns JSON 401 responses for unauthorized requests.
src/main/java/bank/springdemo/jwt/jwtUtils.java— create/validate JWTs and extract username.src/main/java/bank/springdemo/jwt/AuthTokenFilter.java—OncePerRequestFilterthat reads theAuthorizationheader, validates the token, and sets authentication.src/main/java/bank/springdemo/jwt/AuthEntryPoint.java— returns JSON error responses for 401s.src/main/java/bank/springdemo/jwt/LoginRequest.java— DTO for signin payload (maps incomingusernameJSON key to the DTO field).src/main/java/bank/springdemo/controller/GreetingController.java— containsPOST /signinand example protected endpoints.src/main/java/bank/springdemo/SecurityConfigurations/SecurityConfigs.java— security configuration (filter chain,permitAllpaths,PasswordEncoder).
- Client POSTs credentials to
POST /signin(JSON body). - Controller authenticates via
AuthenticationManager. - On success the server creates a JWT via
jwtUtilsand returns it as JSON (includes token, username and roles). - Client includes
Authorization: Bearer <token>on subsequent requests to protected endpoints. AuthTokenFiltervalidates the token and sets authentication in theSecurityContext.
- JSON field binding: the code uses a DTO field
userNamebut clients commonly sendusername. The project mapsusername→userNameusing@JsonProperty, so sending:
{ "username": "user1", "password": "password1" }with header Content-Type: application/json works.
-
Auth filter ordering:
AuthTokenFilterruns early in the filter chain. It should not reject a request just because there's no token; it should only populate theSecurityContextwhen a valid token is present. If the filter calls theAuthenticationEntryPointon missing/invalid tokens for all paths, unprotected endpoints such as/signinor/h2-console/**will be blocked with 401. -
H2 console: the H2 console (
/h2-console) requires frame support and must be explicitly permitted by your security configuration. If you see a 401 or a JSON error at/h2-console, either the endpoint is not permitted or the auth filter rejected the request beforepermitAll()took effect. To allow it, permit/h2-console/**and addhttp.headers().frameOptions().disable()in your security config.
- Sign in (Postman or curl). Header
Content-Type: application/json, body:
{
"username": "user1",
"password": "password1"
}Curl example:
curl -i -H "Content-Type: application/json" -d '{"username":"user1","password":"password1"}' http://localhost:8080/signin- Use returned token to call a protected endpoint:
curl -i -H "Authorization: Bearer <TOKEN>" http://localhost:8080/user/hello- H2 console: open
http://localhost:8080/h2-consoleafter ensuring security config permits it. If the console shows a 401 JSON, check the filter behavior and security config.
-
Bad Credentials / 401 on
/signin:- Cause: request JSON not bound (field name mismatch) or
Content-Typemissing. Fix: sendContent-Type: application/jsonand keys matching the DTO (this project mapsusernameto the DTO field). - Cause:
AuthenticationManagercannot find the user or password encoder mismatch. Fix: ensure seeded users exist and use BCrypt.
- Cause: request JSON not bound (field name mismatch) or
-
401 on
/h2-console:- Cause:
AuthTokenFilterblocked the request beforepermitAll()applied. Fix: make the filter ignore requests with no Authorization header (i.e., skip token parsing when header is absent) or explicitly permit/h2-console/**and disable frame options inSecurityConfigs.
- Cause:
-
Token validation errors:
- Cause: wrong signing key, malformed token, expired token. Fix: check
spring.app.jwtSecretandspring.app.jwtExpirations_msinapplication.propertiesand inspect logs fromjwtUtils.
- Cause: wrong signing key, malformed token, expired token. Fix: check
- Ensure DTO binding: map
usernameJSON key to your DTO (already done here using@JsonProperty). - Make
AuthTokenFiltertolerant of no-token requests: only setSecurityContextwhen a valid token is present; do not callAuthEntryPointfor absent tokens on unprotected endpoints. - In
SecurityConfigs, explicitlypermitAll()forPOST /signinand"/h2-console/**"and disableframeOptions. - Use a single
PasswordEncoderbean (BCrypt) everywhere (user seeding + authentication).
src/main/java/bank/springdemo/jwt/AuthTokenFilter.java— see how the token is parsed and whether missing tokens cause immediate errors.src/main/java/bank/springdemo/jwt/AuthEntryPoint.java— JSON structure and messages for 401 responses.src/main/java/bank/springdemo/jwt/jwtUtils.java— signing/verification logic.src/main/java/bank/springdemo/SecurityConfigurations/SecurityConfigs.java— permitted paths and filter chain ordering.src/main/java/bank/springdemo/controller/GreetingController.java— thePOST /signinimplementation.