401 neteisėtų pavasario saugos klaidų taisymas programoje „React-Spring“ naudojant pasirinktinį autentifikavimą

401 neteisėtų pavasario saugos klaidų taisymas programoje „React-Spring“ naudojant pasirinktinį autentifikavimą
401 neteisėtų pavasario saugos klaidų taisymas programoje „React-Spring“ naudojant pasirinktinį autentifikavimą

„Spring Security“ autentifikavimo problemų derinimas naudojant tinkintą prisijungimą

„Spring Security“ projekte aptikta neteisėta klaida 401 gali būti nelinksma, ypač kai atrodo, kad prisijungimo konfigūracija nustatyta teisingai. 😣 Daugelis kūrėjų, diegdami tinkintą prisijungimo puslapį ne pagal numatytąjį „Spring Security“, susiduria su šia problema bandydami apsaugoti savo programos pagrindinius išteklius.

Ši problema gali kilti, kai sąsaja, pvz., „React“, tvarko prisijungimo puslapį ir palaiko ryšį su užpakaline sistema, apeinant „Spring Security“ formomis pagrįstą prisijungimo sąranką. Tokiose sąrankose „Spring Security“ gali neatpažinti autentifikuoto seanso, todėl jums bus uždrausta prieiga, kai bandote naudoti apsaugotus išteklius.

Šiame straipsnyje apžvelgsime dažniausias šios neteisėtos prieigos klaidos priežastis po iš pažiūros sėkmingo prisijungimo. Suprasdami „Spring's SecurityContext“ ir seansų valdymo vaidmenį, gausite aiškumo, kaip išspręsti šią problemą pasirinktoje sąrankoje.

Išnagrinėkime praktines strategijas, kaip užtikrinti, kad jūsų autentifikavimo logika nuosekliai nustatytų teisingą seanso būseną, suteikdama sklandžią, įgaliotą prieigą prie programos. 🚀

