Skip to content

Commit

Permalink
Spring Security + JWT Example
Browse files Browse the repository at this point in the history
  • Loading branch information
jlzhjp committed Nov 22, 2024
1 parent 73e75a5 commit 5e9bd9a
Show file tree
Hide file tree
Showing 59 changed files with 1,985 additions and 0 deletions.
3 changes: 3 additions & 0 deletions spring-jwt/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/gradlew text eol=lf
*.bat text eol=crlf
*.jar binary
37 changes: 37 additions & 0 deletions spring-jwt/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/

### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

### VS Code ###
.vscode/
43 changes: 43 additions & 0 deletions spring-jwt/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
plugins {
java
id("org.springframework.boot") version "3.3.5"
id("io.spring.dependency-management") version "1.1.6"
}

group = "io.github.jlzhjp"
version = "0.0.1-SNAPSHOT"

java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}

configurations {
compileOnly {
extendsFrom(configurations.annotationProcessor.get())
}
}

repositories {
mavenCentral()
}

dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("io.jsonwebtoken:jjwt-impl:0.11.5")
implementation("io.jsonwebtoken:jjwt-api:0.11.5")
implementation("io.jsonwebtoken:jjwt-jackson:0.11.5")
compileOnly("org.projectlombok:lombok")
runtimeOnly("com.h2database:h2")
annotationProcessor("org.projectlombok:lombok")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.withType<Test> {
useJUnitPlatform()
}
24 changes: 24 additions & 0 deletions spring-jwt/frontend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
3 changes: 3 additions & 0 deletions spring-jwt/frontend/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"semi": false
}
50 changes: 50 additions & 0 deletions spring-jwt/frontend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:

- Configure the top-level `parserOptions` property like this:

```js
export default tseslint.config({
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
})
```

- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
- Optionally add `...tseslint.configs.stylisticTypeChecked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:

```js
// eslint.config.js
import react from 'eslint-plugin-react'

export default tseslint.config({
// Set the react version
settings: { react: { version: '18.3' } },
plugins: {
// Add the react plugin
react,
},
rules: {
// other rules...
// Enable its recommended rules
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
},
})
```
Binary file added spring-jwt/frontend/bun.lockb
Binary file not shown.
30 changes: 30 additions & 0 deletions spring-jwt/frontend/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import js from "@eslint/js"
import globals from "globals"
import reactHooks from "eslint-plugin-react-hooks"
import reactRefresh from "eslint-plugin-react-refresh"
import reactCompiler from "eslint-plugin-react-compiler"
import tseslint from "typescript-eslint"

export default tseslint.config(
{ ignores: ["dist"] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ["**/*.{ts,tsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
"react-compiler": reactCompiler,
},
rules: {
...reactHooks.configs.recommended.rules,
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
},
}
)
13 changes: 13 additions & 0 deletions spring-jwt/frontend/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
42 changes: 42 additions & 0 deletions spring-jwt/frontend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@tanstack/react-query": "^5.61.0",
"react": "^19.0.0-rc.1",
"react-dom": "^19.0.0-rc.1",
"react-router-dom": "^7.0.0-pre.6"
},
"overrides": {
"@types/react": "npm:types-react@rc",
"@types/react-dom": "npm:types-react-dom@rc"
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@tanstack/eslint-plugin-query": "^5.60.1",
"@types/react": "npm:types-react@rc",
"@types/react-dom": "npm:types-react-dom@rc",
"@vitejs/plugin-react": "^4.3.3",
"autoprefixer": "^10.4.20",
"babel-plugin-react-compiler": "^19.0.0-beta-0dec889-20241115",
"daisyui": "^4.12.14",
"eslint": "^9.13.0",
"eslint-plugin-react-compiler": "^19.0.0-beta-0dec889-20241115",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.14",
"globals": "^15.11.0",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.15",
"typescript": "~5.6.2",
"typescript-eslint": "^8.11.0",
"vite": "^5.4.10"
}
}
1 change: 1 addition & 0 deletions spring-jwt/frontend/public/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions spring-jwt/frontend/src/assets/react.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions spring-jwt/frontend/src/components/Preloader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Preloader() {
return (
<div className="flex items-center justify-center">
<span className="loading loading-dots loading-sm"></span>
</div>
)
}
3 changes: 3 additions & 0 deletions spring-jwt/frontend/src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
33 changes: 33 additions & 0 deletions spring-jwt/frontend/src/lib/AuthorizationProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useState, type ReactNode } from "react"
import { createContext } from "react"

type AuthorizationInfo = {
token: string
}

type AuthorizationContextType = {
auth: AuthorizationInfo | undefined
setAuth: (info: AuthorizationInfo | undefined) => void
}

const AuthorizationContext = createContext<AuthorizationContextType>({
setAuth: () => {},
auth: undefined,
})

const AuthorizationProvider = ({ children }: { children?: ReactNode }) => {
const [authorizationInfo, setAuthorizationInfo] = useState<
AuthorizationInfo | undefined
>(undefined)

return (
<AuthorizationContext
value={{ auth: authorizationInfo, setAuth: setAuthorizationInfo }}
>
{children}
</AuthorizationContext>
)
}

export default AuthorizationProvider
export { AuthorizationContext }
63 changes: 63 additions & 0 deletions spring-jwt/frontend/src/lib/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
ApiError,
AuthenticationRequest,
AuthenticationResponse,
RegisterRequest,
UserInfoResponse,
} from "./types"

export const getUser = async (token: string) => {
const res = await fetch("http://localhost:8080/user", {
headers: {
Authorization: `Bearer ${token}`,
},
})
const user: UserInfoResponse = await res.json()
return user
}

export const getNotice = async (token: string) => {
const res = await fetch("http://localhost:8080/notice", {
headers: {
Authorization: `Bearer ${token}`,
},
})
const notice = await res.text()
return notice
}

export const login = async (request: AuthenticationRequest) => {
const res = await fetch("http://localhost:8080/auth/authenticate", {
method: "POST",
body: JSON.stringify(request),
headers: {
"Content-Type": "application/json",
},
})

if (!res.ok) {
const error = (await res.json()) as ApiError
throw error
}

const data = (await res.json()) as AuthenticationResponse
return data
}

export const signup = async (request: RegisterRequest) => {
const res = await fetch("http://localhost:8080/auth/signup", {
method: "POST",
body: JSON.stringify(request),
headers: {
"Content-Type": "application/json",
},
})

if (!res.ok) {
const error = (await res.json()) as ApiError
throw error
}

const data = (await res.json()) as AuthenticationResponse
return data
}
Loading

0 comments on commit 5e9bd9a

Please sign in to comment.