| 158 | | |
| | 158 | Најавата на системот е имплементирана директно на серверската апликација односно на адресата http://localhost:8080/login, каде што се прикажува страницата од сликата. За реализација на ова сценарио користиме Spring Security каде синџирот филтри е соодветно конфигуриран да одговори на нашите потреби, односно да ги филтрира барањата во зависност од тоа дали корисницте се автентицирани, имаат одредена привилегија или пристапуваат до нивен ресурс. |
| | 159 | {{{#!java |
| | 160 | @Bean |
| | 161 | public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
| | 162 | http |
| | 163 | .csrf().disable() |
| | 164 | .authorizeHttpRequests((authz) -> { |
| | 165 | try { |
| | 166 | authz |
| | 167 | .requestMatchers(new AntPathRequestMatcher("/{userId}/hasBusiness")).access(userSecurity) |
| | 168 | .requestMatchers(new AntPathRequestMatcher("/business/*/unapproved")).authenticated() |
| | 169 | .requestMatchers(new AntPathRequestMatcher("/register")).permitAll() |
| | 170 | .requestMatchers(new AntPathRequestMatcher("/hotel/search")).permitAll() |
| | 171 | .requestMatchers(new AntPathRequestMatcher("/transport/search")).permitAll() |
| | 172 | .requestMatchers(new AntPathRequestMatcher("/restaurant/search")).permitAll() |
| | 173 | .requestMatchers(new AntPathRequestMatcher("/upload")).permitAll() |
| | 174 | .requestMatchers(new AntPathRequestMatcher("/business/approve/*")).hasAnyAuthority("SUPERADMIN") |
| | 175 | .requestMatchers(new AntPathRequestMatcher("/users/unlock/*")).hasAnyAuthority("SUPERADMIN") |
| | 176 | .requestMatchers(new AntPathRequestMatcher("/users/approve/*")).hasAnyAuthority("SUPERADMIN") |
| | 177 | .requestMatchers(new AntPathRequestMatcher("/business/unapproved")).hasAnyAuthority("SUPERADMIN") |
| | 178 | .requestMatchers(new AntPathRequestMatcher("/business/add/*")).authenticated() |
| | 179 | .requestMatchers(new AntPathRequestMatcher("/*/user/{userId}")).access(userSecurity) |
| | 180 | .anyRequest().authenticated() |
| | 181 | .and() |
| | 182 | .formLogin() |
| | 183 | .loginPage("/login") |
| | 184 | .successHandler(oAuth2SuccessHandler) |
| | 185 | .permitAll() |
| | 186 | .permitAll() |
| | 187 | .and() |
| | 188 | .sessionManagement() |
| | 189 | .sessionCreationPolicy(SessionCreationPolicy.ALWAYS) |
| | 190 | .and() |
| | 191 | .logout().logoutSuccessHandler((new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK))) |
| | 192 | .permitAll(); |
| | 193 | |
| | 194 | } catch (Exception e) { |
| | 195 | throw new RuntimeException(e); |
| | 196 | } |
| | 197 | } |
| | 198 | ).httpBasic(); |
| | 199 | return http.build(); |
| | 200 | } |
| | 201 | } |
| | 202 | |
| | 203 | }}} |
| | 204 | За да се провери идентитетот на корисникот, односно дали тој е сопственик на ресурсите кои се обидува да ги пристапи е имплементирана следната компонента. |
| | 205 | {{{#!java |
| | 206 | @Component |
| | 207 | public class UserSecurity implements AuthorizationManager<RequestAuthorizationContext> { |
| | 208 | |
| | 209 | @Override |
| | 210 | public AuthorizationDecision check(Supplier authenticationSupplier, RequestAuthorizationContext ctx) { |
| | 211 | Long userId = Long.parseLong(ctx.getVariables().get("userId")); |
| | 212 | Authentication authentication = (Authentication) authenticationSupplier.get(); |
| | 213 | return new AuthorizationDecision(hasUserId(authentication, userId)); |
| | 214 | } |
| | 215 | |
| | 216 | public boolean hasUserId(Authentication authentication, Long userId) { |
| | 217 | Long id; |
| | 218 | User user = (User) authentication.getPrincipal(); |
| | 219 | id = user.getUserID(); |
| | 220 | return userId == id; |
| | 221 | } |
| | 222 | |
| | 223 | } |
| | 224 | }}} |
| | 225 | По успешната најава, серверот го пренасочува корисникот до клиентската апликација односно http://localhost:8080/login-callback по што, преку Hook-от useLogin се испраќа барање за добивање на податоците и нивно зачувување во AuthContext-от, од каде можат сите компоненти да ги пристапат. |
| | 226 | {{{#!javascript |
| | 227 | |
| | 228 | const useLogin = () => { |
| | 229 | const [loading, setLoading] = useState(false); |
| | 230 | const [error, setError] = useState(null); |
| | 231 | const Auth = useAuth(); |
| | 232 | |
| | 233 | const handleLoginCallback = async () => { |
| | 234 | setLoading(true); |
| | 235 | |
| | 236 | try { |
| | 237 | const response = await axios.get("http://localhost:8080/principal"); |
| | 238 | const { id, role, username } = response.data; |
| | 239 | |
| | 240 | Auth.userLogin({userId: id, username: username, role: role}) |
| | 241 | |
| | 242 | } catch (err) { |
| | 243 | setError(err.message); |
| | 244 | } finally { |
| | 245 | setLoading(false); |
| | 246 | } |
| | 247 | }; |
| | 248 | |
| | 249 | return { |
| | 250 | loading, |
| | 251 | error, |
| | 252 | handleLoginCallback, |
| | 253 | }; |
| | 254 | }; |
| | 255 | |
| | 256 | export default useLogin; |
| | 257 | }}} |
| | 258 | \\ |
| | 259 | {{{#!javascript |
| | 260 | const AuthContext = createContext() |
| | 261 | |
| | 262 | const AuthProvider = ({ children }) => { |
| | 263 | const [user, setUser] = useState(null) |
| | 264 | |
| | 265 | useEffect(() => { |
| | 266 | const storedUser = JSON.parse(localStorage.getItem('user')) |
| | 267 | setUser(storedUser) |
| | 268 | }, []) |
| | 269 | |
| | 270 | const getUser = () => { |
| | 271 | return JSON.parse(localStorage.getItem('user')) |
| | 272 | } |
| | 273 | |
| | 274 | const userIsAuthenticated = () => { |
| | 275 | return localStorage.getItem('user') !== null |
| | 276 | } |
| | 277 | |
| | 278 | const userLogin = user => { |
| | 279 | localStorage.setItem('user', JSON.stringify(user)) |
| | 280 | setUser(user) |
| | 281 | } |
| | 282 | |
| | 283 | const userLogout = () => { |
| | 284 | localStorage.removeItem('user') |
| | 285 | setUser(null) |
| | 286 | } |
| | 287 | |
| | 288 | const contextValue = { |
| | 289 | user, |
| | 290 | getUser, |
| | 291 | userIsAuthenticated, |
| | 292 | userLogin, |
| | 293 | userLogout, |
| | 294 | } |
| | 295 | |
| | 296 | return ( |
| | 297 | <AuthContext.Provider value={contextValue}> |
| | 298 | {children} |
| | 299 | </AuthContext.Provider> |
| | 300 | ) |
| | 301 | } |
| | 302 | |
| | 303 | export default AuthContext |
| | 304 | |
| | 305 | export function useAuth() { |
| | 306 | return useContext(AuthContext) |
| | 307 | } |
| | 308 | |
| | 309 | export { AuthProvider } |
| | 310 | }}} |
| | 311 | За да се заштитат рутите за кои е потребна автентикација, имплементирана е wrapper-компонента којашто проверува дали корисникот е најавен и во спротивно го пренасочува кон страницата за најава. |
| | 312 | {{{#!javascript |
| | 313 | function PrivateRoute({ children }) { |
| | 314 | const { userIsAuthenticated } = useAuth() |
| | 315 | if(userIsAuthenticated()) |
| | 316 | { |
| | 317 | return children; |
| | 318 | } |
| | 319 | else |
| | 320 | { |
| | 321 | window.location.href = "http://localhost:8080/login"; |
| | 322 | } |
| | 323 | } |
| | 324 | |
| | 325 | export default PrivateRoute |
| | 326 | }}} |