komandą Naudojimo pavyzdys
sessionManagement Ši komanda sukonfigūruoja, kaip „Spring Security“ tvarko HTTP seansus. Naudojant session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) užtikrinama, kad kiekviena užklausa būtų autentifikuota atskirai, o tai būtina be būsenos API, dažnai naudojamoms REST pagrįstose, prieigos raktais patvirtintose sąrankose.
OncePerRequestFilter OncePerRequestFilter yra pavasario saugos filtras, užtikrinantis vieną vienos užklausos vykdymą. Jis naudojamas pasirinktiniuose autentifikavimo filtruose, siekiant užtikrinti, kad autentifikavimo logika būtų nuosekliai taikoma kiekvienai užklausai be pertekliaus.
SecurityContextHolder Iškviesdama SecurityContextHolder.getContext().setAuthentication(authentication), ši komanda nustato autentifikuoto vartotojo duomenis saugos kontekste, užtikrindama, kad „Spring Security“ atpažintų vartotoją kaip autentifikuotą dabartinei seansui.
DaoAuthenticationProvider Ši komanda nauja DaoAuthenticationProvider() nustato autentifikavimą naudodama konkrečią vartotojo informacijos paslaugą ir slaptažodžio koduotuvą, leidžiantį tinkintą patvirtinimą pagal vartotojo duomenų bazę ir užtikrinti saugų slaptažodžių tvarkymą.
MockMvcRequestBuilders.post Ši komanda testuojant vienetus imituoja HTTP POST užklausą, kaip matyti iš mockMvc.perform(MockMvcRequestBuilders.post("/login")). Tai leidžia išbandyti „Spring MVC“ valdiklius, siunčiant HTTP užklausas tiesiai į valdiklio galutinį tašką.
authorizeHttpRequests Ši komanda nurodo, kurias užklausas reikia autentifikuoti ir kurios yra viešai prieinamos. Authorize.requestMatchers("/user/login").permitAll() leidžia pasiekti prisijungimo ir registracijos galinius taškus be kredencialų.
TokenProvider Pasirinktinė klasė, pvz., TokenProvider, naudojama JWT žetonams generuoti ir valdyti. Ši klasė apima žetonų kūrimo logiką, kad būtų užtikrintas modulinis, daugkartinio naudojimo ir saugus žetonų tvarkymas, kuris yra gyvybiškai svarbus žetonu pagrįstam autentifikavimui.
csrf().disable() Disabling CSRF is critical in stateless API configurations, particularly for REST APIs without session-based login. csrf(csrf ->CSRF išjungimas yra labai svarbus API konfigūracijose be būsenos, ypač REST API be seanso prisijungimo. csrf(csrf -> csrf.disable()) paprastai reikalingas programoms, naudojančioms autentifikavimą žetonu, nes šiuo atveju CSRF apsauga nereikalinga.
requestMatchers Ši komanda filtruoja, kurie galiniai taškai atitinka konkrečias saugos taisykles, pvz., authorize.requestMatchers("/user/register"). Čia jis naudojamas norint neįtraukti registracijos ir prisijungimo galinių taškų iš autentifikavimo reikalavimų.
usernamePasswordAuthenticationToken Ši komanda yra būtina pasirinktiniuose autentifikavimo procesuose. naujasis UsernamePasswordAuthenticationToken() sukuria autentifikavimo prieigos raktą su pateiktais kredencialais, leidžiančius autentifikavimo tvarkytuvui patikrinti šiuos kredencialus pagal saugomą vartotojo informaciją.

Pavasario saugos konfigūracijos, skirtos pasirinktinio prisijungimo autentifikavimui, supratimas

Pateiktame scenarijuje matome pasirinktinę tvarkymo konfigūraciją autentifikavimas Spring Security nenaudodami numatytosios prisijungimo formos. Sukūrus atskirą SecurityFilterChain konfigūraciją, valdome, kurie galiniai taškai yra apsaugoti ir kaip Spring tvarko seansus. Konfigūracija išjungia CSRF (angl. Cross-Site Request Forgery) apsaugą, kuri yra įprasta REST API, nes sąsajos sistema (pvz., React) bendrauja naudodama saugias, prieigos raktais pagrįstas užklausas. Čia svarbiausia komanda authorizeHttpRequests; ji užtikrina, kad konkretūs URL, pvz., „/user/login“ ir „/user/register“, būtų atviri visiems vartotojams, o kitas užklausas, pvz., prieigą prie apsaugotų išteklių, gali pateikti tik autentifikuoti vartotojai.

Taip pat nustatome seanso kūrimo politiką naudodami SessionCreationPolicy.IF_REQUIRED, kuri leidžia kurti seansą tik tada, kai reikia. Šis metodas tinka programoms, kuriose kai kurios užklausos gali būti pagrįstos seansu pagrįstu autentifikavimu, bet kitos (pvz., su prieigos raktais) ne. Pavyzdžiui, jei vartotojas prisijungia naudodamas „React“ sąsają ir tikisi nuolatinės prieigos prie išteklių, ši seanso politika užtikrina, kad vartotojas nesusidurs su pasikartojančiais atsijungimais, kai programoje perjungs maršrutus. Tai ypač naudinga tvarkant seanso ir be būsenos reikalavimus, atsižvelgiant į tai, kaip klientas (programa „React“) sąveikauja su užpakalinės programos API.

Paslaugų klasė apima metodą, vadinamą authenticateUser, kuriame veikia AuthenticationManager komponentas. Šis komponentas yra sukonfigūruotas naudojant „DaoAuthenticationProvider“ ir „PasswordEncoder“, kurie yra būtini norint patikrinti vartotojo kredencialus pagal duomenų bazę. Metodas iškviečia autentifikavimą. Jei pavyks, „Spring Security“ „SecurityContextHolder“ palaiko šią autentifikuoto vartotojo seansą. Tokiu būdu, kai sąsaja pateikia kitą užklausą, „Spring“ gali nuskaityti vartotojo autentifikavimo būseną nereikalaujant pakartotinio patvirtinimo.

Tačiau nepaisant šios sąrankos, gali kilti problemų, pvz., gauti 401 neteisėtą klaidą, jei sesija arba prieigos raktas nėra tinkamai prižiūrimi. Pavyzdžiui, naudojant REST API su seansais be būsenos, ši sąranka gali nepavykti, jei serveris neišsaugo autentifikavimo tarp užklausų. Norėdami tai išspręsti, galėtume įdiegti žetonu pagrįstą autentifikavimą, kai sugeneruotas prieigos raktas pridedamas prie kiekvienos užklausos antraštės po prisijungimo, todėl seansas nepriklauso nuo serverio. Testavimo aplinkose MockMvcRequestBuilders leidžia kūrėjams imituoti užklausas ir patvirtinti, kad prisijungimo galutinis taškas teisingai grąžina prieigos raktą. Tada šis prieigos raktas gali būti naudojamas tolimesnėse užklausose, todėl „React“ sąsaja gali pasiekti saugius galutinius taškus be pakartotinio autentifikavimo, o tai užtikrina sklandesnę vartotojo patirtį. 🔐

1 sprendimas: atnaujinkite pavasario saugos konfigūraciją, skirtą seansų be būsenos valdymui

Šis metodas naudoja „Spring Security“ be būsenos seansų politiką, kad išspręstų seansų valdymą REST API kontekste, kuris yra optimizuotas vieno puslapio programoms (SPA), pvz., „React“. Čia pakoreguojame SecurityFilterChain konfigūraciją, kad ji atitiktų REST API be būsenos modelį.

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    return http
        .csrf(csrf -> csrf.disable())  // Disable CSRF for REST APIs
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/user/register", "/user/login").permitAll()
            .anyRequest().authenticated()
        )
        .httpBasic(Customizer.withDefaults())
        .sessionManagement(session ->
            session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        )
        .build();
}

