Memperbaiki 401 Kesalahan Keamanan Pegas Tidak Sah di Aplikasi React-Spring dengan Otentikasi Kustom

Authentication

Men-debug Masalah Otentikasi Keamanan Musim Semi dalam Implementasi Login Kustom

Mengalami kesalahan 401 Tidak Sah di proyek Spring Security Anda dapat membuat frustasi, terutama ketika konfigurasi login tampaknya telah diatur dengan benar. 😣 Banyak pengembang, ketika menerapkan halaman login khusus di luar default Spring Security, menghadapi masalah ini ketika mencoba mengamankan sumber daya backend aplikasi mereka.

Masalah ini dapat muncul ketika kerangka kerja front-end seperti React mengelola halaman login dan berkomunikasi dengan backend, melewati pengaturan login berbasis formulir Spring Security. Dalam pengaturan seperti itu, Spring Security mungkin gagal mengenali sesi yang diautentikasi, sehingga menyebabkan penolakan akses saat Anda mencoba menggunakan sumber daya yang dilindungi.

Dalam artikel ini, kita akan mendalami penyebab umum di balik kesalahan akses tidak sah ini setelah login yang tampaknya berhasil. Dengan memahami peran SecurityContext dan manajemen sesi Spring, Anda akan mendapatkan kejelasan tentang cara mengatasi masalah ini dalam pengaturan khusus.

Mari kita jelajahi strategi praktis untuk memastikan logika autentikasi Anda secara konsisten menetapkan status sesi yang benar, sehingga memungkinkan akses yang lancar dan sah di seluruh aplikasi Anda. 🚀

