Dalam artikel ini kami akan meneroka keselamatan Spring dan akan membina sistem pengesahan dengan OAuth 2.0.
Sebelum menyelami cara Spring Security beroperasi, adalah penting untuk memahami kitaran hayat pengendalian permintaan dalam pelayan web berasaskan Java. Spring Security disepadukan dengan lancar ke dalam kitaran hayat ini untuk mendapatkan permintaan masuk.
Kitaran hayat mengendalikan permintaan HTTP dalam aplikasi berasaskan Spring dengan Spring Security melibatkan beberapa peringkat, masing-masing memainkan peranan penting dalam memproses, mengesahkan dan melindungi permintaan.
Kitaran hayat bermula apabila pelanggan (cth., penyemak imbas, apl mudah alih atau alat API seperti Posman) menghantar permintaan HTTP ke pelayan.
Contoh:
DAPATKAN /api/admin/papan pemuka HTTP/1.1
Bekas servlet (cth., Tomcat) menerima permintaan dan mewakilkannya kepada DispatcherServlet, pengawal hadapan dalam aplikasi Spring. Di sinilah saluran paip pemprosesan aplikasi bermula.
Sebelum DispatcherServlet memproses permintaan, Rantai Penapis Spring Security memintasnya. Rantaian penapis ialah urutan penapis, masing-masing bertanggungjawab untuk mengendalikan tugas keselamatan tertentu. Penapis ini memastikan permintaan memenuhi keperluan pengesahan dan kebenaran sebelum mencapai logik aplikasi.
Penapis Pengesahan:
Penapis ini mengesahkan sama ada permintaan mengandungi bukti kelayakan yang sah, seperti nama pengguna/kata laluan, JWT atau kuki sesi.
Penapis Kebenaran:
Selepas pengesahan, penapis ini memastikan pengguna yang disahkan mempunyai peranan atau kebenaran yang diperlukan untuk mengakses sumber yang diminta.
Penapis Lain:
* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks. * **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains. * **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.
Jika pengesahan berjaya, Spring Security mencipta objek Pengesahan dan menyimpannya dalam SecurityContext. Objek ini, selalunya disimpan dalam storan setempat-benang, boleh diakses sepanjang kitaran hayat permintaan.
Pengetua: Mewakili pengguna yang disahkan (cth., nama pengguna).
Kelayakan: Termasuk butiran pengesahan seperti token atau kata laluan JWT.
Pihak berkuasa: Mengandungi peranan dan kebenaran yang diberikan kepada pengguna.
Permintaan melalui penapis pengesahan.
Jika bukti kelayakan adalah sah, objek Pengesahan dibuat dan ditambahkan pada SecurityContext.
Jika bukti kelayakan tidak sah, ExceptionTranslationFilter menghantar respons 401 Tanpa kebenaran kepada pelanggan.
Setelah permintaan berjaya melalui Rantaian Penapis Keselamatan Spring, DispatcherServlet mengambil alih:
Pemetaan Pengendali:
Ia memetakan permintaan masuk kepada kaedah pengawal yang sesuai berdasarkan URL dan kaedah HTTP.
Seruan Pengawal:
Pengawal yang dipetakan memproses permintaan dan mengembalikan respons yang sesuai, selalunya dengan bantuan daripada komponen Spring lain seperti perkhidmatan dan repositori.
Spring Security menyepadukan dirinya ke dalam kitaran hayat ini melalui penapisnya, memintas permintaan pada peringkat terawal. Apabila permintaan mencapai logik aplikasi, ia telah pun disahkan dan dibenarkan, memastikan hanya trafik yang sah diproses oleh aplikasi teras.
Reka bentuk Spring Security memastikan pengesahan, kebenaran dan langkah keselamatan lain dikendalikan secara deklaratif, memberikan kelonggaran kepada pembangun untuk menyesuaikan atau melanjutkan tingkah lakunya mengikut keperluan. Ia bukan sahaja menguatkuasakan amalan terbaik tetapi juga memudahkan pelaksanaan keperluan keselamatan yang kompleks dalam aplikasi moden.
Setelah meneroka Rantaian Penapis dalam Spring Security, mari kita teliti beberapa komponen utama lain yang memainkan peranan penting dalam proses pengesahan dan kebenaran.
AuthenticationManager ialah antara muka yang mentakrifkan satu kaedah , authenticate(Authentication authentication) , yang digunakan untuk mengesahkan kelayakan pengguna dan menentukan sama ada ia sah. Anda boleh menganggap AuthenticationManager sebagai penyelaras tempat anda boleh mendaftarkan berbilang pembekal dan berdasarkan jenis permintaan, ia akan menghantar permintaan pengesahan kepada pembekal yang betul.
Penyedia Pengesahan ialah antara muka yang mentakrifkan kontrak untuk mengesahkan pengguna berdasarkan kelayakan mereka. Ia mewakili mekanisme pengesahan khusus, seperti nama pengguna/kata laluan, OAuth atau LDAP. Pelaksanaan Multiple AuthenticationProvider boleh wujud bersama, membolehkan aplikasi menyokong pelbagai strategi pengesahan.
Objek Pengesahan:
AuthenticationProvider memproses objek Authentication, yang merangkumi bukti kelayakan pengguna (cth., nama pengguna dan kata laluan).
kaedah pengesahan:
Setiap AuthenticationProvider melaksanakan kaedah authenticate(Authentication authentication), di mana logik pengesahan sebenar berada. Kaedah ini:
* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks. * **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains. * **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.
Penyedia Pengesahan yang disokong pangkalan data mengesahkan nama pengguna dan kata laluan.
Penyedia Pengesahan berasaskan OAuth mengesahkan token yang dikeluarkan oleh pembekal identiti luaran.
UserDetailsService diterangkan sebagai antara muka teras yang memuatkan data khusus pengguna dalam dokumentasi Spring Ia mengandungi satu kaedah loadUserByUsername yang menerima nama pengguna sebagai parameter dan mengembalikan ==User== objek identiti . Pada asasnya kami mencipta dan melaksanakan kelas UserDetailsService di mana kami mengatasi kaedah loadUserByUsername.
* Validates the user’s credentials. * Returns an authenticated `Authentication` object upon success. * Throws an `AuthenticationException` if authentication fails.
Sekarang bagaimana ketiga-tiga ini berfungsi bersama ialah AuthenticationManager akan meminta AuthenticationProvider untuk meneruskan pengesahan mengikut jenis Penyedia yang ditentukan dan pelaksanaan UserDetailsService akan membantu AuthenticationProvider dalam membuktikan butiran pengguna.
Pengguna menghantar permintaan ke titik akhir yang disahkan dengan bukti kelayakan mereka (nama pengguna dan kata laluan) atau token JWT (dalam pengepala) dan permintaan itu dihantar ke Penapis Pengesahan
AuthenticationFilter (cth., UsernamePasswordAuthenticationFilter):
Penapis tersuai ini memanjangkan OncePerRequestFilter dan diletakkan sebelum UsernamePasswordAuthenticationFilter , dan apa yang dilakukannya ialah ia mengeluarkan token daripada permintaan dan mengesahkannya.
Jika token itu sah, ia mencipta UsernamePasswordAuthenticationToken dan menetapkan token itu ke dalam Konteks Keselamatan yang memberitahu keselamatan musim bunga bahawa permintaan itu disahkan dan apabila permintaan ini dihantar ke UsernamePasswordAuthenticationFilter ia hanya lulus kerana ia mempunyai UsernamePasswordAuthenticationToken
* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks. * **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains. * **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.
3.
* Validates the user’s credentials. * Returns an authenticated `Authentication` object upon success. * Throws an `AuthenticationException` if authentication fails.
UserDetailsService: AuthenticationProvider menggunakan UserDetailsService untuk memuatkan butiran pengguna berdasarkan nama pengguna. Dan kami menyediakan ini dengan pelaksanaan UserDetailsService
Pengesahan Kredensial: Ia membandingkan kata laluan yang disediakan dengan kata laluan yang disimpan dalam butiran pengguna (biasanya menggunakan Pengekod Kata Laluan).
package com.oauth.backend.services; import com.oauth.backend.entities.User; import com.oauth.backend.repositories.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; @Component public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; public CustomUserDetailsService(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if(user==null){ throw new UsernameNotFoundException(username); } return new UserDetailsImpl(user); } public UserDetails loadUserByEmail(String email) throws UsernameNotFoundException { User user = userRepository.findByEmail(email); if(user==null){ throw new UsernameNotFoundException(email); } return new UserDetailsImpl(user); } }
@Component public class JWTFilter extends OncePerRequestFilter { private final JWTService jwtService; private final UserDetailsService userDetailsService; public JWTFilter(JWTService jwtService,UserDetailsService userDetailsService) { this.jwtService = jwtService; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { final String authHeader = request.getHeader("Authorization"); if(authHeader == null || !authHeader.startsWith("Bearer")) { filterChain.doFilter(request,response); return; } final String jwt = authHeader.substring(7); final String userName = jwtService.extractUserName(jwt); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if(userName !=null && authentication == null) { //Authenticate UserDetails userDetails = userDetailsService.loadUserByUsername(userName); if(jwtService.isTokenValid(jwt,userDetails)) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); SecurityContextHolder.getContext() .setAuthentication(authenticationToken); } } filterChain.doFilter(request,response); } }
Kami akan mencipta aplikasi mudah dengan dua pengawal AuthController (mengendalikan log masuk dan mendaftar) dan ProductController (pengawal yang dilindungi dummy)
* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks. * **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains. * **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.
* Validates the user’s credentials. * Returns an authenticated `Authentication` object upon success. * Throws an `AuthenticationException` if authentication fails.
package com.oauth.backend.services; import com.oauth.backend.entities.User; import com.oauth.backend.repositories.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Component; @Component public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; public CustomUserDetailsService(UserRepository userRepository) { this.userRepository = userRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if(user==null){ throw new UsernameNotFoundException(username); } return new UserDetailsImpl(user); } public UserDetails loadUserByEmail(String email) throws UsernameNotFoundException { User user = userRepository.findByEmail(email); if(user==null){ throw new UsernameNotFoundException(email); } return new UserDetailsImpl(user); } }
@Component public class JWTFilter extends OncePerRequestFilter { private final JWTService jwtService; private final UserDetailsService userDetailsService; public JWTFilter(JWTService jwtService,UserDetailsService userDetailsService) { this.jwtService = jwtService; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { final String authHeader = request.getHeader("Authorization"); if(authHeader == null || !authHeader.startsWith("Bearer")) { filterChain.doFilter(request,response); return; } final String jwt = authHeader.substring(7); final String userName = jwtService.extractUserName(jwt); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if(userName !=null && authentication == null) { //Authenticate UserDetails userDetails = userDetailsService.loadUserByUsername(userName); if(jwtService.isTokenValid(jwt,userDetails)) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities() ); SecurityContextHolder.getContext() .setAuthentication(authenticationToken); } } filterChain.doFilter(request,response); } }
@Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception{ return config.getAuthenticationManager(); }
@Bean public AuthenticationProvider authenticationProvider(){ DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); authenticationProvider.setUserDetailsService(userDetailsServiceImpl); authenticationProvider.setPasswordEncoder(passwordEncoder); return authenticationProvider; }
sehingga kini kami telah melaksanakan pendaftaran, log masuk dan pengesahan tetapi bagaimana jika saya juga ingin menambah kefungsian Log Masuk Dengan Google/Github maka kami boleh melakukannya dengan bantuan OAuth2.0
OAuth 2.0 ialah protokol yang dibuat untuk kebenaran yang membolehkan pengguna memberikan aplikasi pihak ketiga akses kepada sumber yang disimpan pada platform lain (cth. Google Drive , Github) tanpa berkongsi bukti kelayakan platform tersebut.
Ia kebanyakannya digunakan dalam mendayakan log masuk sosial seperti “Log masuk dengan google” , “Log masuk dengan github ” .
Platform seperti Google , Facebook , Github menyediakan pelayan Kebenaran yang melaksanakan protokol OAuth 2.0 untuk log masuk sosial ini atau membenarkan akses .
Sekarang kita akan melihat setiap konsep satu demi satu
Pemilik sumber ialah pengguna yang ingin membenarkan aplikasi pihak ketiga (Aplikasi Anda).
Aplikasi (pihak ketiga) anda yang ingin mengakses data atau sumber daripada pelayan sumber.
Ia adalah pelayan tempat data pengguna disimpan dan akan diakses oleh aplikasi pihak ketiga.
Pelayan yang mengesahkan pemilik sumber dan mengeluarkan token akses kepada pelanggan (cth., Akaun Google).
Satu kelayakan yang dikeluarkan oleh pelayan kebenaran kepada klien, membenarkannya mengakses pelayan sumber bagi pihak pengguna. Ia biasanya berumur pendek yang akan tamat tempoh tidak lama lagi jadi token muat semula juga disediakan untuk memuat semula token akses ini supaya pengguna tidak perlu memberi kebenaran lagi.
Kebenaran khusus yang diberikan oleh pengguna, menentukan perkara yang boleh dan tidak boleh dilakukan oleh pelanggan dengan data pengguna. Sebagai contoh untuk kebenaran, kami hanya memerlukan maklumat pengguna seperti profil, nama dan lain-lain tetapi untuk akses fail skop yang berbeza diperlukan.
Ia merujuk kepada kaedah aplikasi Klien boleh mendapatkan token akses daripada Pelayan Kebenaran. Geran mentakrifkan proses dan syarat di mana permohonan pelanggan diberi kuasa untuk mengakses data dilindungi pemilik sumber.
Ia selamat kerana kami tidak perlu mendedahkan rahsia pelanggan kami dan bukti kelayakan lain kepada penyemak imbas
Terdapat dua jenis Geran yang kebanyakannya digunakan yang disediakan oleh OAuth 2.0
Ia adalah jenis geran/kaedah yang paling banyak digunakan, paling selamat dan untuk aplikasi sebelah pelayan
Dalam hal ini kod kebenaran diberikan oleh pelanggan kepada bahagian belakang dan bahagian belakang memberikan token akses kepada pelanggan.
Proses:
Digunakan oleh apl satu halaman (SPA) atau aplikasi tanpa hujung belakang. Dalam hal ini, token akses dijana secara langsung dan dikeluarkan dalam penyemak imbas itu sendiri.
Proses:
Kami akan melaksanakan kedua-duanya secara berasingan untuk pemahaman yang lengkap, tetapi mula-mula kami akan melaksanakan Pemberian Kod Kebenaran untuk itu yang kami perlukan
Pelayan Kebenaran
Ia boleh sama ada daripada platform (seperti google , github) atau anda boleh mencipta sendiri juga menggunakan KeyCloak atau juga boleh membina pematuhan anda sendiri pada piawaian OAuth 2.0 ( kami mungkin melakukan ini dalam blog seterusnya ?)
Aplikasi Spring Boot
Ini akan menjadi aplikasi/perkhidmatan bahagian belakang utama kami yang akan mengendalikan semua operasi seperti pertukaran kod, pengesahan, menyimpan butiran pengguna dan memberikan token JWT
Aplikasi React (Frontend)
Ini akan menjadi pelanggan kami yang akan mengubah hala pengguna ke Pelayan Kebenaran untuk mendapatkan kebenaran.
Jadi dalam pelaksanaan kami, apa yang akan kami lakukan ialah frontend(web/app) akan mengubah hala pengguna kami ke log masuk google dengan redirect uri ke endpoint backend kami yang akan mengawal lebih lanjut kami akan membincangkannya kemudian dan bersama-sama dengan redirect_url kami juga akan menghantar id pelanggan apl kami semua ini akan dihantar dalam parameter pertanyaan.
Tidak apabila pengguna berjaya log masuk dalam google, pelayan pengesahan(google) akan mengubah hala permintaan kami ke bahagian belakang dan di sana apa yang akan kami lakukan ialah menukar kod Keizinan dengan pelayan kebenaran untuk mendapatkan token akses dan menyegarkan token dan kemudian kami boleh mengendalikan pengesahan seperti yang kami mahu dan akhirnya kami akan menghantar balasan kembali ke bahagian hadapan kami yang akan mempunyai kuki dan ubah hala ke papan pemuka kami atau mungkin halaman yang dilindungi.
Sekarang kami akan melihat ke dalam kod itu, tetapi pastikan anda menambah url titik akhir hujung belakang anda dalam url ubah hala yang dibenarkan dalam papan pemuka konsol Google untuk klien OAuth.
* **CsrfFilter**: Validates CSRF tokens to prevent Cross-Site Request Forgery attacks. * **CorsFilter**: Manages Cross-Origin Resource Sharing (CORS) rules for secure API access from different domains. * **ExceptionTranslationFilter**: Handles security-related exceptions (e.g., invalid credentials) and sends appropriate responses to the client.
dan itu sahaja ini akan berfungsi dengan baik dan untuk ujian anda boleh membuat aplikasi bahagian hadapan yang ringkas yang hanya mempunyai konteks dan anda tahu fungsi log masuk dan pendaftaran.
Terima kasih kerana membaca sehingga ini lama, dan jika anda mempunyai sebarang cadangan, sila letakkannya dalam komen
Atas ialah kandungan terperinci Memahami Spring Security dan OAuth. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!