2 sprendimas: tinkintas autentifikavimo filtras, skirtas autentifikavimui žetonais

Šiame sprendime pasirinktinis filtras autentifikuoja vartotoją ir atsakymo antraštėje prideda prieigos raktą. Šis filtras naudoja žetonų autentifikavimą, kuris idealiai tinka RESTful programoms ir gali sklandžiai veikti su React.

@Component
public class CustomAuthFilter extends OncePerRequestFilter {
    private final AuthenticationManager authenticationManager;
    public CustomAuthFilter(AuthenticationManager authManager) {
        this.authenticationManager = authManager;
    }
    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
                                    throws ServletException, IOException {
        String authHeader = request.getHeader("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            String token = authHeader.substring(7);
            Authentication authentication = new UsernamePasswordAuthenticationToken(token, null);
            Authentication authResult = authenticationManager.authenticate(authentication);
            SecurityContextHolder.getContext().setAuthentication(authResult);
        }
        filterChain.doFilter(request, response);
    }
}

3 sprendimas: paslaugų klasės koregavimai ir atsakymas į žetonus

Šis paslaugos diegimas siunčia JWT prieigos raktą sėkmingo prisijungimo metu, naudojant modulinį dizainą, siekiant užtikrinti, kad kiekviena funkcija būtų išbandoma ir pakartotinai naudojama visoje programoje.

@Service
public class AuthenticationService {
    private final AuthenticationManager authenticationManager;
    private final TokenProvider tokenProvider; // Custom class for generating JWTs
    public AuthenticationService(AuthenticationManager authenticationManager,
                                 TokenProvider tokenProvider) {
        this.authenticationManager = authenticationManager;
        this.tokenProvider = tokenProvider;
    }
    public String authenticateAndGenerateToken(LoginDTO loginDTO) {
        Authentication authentication = authenticationManager
            .authenticate(new UsernamePasswordAuthenticationToken(loginDTO.getUserName(),
                                                                loginDTO.getPassword()));
        SecurityContextHolder.getContext().setAuthentication(authentication);
        return tokenProvider.createToken(authentication);
    }
}

Žetonų generavimo ir autentifikavimo vieneto testas

Šis JUnit testas užtikrina, kad autentifikavimas ir prieigos raktų generavimas veiktų tinkamai, ir patvirtina autentifikavimą norint pasiekti saugius išteklius.