Memerintah Contoh penggunaan
sessionManagement Perintah ini mengonfigurasi cara Spring Security menangani sesi HTTP. Penggunaan session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) memastikan bahwa setiap permintaan diautentikasi secara individual, yang penting untuk API tanpa kewarganegaraan yang sering digunakan dalam penyiapan yang diautentikasi token berbasis REST.
OncePerRequestFilter OncePerRequestFilter adalah filter Keamanan Musim Semi yang menjamin satu eksekusi per permintaan. Ini digunakan dalam filter autentikasi khusus untuk memastikan bahwa logika autentikasi diterapkan secara konsisten untuk setiap permintaan tanpa redundansi.
SecurityContextHolder Dengan memanggil SecurityContextHolder.getContext().setAuthentication(authentication), perintah ini menetapkan detail pengguna yang diautentikasi dalam konteks keamanan, memastikan Spring Security mengenali pengguna yang diautentikasi untuk sesi saat ini.
DaoAuthenticationProvider Perintah new DaoAuthenticationProvider() ini menyiapkan autentikasi menggunakan layanan detail pengguna tertentu dan pembuat kata sandi, memungkinkan validasi khusus berdasarkan database pengguna dan memastikan penanganan kata sandi yang aman.
MockMvcRequestBuilders.post Perintah dalam pengujian unit ini mensimulasikan permintaan HTTP POST, seperti yang terlihat di mockMvc.perform(MockMvcRequestBuilders.post("/login")). Ini memungkinkan pengujian pengontrol Spring MVC dengan mengirimkan permintaan HTTP langsung ke titik akhir pengontrol.
authorizeHttpRequests Perintah ini menentukan permintaan mana yang memerlukan otentikasi dan mana yang dapat diakses publik. authorize.requestMatchers("/user/login").permitAll() memungkinkan titik akhir login dan registrasi diakses tanpa kredensial.
TokenProvider Kelas khusus seperti TokenProvider digunakan untuk menghasilkan dan mengelola token JWT. Kelas ini merangkum logika pembuatan token untuk memastikan penanganan token yang modular, dapat digunakan kembali, dan aman, yang penting dalam autentikasi berbasis token.
csrf().disable() Disabling CSRF is critical in stateless API configurations, particularly for REST APIs without session-based login. csrf(csrf ->Menonaktifkan CSRF sangat penting dalam konfigurasi API stateless, khususnya untuk REST API tanpa login berbasis sesi. csrf(csrf -> csrf.disable()) biasanya diperlukan untuk aplikasi yang menggunakan autentikasi berbasis token, karena perlindungan CSRF tidak diperlukan dalam kasus ini.
requestMatchers Perintah ini memfilter titik akhir mana yang cocok dengan aturan keamanan tertentu, seperti authorize.requestMatchers("/user/register"). Ini digunakan di sini untuk mengecualikan titik akhir pendaftaran dan login dari persyaratan otentikasi.
usernamePasswordAuthenticationToken Perintah ini penting dalam proses otentikasi khusus. new UsernamePasswordAuthenticationToken() membuat token autentikasi dengan kredensial yang diberikan, memungkinkan manajer autentikasi memverifikasi kredensial ini terhadap detail pengguna yang disimpan.

Memahami Konfigurasi Keamanan Musim Semi untuk Otentikasi Login Kustom

Dalam skrip yang disediakan, kita melihat konfigurasi khusus untuk penanganannya di Spring Security tanpa menggunakan formulir login defaultnya. Dengan membuat yang terpisah konfigurasi, kami mendapatkan kendali atas titik akhir mana yang dilindungi dan bagaimana Spring mengelola sesi. Konfigurasi ini menonaktifkan perlindungan CSRF (Pemalsuan Permintaan Lintas Situs), yang umum terjadi di REST API, karena kerangka kerja frontend (seperti React) berkomunikasi menggunakan permintaan berbasis token yang aman. Di sini, perintah authorizeHttpRequests adalah kuncinya; ini memastikan bahwa URL tertentu, seperti "/user/login" dan "/user/register," terbuka untuk semua pengguna, sekaligus membatasi permintaan lain, seperti mengakses sumber daya yang dilindungi, hanya untuk pengguna yang diautentikasi.

Kami juga menetapkan kebijakan pembuatan sesi dengan SessionCreationPolicy.IF_REQUIRED, yang memungkinkan pembuatan sesi hanya jika diperlukan. Pendekatan ini cocok untuk aplikasi yang beberapa permintaannya mungkin bergantung pada autentikasi berbasis sesi, namun permintaan lainnya (seperti permintaan dengan token) tidak. Misalnya, jika pengguna masuk melalui frontend React dan mengharapkan akses terus-menerus ke sumber daya, kebijakan sesi ini memastikan pengguna tidak mengalami logout berulang kali saat berpindah rute dalam aplikasi. Ini sangat berguna untuk menangani persyaratan sesi dan stateless, bergantung pada bagaimana klien (aplikasi React) berinteraksi dengan API backend.

Kelas layanan menyertakan metode yang disebut authenticateUser, tempat kacang AuthenticationManager berperan. Kacang ini dikonfigurasi dengan DaoAuthenticationProvider dan PasswordEncoder, yang penting untuk memverifikasi kredensial pengguna terhadap database. Metode ini memanggil AuthenticationManager.authenticate dengan UsernamePasswordAuthenticationToken, mencoba mengautentikasi berdasarkan nama pengguna dan kata sandi yang diberikan. Jika berhasil, SecurityContextHolder Spring Security memegang sesi pengguna yang diautentikasi ini. Dengan cara ini, ketika frontend membuat permintaan lain, Spring dapat mengambil status autentikasi pengguna tanpa memerlukan verifikasi ulang.

Namun, meskipun pengaturan ini, masalah seperti menerima kesalahan 401 Tidak Sah dapat muncul jika sesi atau token tidak dikelola dengan benar. Misalnya, saat menggunakan REST API dengan sesi tanpa kewarganegaraan, penyiapan ini mungkin gagal jika server tidak mempertahankan autentikasi di antara permintaan. Untuk mengatasi hal ini, kita dapat menerapkan otentikasi berbasis token, di mana token yang dihasilkan dilampirkan ke setiap header permintaan setelah login, sehingga sesi tidak bergantung pada server. Dalam lingkungan pengujian, MockMvcRequestBuilders memungkinkan pengembang untuk menyimulasikan permintaan dan mengonfirmasi bahwa titik akhir login mengembalikan token otorisasi dengan benar. Token ini kemudian dapat digunakan dalam permintaan lebih lanjut, memungkinkan frontend React mengakses titik akhir yang aman tanpa mengautentikasi ulang, sehingga memberikan pengalaman pengguna yang lebih lancar. 🔐

Solusi 1: Memperbarui Konfigurasi Keamanan Musim Semi untuk Manajemen Sesi Tanpa Status

Pendekatan ini menggunakan kebijakan sesi stateless Spring Security untuk menyelesaikan manajemen sesi dalam konteks REST API, yang dioptimalkan untuk aplikasi satu halaman (SPA) seperti React. Di sini, kami menyesuaikan konfigurasi SecurityFilterChain agar sesuai dengan model stateless REST API.

@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();
}

