O SchoolAPI agora está organizado como um repositório full stack, com backend Spring Boot em backend/ e frontend Next.js em frontend/. Além do domínio acadêmico, o projeto também serve como laboratório de OAuth2/OIDC com Keycloak, usando a API como Resource Server protegido por JWT e um frontend React para explorar os endpoints.
- Java 21
- Spring Boot
- Spring Web MVC
- Spring Data JPA
- Spring Security
- OAuth2 Resource Server
- PostgreSQL
- Keycloak
- Swagger / OpenAPI
- Maven
- Docker Compose
| Pasta | Responsabilidade |
|---|---|
backend/ |
API Spring Boot, testes, Dockerfile e realm do Keycloak |
frontend/ |
App Next.js com hooks, providers, routes e Route Handlers para integração |
markdowns/ |
documentação de endpoints e relatório de estudo |
compose.yaml |
infraestrutura local de PostgreSQL e Keycloak |
- Controllers + Services + Repositories: separação clara entre entrada HTTP, regra de negócio e persistência.
- DTOs: controle do formato de entrada e saída da API.
@PreAuthorize: regras de autorização por papel na camada de serviço.- Resource Server stateless: a API não cria sessão nem faz login local; ela valida Bearer Tokens emitidos pelo Keycloak.
- Tratamento padronizado de erro: respostas 401/403 e erros da aplicação seguem payload consistente.
O projeto usa Keycloak como provedor OAuth2/OIDC e o SchoolAPI como OAuth2 Resource Server.
Na prática isso significa:
- O usuário faz login no Keycloak.
- O Keycloak emite um access token JWT.
- O cliente envia
Authorization: Bearer <token>para a API. - O Spring Security valida assinatura, emissor e expiração do token.
- As roles do token são convertidas em authorities do Spring, como
ROLE_ADMINeROLE_TEACHER. - A API libera ou bloqueia acesso conforme as regras configuradas.
A API não usa formLogin() nem httpBasic() para autenticar usuários.
Ela também não emite JWT próprio neste fluxo de estudo; quem emite o token é o Keycloak.
OAuth2 é um protocolo de autorização. Ele define como uma aplicação obtém um token para acessar um recurso protegido.
Neste projeto:
- Keycloak atua como Authorization Server / Identity Provider
- SchoolAPI atua como Resource Server
OpenID Connect (OIDC) é uma camada de identidade sobre OAuth2.
É ele que padroniza informações sobre o usuário autenticado, como sub, preferred_username, email e metadados do emissor.
O JWT (JSON Web Token) é o formato do access token usado aqui.
Ele carrega claims como:
subpreferred_usernameexpiatissrealm_access.roles
O token é assinado pelo Keycloak e validado pela API usando a chave pública exposta pelo provedor.
| Conceito | Arquivo | O que estudar |
|---|---|---|
| Configuração principal da segurança | backend/src/main/java/digu_dev/com/github/SchoolAPI/config/SecurityConfig.java |
SecurityFilterChain, regras por endpoint, modo stateless, JWT, handlers 401/403 |
| Conversão de roles do Keycloak | backend/src/main/java/digu_dev/com/github/SchoolAPI/security/KeycloakRealmRoleConverter.java |
leitura de realm_access.roles e conversão para ROLE_* |
| Endpoint didático do usuário autenticado | backend/src/main/java/digu_dev/com/github/SchoolAPI/controller/AuthController.java |
como acessar claims e authorities do token já validado |
| DTO de resposta do usuário autenticado | backend/src/main/java/digu_dev/com/github/SchoolAPI/dto/AuthenticatedUserResponse.java |
estrutura de retorno do /auth/me |
| Regras por papel na camada de negócio | backend/src/main/java/digu_dev/com/github/SchoolAPI/service/*.java |
uso de @PreAuthorize |
| Erro padronizado | backend/src/main/java/digu_dev/com/github/SchoolAPI/exception/StandardError.java |
formato dos erros retornados |
| Infra local do Keycloak | compose.yaml e backend/infra/keycloak/school-realm.json |
containers, realm, client, roles e usuários de estudo |
| Testes de segurança | backend/src/test/java/digu_dev/com/github/SchoolAPI/tests/controller |
uso de JWT simulado com spring-security-test |
| Área | Regra |
|---|---|
| Swagger / OpenAPI | público |
/auth/me |
qualquer usuário autenticado |
/users/** |
ADMIN |
/teachers/** |
ADMIN |
/gpa/** |
TEACHER |
/departments/**, /students/**, /subjects/**, /school-classes/** |
ADMIN ou TEACHER |
Além disso, a camada de serviço também reforça autorização com @PreAuthorize.
Na raiz do projeto:
docker compose up -dServiços disponíveis:
- PostgreSQL:
localhost:5431 - Keycloak:
http://localhost:8180
Acesso ao console do Keycloak
- usuário:
admin - senha:
admin
Usuários do realm school
| Usuário | Senha | Role |
|---|---|---|
admin |
admin123 |
admin |
teacher |
teacher123 |
teacher |
No Windows:
cd backend
.\mvnw.cmd spring-boot:runO backend ficará disponível em:
http://localhost:8080
Em outro terminal:
cd frontend
npm run devO frontend ficará disponível em:
http://localhost:3000
Terminal 1 — infraestrutura
docker compose up -dTerminal 2 — backend
cd backend
.\mvnw.cmd spring-boot:runTerminal 3 — frontend
cd frontend
npm run dev- Swagger UI:
http://localhost:8080/swagger-ui/index.html - Frontend Next.js:
http://localhost:3000
Para estudo local, o client school-api foi configurado com Direct Access Grants habilitado.
curl -X POST "http://localhost:8180/realms/school/protocol/openid-connect/token" ^
-H "Content-Type: application/x-www-form-urlencoded" ^
-d "client_id=school-api" ^
-d "username=admin" ^
-d "password=admin123" ^
-d "grant_type=password"curl -X POST "http://localhost:8180/realms/school/protocol/openid-connect/token" ^
-H "Content-Type: application/x-www-form-urlencoded" ^
-d "client_id=school-api" ^
-d "username=teacher" ^
-d "password=teacher123" ^
-d "grant_type=password"O campo importante da resposta é access_token.
Observação didática: o grant type
passwordé útil para laboratório e entendimento do fluxo, mas não é a melhor opção para produção moderna.
Depois de copiar o access_token:
curl http://localhost:8080/auth/me ^
-H "Authorization: Bearer SEU_TOKEN"Esse endpoint existe justamente para estudo. Ele mostra:
- username resolvido
- subject
- issuer
- datas do token
- authorities do Spring Security
- claims relevantes do JWT
Exemplo de uso em endpoint protegido:
curl http://localhost:8080/departments ^
-H "Authorization: Bearer SEU_TOKEN"Abra SecurityConfig.java e leia nesta ordem:
sessionCreationPolicy(STATELESS)authorizeHttpRequests(...)oauth2ResourceServer(...)- handlers de
401e403 - bean
JwtAuthenticationConverter
Abra KeycloakRealmRoleConverter.java.
Ali acontece um ponto central:
- o código lê
realm_access.roles - converte cada role em
ROLE_* - entrega authorities para o Spring Security
Exemplo:
- Keycloak envia
admin - a aplicação converte para
ROLE_ADMIN hasRole("ADMIN")passa a funcionar
Autenticação
- verificar se o token é válido
- conferir assinatura, emissor e expiração
Autorização
- decidir o que aquele usuário pode fazer
- isso acontece nas regras HTTP e em
@PreAuthorize
Abra AuthController.java e faça chamadas para /auth/me.
Esse endpoint é importante para visualizar:
- claims do JWT
- principal resolvido
- authorities finais que o Spring montou
Veja:
SecurityConfig.javaTeacherService.javaDepartmentService.javaStudentService.javaSubjectService.javaSchoolClassService.javaGPAService.java
Você vai notar duas camadas:
- segurança no endpoint
- segurança na regra de negócio
Isso ajuda a entender defesa em profundidade.
Nos testes de controller, veja o uso de JWT falso com spring-security-test.
Arquivos úteis:
ControllerSecurityTestSupport.javaAuthControllerTest.javaTeacherControllerTest.java
Esses testes mostram:
- como simular
ROLE_ADMIN - como simular
ROLE_TEACHER - como validar
401,403e200
- Bearer Token substitui sessão
- JWT não precisa ser gerado pela API para a API usar JWT
- Resource Server valida token; Authorization Server emite token
- roles do provedor precisam ser mapeadas para authorities da aplicação
@EnableMethodSecurityé necessário para@PreAuthorizefuncionar- não exponha senha em responses
- 401 e 403 são erros diferentes
401: sem autenticação válida403: autenticado, mas sem permissão
Os endpoints /users continuam no projeto, mas a autenticação OAuth2/OIDC deste estudo vem do Keycloak.
Ou seja: o login real do fluxo estudado não depende do cadastro local de usuários da aplicação.
Esses endpoints foram mantidos como referência de CRUD interno e agora não expõem mais senha nas respostas.
Se quiser aprofundar depois desta base:
- adicionar validação de
audience - estudar refresh token
- trocar
password grantpor fluxo com Authorization Code + PKCE - integrar Swagger com OAuth2 login
- adicionar testes de integração com token real emitido pelo Keycloak