@SpringBootTest
@AutoConfigureMockMvc
public class AuthenticationServiceTest {
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private AuthenticationService authenticationService;
    @Test
    public void testAuthenticateAndGenerateToken() throws Exception {
        LoginDTO loginDTO = new LoginDTO("user", "password");
        String token = authenticationService.authenticateAndGenerateToken(loginDTO);
        mockMvc.perform(MockMvcRequestBuilders.post("/login")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\\"userName\\":\\"user\\", \\"password\\":\\"password\\"}"))
                .andExpect(status().isOk())
                .andExpect(header().exists("Authorization"))
                .andExpect(content().string("Successfully Authenticated"));
    }
}

Sesijos iššūkių įveikimas be pilietybės pavasario saugumo programose

Tais atvejais, kai Pavasario apsauga yra sukonfigūruotas API ryšiui be būsenos, seansų valdymas gali būti sudėtingas, ypač naudojant pasirinktinį prisijungimo srautą. Konfigūracijos be statuso reiškia, kad idealiu atveju kiekviena užklausa turėtų turėti savo autentifikavimo prieigos raktą, kurį serveris patvirtina nepriklausomai nuo ankstesnių užklausų. Tai skiriasi nuo tradicinių seansu pagrįstų sąrankų, kai vartotojas prisijungia vieną kartą, o jo seansas išlieka serveryje. Naudojant „React“ sąsajas, kurios dažniausiai naudojamos autentifikavimui tvarkyti ir prisijungimo užklausoms siųsti per REST API, integruojant reikia užtikrinti, kad kiekviena API užklausa būtų autentifikuota, dažnai naudojant tokius prieigos raktus kaip JWT.

Kai numatytasis „Spring Security“ seansų valdymas pakeičiamas tinkinta konfigūracija, labai svarbu suprasti, kaip nustatyti ir palaikyti vartotojo autentifikavimą SecurityContextHolder. Vienas iš būdų tai išspręsti yra naudoti pasirinktinį autentifikavimo filtrą, kuris patikrina užklausų antraštėse esančius prieigos raktus, o ne pasikliauja seansais. Jei jūsų programa reikalauja pakartotinio vartotojo identifikavimo be seanso išlikimo, prieigos raktą galite saugoti vietoje ir įtraukti jį į kiekvienos užklausos antraštę. Tai pašalina poreikį serveriui sekti seanso būseną, suderinant su be būsenos projektavimo modeliu saugioms ir veiksmingoms RESTful API.

Be to, atsijungimo funkcijos įgyvendinimas yra dar vienas aspektas, į kurį reikia atsižvelgti naudojant programas be būsenos. Kadangi serveryje nėra seanso, atsijungimas paprastai apima prieigos rakto pašalinimą iš kliento pusės. Pagal šį scenarijų sėkmingas atsijungimas pasiekiamas tiesiog atmetant prieigos raktą kliento vietinėje saugykloje ir atmetant užklausas su prieigos raktu serveryje. Šis metodas palaiko aukštesnius saugos lygius, užkertant kelią neteisėtai prieigai be serverio pusės seanso tvarkymo. Galiausiai ši konfigūracija puikiai tinka programoms, kurios teikia pirmenybę mastelio keitimui ir saugumui, ypač kai ji yra suporuota su priekinėmis sistemomis, tokiomis kaip „React“, kurios gali efektyviai valdyti žetonų saugyklą. 🚀