Solusi 2: Filter Otentikasi Kustom untuk Otentikasi Berbasis Token

Dalam solusi ini, filter khusus mengautentikasi pengguna dan melampirkan token di header respons. Filter ini menggunakan autentikasi berbasis token, yang ideal untuk aplikasi RESTful dan dapat bekerja secara lancar dengan 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);
    }
}

Solusi 3: Penyesuaian Kelas Layanan dan Respons Token

Implementasi layanan ini mengirimkan token JWT saat login berhasil, menggunakan desain modular untuk memastikan setiap fungsi dapat diuji dan digunakan kembali di seluruh aplikasi.

@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);
    }
}

Tes Unit untuk Pembuatan dan Otentikasi Token

Pengujian JUnit ini memastikan bahwa autentikasi dan pembuatan token berfungsi dengan benar dan memvalidasi autentikasi untuk mengakses sumber daya yang aman.

@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"));
    }
}

Mengatasi Tantangan Sesi dalam Aplikasi Keamanan Musim Semi Tanpa Kewarganegaraan

Dalam kasus di mana dikonfigurasi untuk komunikasi API tanpa kewarganegaraan, manajemen sesi bisa jadi rumit, terutama saat menggunakan alur login khusus. Konfigurasi tanpa kewarganegaraan berarti bahwa setiap permintaan idealnya membawa token autentikasinya sendiri, yang divalidasi oleh server secara independen dari permintaan sebelumnya. Ini berbeda dari pengaturan berbasis sesi tradisional di mana pengguna login satu kali, dan sesi mereka tetap ada di server. Dengan frontend React yang biasa digunakan untuk menangani autentikasi dan mengirim permintaan login melalui REST API, integrasi perlu memastikan setiap permintaan API diautentikasi, sering kali menggunakan token seperti JWT.

Ketika manajemen sesi default Spring Security digantikan oleh konfigurasi khusus, penting untuk memahami cara mengatur dan memelihara otentikasi pengguna dalam . Salah satu cara untuk mengatasi hal ini adalah dengan menggunakan filter autentikasi khusus yang memverifikasi token yang disertakan dalam header permintaan, daripada mengandalkan sesi. Jika aplikasi Anda memerlukan identifikasi pengguna berulang tanpa persistensi sesi, Anda mungkin ingin menyimpan token secara lokal di frontend dan memasukkannya ke dalam setiap header permintaan. Hal ini menghilangkan kebutuhan server untuk melacak status sesi, menyelaraskan dengan model desain stateless untuk RESTful API yang aman dan efisien.

