Projekt

Obecné

Profil

« Předchozí | Další » 

Revize fc79a8cb

Přidáno uživatelem Václav Honzík před téměř 3 roky(ů)

re #9368 jwt simple login

Zobrazit rozdíly:

backend/src/main/java/cz/zcu/kiv/backendapi/security/SecurityConfig.java
17 17
import org.springframework.security.config.http.SessionCreationPolicy;
18 18
import org.springframework.security.core.userdetails.UserDetailsService;
19 19
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
20
import org.springframework.web.cors.CorsConfiguration;
21
import org.springframework.web.cors.CorsConfigurationSource;
22
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
20 23

  
21 24
/**
22 25
 * Security config class
......
54 57
     */
55 58
    @Override
56 59
    protected void configure(HttpSecurity http) throws Exception {
57
        http.
58
                csrf().disable()
60
        http.csrf().disable()
61
                .cors()
62
                .and()
59 63
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
60 64
                .and()
61 65
                .addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager(), jwtUtils))
......
69 73
                .antMatchers("/delete/**").hasAuthority(Permission.DELETE.name())
70 74
                .anyRequest()
71 75
                .authenticated();
72

  
73 76
    }
74 77

  
75 78
    /**
......
95 98
        provider.setPasswordEncoder(bCryptPasswordEncoder);
96 99
        return provider;
97 100
    }
101

  
102
    @Bean
103
    CorsConfigurationSource corsConfigurationSource() {
104
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
105
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
106
        return source;
107
    }
98 108
}
frontend/package.json
13 13
    "axios": "^0.26.0",
14 14
    "dotenv": "^16.0.0",
15 15
    "formik": "^2.2.9",
16
    "jwt-decode": "^3.1.2",
16 17
    "react": "^17.0.2",
17 18
    "react-dom": "^17.0.2",
18 19
    "react-redux": "^7.2.6",
frontend/src/features/Auth/Login.tsx
5 5
import { useNavigate } from 'react-router-dom'
6 6
import * as yup from 'yup'
7 7
import { SchemaOf } from 'yup'
8
import { PasswordDto, UserDto } from '../../swagger/data-contracts'
9 8
import { RootState } from '../redux/store'
10 9
import { logIn } from './userThunks'
11 10

  
......
30 29
        onSubmit: () => {
31 30
            dispatch(
32 31
                logIn({
33
                    name: formik.values.username,
34
                    passwords: {
35
                        password: formik.values.password,
36
                        confirmationPassword: '',
37
                    } as PasswordDto,
38
                } as UserDto)
32
                    username: formik.values.username,
33
                    password: formik.values.password,
34
                })
39 35
            )
40 36
        },
41 37
    })
frontend/src/features/Auth/userSlice.ts
41 41
            ...state,
42 42
            lastErr: action.payload,
43 43
        }),
44
        setUserState: (state, action) => {
45
            console.log('user state set')
46
            return ({ ...state, ...action.payload })
47
        },
44 48
    },
45 49

  
46 50
    // Thunks
47 51
    extraReducers: (builder) => {
48
        builder.addCase(logIn.fulfilled, () => {
49
            console.log('Action performed') // TODO remove
50
        }) // TODO funny
52
        builder.addCase(logIn.fulfilled, (state, action) => {
53
            return ({ ...state, ...action.payload })
54
        })
55
        builder.addCase(logIn.rejected, (state, action) => {
56
            if (action.payload && typeof action.payload === 'string') {
57
                return ({ ...state, lastErr: action.payload })
58
            }
59
        })
51 60
    },
52 61
})
53 62

  
54 63
const userReducer = persistReducer(persistConfig, userSlice.reducer)
55 64

  
65
export const { logout, refreshTokens, setErr, setUserState } = userSlice.actions
66

  
56 67
export default userReducer
frontend/src/features/Auth/userThunks.ts
1 1
import { createAsyncThunk } from '@reduxjs/toolkit'
2 2
import axiosInstance from '../../api/api'
3 3
import { UserDto } from '../../swagger/data-contracts'
4
import { setErr, setUserState, UserState } from './userSlice'
5
import jwt from 'jwt-decode' 
6
import { RootState } from '../redux/store'
4 7

  
5 8
const loginError =
6 9
    'Server error occurred while logging in. Please contact help service to resolve this issue or try again later.'
7 10

  
11
// This is not present in the swagger since spring generates
12
export interface UserLogin {
13
    username: string,
14
    password: string
15
}
8 16

  
9 17
export const logIn = createAsyncThunk(
10 18
    'user/login',
11
    async (userDto: UserDto, { dispatch, getState }) => {
19
    async (userDto: UserLogin, { dispatch, getState }) => {
12 20
        try {
13 21
            // @ts-ignore
14
            // TODO fix
15 22
            if (getState().user.isLoggedIn) {
16
                return
23
                return Promise.reject(undefined)
17 24
            }
18
            console.log('Dispatching login thunk') // TODO remove
25

  
19 26
            const { data, status } = await axiosInstance.post('/login', userDto)
20
            const { accessToken, refreshToken } = data
21
            console.log(data) // TODO remove
27
            const [ accessToken, refreshToken ] = [data.access_token, data.refresh_token]
22 28
            if (status !== 200) {
23 29
                // TODO read API err
24
                dispatch({ type: 'user/setErr', payload: loginError })
25
                return
30
                return Promise.reject(loginError)
31
            }
32

  
33
            // Strip bearer from access token
34
            const userInfo = jwt(accessToken.replace('Bearer ', '')) as any
35
            const { sub, authorities } = userInfo
36
            if (!sub || !authorities) {
37
                return Promise.reject(loginError)
26 38
            }
27 39

  
28
            dispatch({ type: 'user/refreshTokens', payload: { accessToken, refreshToken } })
40
            const userState: UserState = {
41
                accessToken,
42
                refreshToken,
43
                username: sub,
44
                roles: authorities,
45
                isLoggedIn: true
46
            }
47
            
48
            return userState
29 49
        } catch (err: any) {
30
            dispatch({ type: 'user/setErr', payload: loginError })
50
            console.log(err)
51
            return Promise.reject(loginError)
31 52
        }
32 53
    }
33 54
)
frontend/src/features/Home/Home.tsx
1
import { Button } from '@mui/material'
2
import { useDispatch, useSelector } from 'react-redux'
3
import { logout } from '../Auth/userSlice'
4
import { RootState } from '../redux/store'
5

  
1 6
const Home = () => {
7
    const dispatch = useDispatch()
8

  
9
    const userLoggedIn = useSelector(
10
        (state: RootState) => state.user.isLoggedIn
11
    )
2 12

  
3
    return (<>
4
    <h1>Home</h1>
5
    </>)
13
    return (
14
        <>
15
            <h1>Home</h1>
16
            {userLoggedIn ? (
17
                <Button
18
                    size="large"
19
                    variant="contained"
20
                    color="primary"
21
                    onClick={() => dispatch(logout())}
22
                >
23
                    Logout
24
                </Button>
25
            ) : null}
26
        </>
27
    )
6 28
}
7 29

  
8
export default Home
30
export default Home
frontend/src/utils/ApiCallError.ts
1
export default class ApiCallError extends Error {}
frontend/src/utils/thunkResponse.ts
1

  
2
export default interface ThunkResponse<T> {
3
    success: boolean
4
    data?: T
5
}

Také k dispozici: Unified diff