Dažni klausimai apie pavasario saugos pasirinktinio autentifikavimo problemas

  1. Kodėl aš vis dar gaunu 401 neteisėtą klaidą net nustatęs SecurityContextHolder?
  2. 401 klaida dažnai įvyksta, jei autentifikavimo kontekstas išlieka. Įsitikinkite, kad naudojate prieigos raktu pagrįstą autentifikavimą, jei jūsų programa yra be būsenos.
  3. Kaip „Spring Security“ įjungti seansų valdymą be pilietybės?
  4. Nustatyti SessionCreationPolicy.STATELESS tavo SecurityFilterChain užtikrinti, kad kiekviena užklausa būtų patvirtinta nepriklausomai.
  5. Koks yra vaidmuo DaoAuthenticationProvider pasirinktinio autentifikavimo?
  6. The DaoAuthenticationProvider patikrina vartotojo kredencialus pagal jūsų duomenų bazę ir užkoduoja slaptažodžius saugiam autentifikavimui.
  7. Ar galiu naudoti JWT žetonus sesijos valdymui „Spring Security“?
  8. Taip, JWT žetonai idealiai tinka programoms be būsenos. Po autentifikavimo sugeneruokite prieigos raktą ir įtraukite jį į vėlesnių užklausų antraštę.
  9. Kaip CSRF apsauga veikia API be būsenos?
  10. CSRF apsauga paprastai išjungiama naudojant API be būsenos csrf().disable() nes tai nereikalinga API be seansų.
  11. Ką daryti, jei noriu leisti viešą prieigą prie kai kurių galinių taškų, pvz., prisijungimo ar registracijos?
  12. Naudokite authorizeHttpRequests ir nurodykite galinius taškus, kurie turėtų būti pasiekiami be autentifikavimo naudojant permitAll().
  13. Kaip išsaugoti žetonus kliento pusėje naudojant „React“?
  14. Laikykite žetonus localStorage arba sessionStorage, tada įtraukite juos į kiekvienos užklausos antraštę, kad užtikrintumėte, jog užpakalinė programa gali autentifikuoti kiekvieną užklausą.
  15. Ar saugu išjungti CSRF API?
  16. Išjungti CSRF API yra saugu, jei jūsų programa remiasi prieigos raktais arba nenaudoja slapukų, nes CSRF daugiausia apsaugo nuo slapukais pagrįstų atakų.
  17. Kokia yra funkcija OncePerRequestFilter pasirinktinio autentifikavimo?
  18. Šis filtras vykdomas tik vieną kartą per užklausą, užtikrinant, kad autentifikavimo logika būtų taikoma nuosekliai be perteklinių patikrų užklausos cikle.
  19. Kodėl mano autentifikavimo prieigos raktas gali būti neatpažįstamas skirtinguose galutiniuose taškuose?
  20. Įsitikinkite, kad kiekvienos užklausos antraštėje nustatėte prieigos raktą ir, naudodami nuoseklų prieigos rakto patvirtinimo procesą, patvirtinkite, kad jis tinkamai patvirtintas serveryje.
  21. Kaip galiu išbandyti „Spring Security“ konfigūraciją?
  22. Naudokite MockMvc savo bandymuose, kad imituotumėte užklausas, patikrintumėte autentifikavimo atsakymus ir patvirtintumėte, kad apsaugoti galiniai taškai pasiekiami tik prisijungus.

Paskutinės mintys, kaip išspręsti 401 klaidas naudojant pasirinktinį pavasario saugos autentifikavimą

Norint sėkmingai apsaugoti pavasario programą naudojant tinkintą prisijungimo puslapį, reikia kruopštaus konfigūravimo, ypač jei naudojami seansai be būsenos arba prieigos raktais pagrįsti metodai. Integruojant su „React“ sąsaja, užtikrinant, kad jūsų saugos konfigūracija atitiktų RESTful, be būsenos principus, galite išvengti seanso problemų.

Nuo modifikavimo SecurityFilterChain parametrus, kad būtų įgyvendinti prieigos raktais pagrįsti srautai, kiekvienas metodas atlieka svarbų vaidmenį kuriant patikimą autentifikavimo sąranką. Suprasdami seansų valdymą, prieigos raktų tvarkymą ir SecurityContext, būsite gerai pasirengę išspręsti 401 neteisėtą „Spring Security“ programų klaidą. 🔒

Ištekliai ir nuorodos, kaip įdiegti pasirinktinį autentifikavimą „Spring Security“.
  1. Norėdami gauti išsamios informacijos apie Spring Security konfigūraciją ir seansų valdymą, žr Pavasario saugumo oficialūs dokumentai .
  2. Norėdami suprasti ir įdiegti tinkintus autentifikavimo srautus naudojant „React“ sąsają, žr. vadovą adresu Pavasario saugumo ir reakcijos prisijungimo pamoka .
  3. Šio straipsnio konfigūracijos pavyzdžiai ir „Spring Boot“ sąranka yra pagrįsti įžvalgomis iš Baeldung pavasario saugumo sesijos vadovas .