Selain itu, penerapan fungsionalitas logout adalah aspek lain yang perlu dipertimbangkan dalam aplikasi tanpa kewarganegaraan. Karena tidak ada sesi di server, logout biasanya melibatkan penghapusan token dari sisi klien. Dalam skenario ini, logout yang berhasil dicapai hanya dengan membuang token di penyimpanan lokal klien dan menolak permintaan dengan token di server. Metode ini mendukung tingkat keamanan yang lebih tinggi dengan mencegah akses tidak sah tanpa penanganan sesi di sisi server. Pada akhirnya, konfigurasi ini sangat cocok untuk aplikasi yang memprioritaskan skalabilitas dan keamanan, terutama bila dipasangkan dengan kerangka kerja front-end seperti React yang dapat mengelola penyimpanan token secara efektif. 🚀

  1. Mengapa saya masih mendapatkan kesalahan 401 Tidak Sah bahkan setelah mengaturnya ?
  2. Kesalahan 401 sering terjadi jika konteks autentikasi tidak bertahan. Pastikan Anda menggunakan autentikasi berbasis token jika aplikasi Anda tidak memiliki kewarganegaraan.
  3. Bagaimana cara mengaktifkan manajemen sesi tanpa kewarganegaraan di Spring Security?
  4. Mengatur di dalam kamu untuk memastikan bahwa setiap permintaan diautentikasi secara independen.
  5. Apa perannya dalam otentikasi khusus?
  6. Itu memverifikasi kredensial pengguna terhadap database Anda dan mengkodekan kata sandi untuk otentikasi aman.
  7. Bisakah saya menggunakan token JWT untuk manajemen sesi di Spring Security?
  8. Ya, token JWT ideal untuk aplikasi tanpa kewarganegaraan. Hasilkan token setelah autentikasi dan sertakan di header untuk permintaan berikutnya.
  9. Bagaimana perlindungan CSRF memengaruhi API tanpa kewarganegaraan?
  10. Perlindungan CSRF biasanya dinonaktifkan pada penggunaan API tanpa kewarganegaraan karena API tanpa sesi tidak diperlukan.
  11. Bagaimana jika saya ingin mengizinkan akses publik ke beberapa titik akhir seperti login atau registrasi?
  12. Menggunakan dan tentukan titik akhir yang harus dapat diakses tanpa menggunakan otentikasi .
  13. Bagaimana cara menyimpan token di sisi klien dengan React?
  14. Simpan token di atau , lalu sertakan di setiap header permintaan untuk memastikan backend dapat mengautentikasi setiap permintaan.
  15. Apakah aman menonaktifkan CSRF untuk API?
  16. Menonaktifkan CSRF untuk API aman jika aplikasi Anda mengandalkan token atau tidak menggunakan cookie, karena CSRF terutama melindungi terhadap serangan berbasis cookie.
  17. Apa fungsinya dalam otentikasi khusus?
  18. Filter ini hanya dijalankan sekali per permintaan, memastikan bahwa logika autentikasi berlaku secara konsisten tanpa pemeriksaan berlebihan dalam siklus permintaan.
  19. Mengapa token autentikasi saya mungkin tidak dikenali di berbagai titik akhir?
  20. Pastikan Anda menyetel token di setiap header permintaan dan mengonfirmasi bahwa token tersebut divalidasi dengan benar di server menggunakan proses verifikasi token yang konsisten.
  21. Bagaimana cara menguji konfigurasi Spring Security saya?
  22. Menggunakan dalam pengujian Anda untuk menyimulasikan permintaan, memeriksa respons autentikasi, dan memvalidasi bahwa titik akhir yang dilindungi hanya dapat diakses setelah login.

Keberhasilan mengamankan aplikasi berbasis Spring dengan halaman login khusus memerlukan konfigurasi yang cermat, terutama jika menggunakan sesi stateless atau pendekatan berbasis token. Saat berintegrasi dengan frontend React, memastikan bahwa konfigurasi keamanan Anda selaras dengan prinsip-prinsip RESTful dan stateless dapat membantu menghindari masalah sesi.

Dari memodifikasi pengaturan untuk menerapkan alur berbasis token, setiap pendekatan berperan dalam menciptakan pengaturan otentikasi yang andal. Dengan memahami manajemen sesi, penanganan token, dan SecurityContext, Anda akan diperlengkapi dengan baik untuk mengatasi kesalahan 401 Tidak Sah di aplikasi Spring Security Anda. 🔒

  1. Untuk detail komprehensif tentang konfigurasi dan manajemen sesi Spring Security, lihat Dokumentasi Resmi Keamanan Musim Semi .
  2. Untuk memahami dan mengimplementasikan alur autentikasi khusus dengan frontend React, lihat panduan di Tutorial Keamanan Musim Semi dan Login React .
  3. Contoh konfigurasi artikel ini dan penyiapan Spring Boot didasarkan pada wawasan dari Panduan Sesi Keamanan Musim Semi Baeldung .