Kata pengantar
Saat menggunakan SpringCloud untuk membangun sistem terdistribusi dengan arsitektur Microservice, OAuth2.0 adalah standar industri untuk sertifikasi. Spring Security OAuth2 juga menyediakan serangkaian solusi lengkap untuk mendukung penggunaan OAuth2.0 di lingkungan Spring Cloud/Spring Boot, menyediakan komponen di luar kotak. Namun, selama proses pengembangan, kami akan menemukan bahwa karena komponen keamanan musim semi OAuth2 sangat komprehensif, ini membuatnya sangat tidak nyaman untuk diperluas atau tidak mudah untuk secara langsung menentukan solusi ekstensi, seperti:
Saat menghadapi skenario ini, diharapkan banyak orang yang tidak terbiasa dengan musim semi keamanan OAuth2 tidak akan dapat memulai. Berdasarkan persyaratan skenario di atas, cara mengintegrasikan login kode verifikasi SMS secara elegan dan login pihak ketiga, dan bagaimana dianggap terintegrasi secara elegan? Ada persyaratan berikut:
Berdasarkan persyaratan desain di atas, kami akan memperkenalkan secara rinci cara mengembangkan serangkaian komponen otentikasi login terintegrasi untuk memenuhi persyaratan di atas dalam artikel.
Baca artikel ini yang perlu Anda ketahui tentang sistem sertifikasi OAuth2.0, Springboot, Springsecurity, Spring Cloud dan pengetahuan terkait lainnya
Ide
Mari kita lihat proses otentikasi musim semi OAuth2:
Dalam proses ini, tidak ada banyak titik masuk, dan gagasan login terintegrasi adalah sebagai berikut:
Setelah mengakses proses ini, Anda pada dasarnya dapat mengintegrasikan login pihak ketiga secara elegan.
menyelesaikan
Setelah memperkenalkan ide -ide, kode berikut menunjukkan cara mengimplementasikannya:
Langkah pertama adalah mendefinisikan interseptor untuk mencegat permintaan login
/** * @Author liqiu * @date 2018-3-30 **/ @ComponentPublic Class IntegrationAuthenticationFilter Memperluas GenericFilterBean mengimplementasikan ApplicationContextAware {private static final string auth_type_parm_name = "auth_type"; string final statis pribadi oauth_token_url = "/oauth/token"; Koleksi Pribadi <TegrationAuthenticator> AUTENTICATOR; Private ApplicationContext ApplicationContext; permintaan pribadi permintaan requestMatcher; Public IntegrationAuthenticationFilter () {this.RequestMatcher = New OrrequestMatcher (AntpathRequestMatcher baru (oauth_token_url, "get"), antpathrequestmatcher baru (oauth_token_url, "pos")); } @Override public void dofilter (ServletRequest ServletRequest, ServletResponse ServletResponse, FilterChain FilterChain) melempar ioException, ServletException {httpservletRequest request = (httpservletRequest) ServletRequest; HttpservletResponse respons = (httpservletResponse) ServletResponse; if (requestMatcher.matches (request)) {// atur integrasi Integrasi Integrasi Integrasiutikasi IntegrationAuthentication = IntegrationAuthentication baru (); integrationAuthentication.setAuthType (request.getParameter (auth_type_parm_name)); integrationAuthentication.setAuthParameters (request.getParametermap ()); IntegrationAuthenticationContext.set (IntegrationAuthentication); coba {// preprocessing this.prepare (integrationAuthentication); filterchain.dofilter (permintaan, respons); // post-processing this. } akhirnya {IntegrationAuthenticationContext.Clear (); }} else {filterchain.dofilter (request, response); }} / *** preprocessing* @param IntegrationAuthentication* / private void persiapan (integrasi integrasi integrasiutikasi) {// malas memuat authenticator if (this.authenticators == null) {disinkronkan (this) {peta <string, integrationAticators == null) {Synchronized (this) {MAP <string, integrationAtikator> null) applicationContext.getBeansOfType (integrasionuthenticator.class); if (integrationAuthentiCaTorMap! = null) {this.authenticators = integrationAuthenticaTormap.values (); }}} if (this.authenticators == null) {this.authenticators = new ArrayList <> (); } untuk (IntegrationAuthenticator Authenticator: Authenticators) {if (authenticator.support (IntegrationAuthentication)) {authenticator.prepare (IntegrasionAuthentication); } } } /** * Post-processing* @param integrationAuthentication */ private void complete(IntegrationAuthentication integrationAuthentication){ for (IntegrationAuthenticator authentication: authentications) { if(authenticator.support(integrationAuthentication)){ authentication.complete(integrationAuthentication); }}} @Override public void setApplicationContext (ApplicationContext ApplicationContext) melempar BeansException {this.applicationContext = ApplicationContext; }}Di kelas ini, dua bagian pekerjaan terutama diselesaikan: 1. Dapatkan tipe otentikasi saat ini sesuai dengan parameter, 2. Panggil integrasi yang berbeda.
Langkah 2: Masukkan interseptor ke dalam rantai intersep
/** * @author liqiu * @date 2018-3-7 **/ @configuration @enableAuthorizationServerpublic Kelas otorisasiCerverFiguration Memperluas otorisasi yang lebih rendah hati; @Autowired Private AuthenticationManager AuthenticationManager; @Autowired Private IntegrationUserDetailsService IntegrationUserdetailsService; @Autowired pribadi WebResponseExceptionTranslator WebResponseExceptionTranslator; @Autowired Private IntegrationAuthenticationFilter IntegrationAuthenticationFilter; @Autowired Private DataBaseCachableClientDetailsService RedisClientDetailsService; @Override public void configure (clientDetailsServiceConfigurer klien) melempar Exception {// TODO SENGHET Klien merinci klien. TwithClientDetails (RedisClientDetailsService); } @Override public void configure (otorisasiServerEndPointSconfigurer Endpoints) {endpoints .tokenStore (redistokenStore baru (redisconnectionFactory)) // .AccesstokeConverter (JWTACCESSTECCEPRANSCEPRANSEPRANSEPRANSEPRANSEPRANSEPRANSEPRANSEPRANSEPRANSEPRANSEPRANSEPRANSEPRANSEPRANSEPRANSEPRANSECRANSECRANSECERTER (). .reuserefreshtokens (false) .userdetailsservice (integrationUserdetailsservice); } @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { security.allowFormAuthenticationForClients() .tokenKeyAccess("isAuthenticated()") .checkTokenAccess("permitAll()") .addTokenEndpointAuthenticationFilter(integrationAuthenticationFilter); } @Bean Public PasswordEncoder PasswordEncoder () {return bcryptpasswordEncoder baru (); } @Bean public jwtaccesstokenconverter jwtaccesstokenconverter () {jwtaccesstokenconverter jwtaccesstokenconverter = jwtaccesstokenconverter () baru; jwtaccesstokenconverter.setsigningkey ("cola-cloud"); return JWTACCESSTOKENCONVERTER; }}Masukkan Interceptor ke dalam rantai otentikasi dengan memanggil keamanan. .addTokenendPointAuthenticationFilter (IntegrationAuthenticationFilter); metode.
Langkah 3: Proses informasi pengguna sesuai dengan jenis otentikasi
@Servicepublic kelas integrationUserDetailsService mengimplementasikan userDetailsService {@Autowired private upmClient upmClient private; Daftar Pribadi <TegrationAuthenticator> AUTENTICATOR; @Autowired (wajib = false) public void setIntegrationAuthenticators (Daftar <TegrationAuthenticator> AUTENTICATORS) {this.authenticators = authenticators; } @Override User Public LoadUserByUserName (String Username) melempar UserNamenotFoundException {IntegrationAuthentication IntegrationAuthentication = IntegrationAuthenticationContext.get (); // menilai apakah itu login terintegrasi IF (integrationAuthentication == null) {integrationAuthentication = integrationAuthentication baru (); } integrationAuthentication.setUserName (nama pengguna); UserVo userVo = this.Athenticate (integrationAuthentication); if (uservo == null) {lempar UserNamenotFoundException baru ("Username atau Kata Sandi Kesalahan"); } Pengguna pengguna = pengguna baru (); Beanutils.copyproperties (uservo, pengguna); this.setAuthorize (pengguna); Pengguna Kembali; } / ** * Tetapkan informasi otorisasi * * @param pengguna * / public void setAuthorize (pengguna pengguna) {otorize otorize = this.upmclient.getAuthorize (user.getId ()); user.setroles (otorize.getroles ()); user.setresources (otorize.getResources ()); } private uservo authenticate (integrasionutikasi integrasiutikasi) {if (this.authenticators! = null) {for (integrasionutikator authenticator: authenticators) {if (authenticator.support (integrasion)) {return authenticator.authenticate (integrasionicate (integrasion)) {return authenticator. }} return null; }}Berikut ini adalah integrationUserdetailsService. Metode otentikasi akan dipanggil dalam metode LoadUserbyUnername. Dalam metode otentikasi, tipe otentikasi konteks saat ini akan menghubungi integrationAuthenticator yang berbeda untuk mendapatkan informasi pengguna. Mari kita lihat bagaimana nama pengguna dan kata sandi default ditangani:
@Component @primarypublic kelas UsernamepasswordAuthenticator memperluas abstrakPreparableIntegrationAuthenticator {@autowired private ucclient ucclient; @Override Public UserVo Authentication (IntegrationAuthentication IntegrationAuthentication) {return ucclient.finduserbyUserName (integrationAuthentication.getUserName ()); } @Override public void persiapan (integrasionutikasi integrasiutikasi) {} @Override dukungan boolean publik (integrasi integrationAuthentication) {return stringutils.isempty (integrationAuthentication.getAuthType ()); }}UsernamepasswordAuthenticator hanya akan menangani tipe otentikasi default tanpa jenis otentikasi yang ditentukan. Kelas ini terutama memperoleh kata sandi melalui nama pengguna. Selanjutnya, mari kita lihat cara menangani login kode verifikasi gambar:
/*** Otentikasi kode verifikasi terintegrasi* @Author liqiu* @date 2018-3-31 **/ @ComponentPublic Class VerificationCodeIntegationAuthenticator Memperluas USERNAMEPASSWORDAuthENTICATOR {private final string statis verifikasi_code_auth_type = "vc"; @Autowired private vccClient vccClient; @Override public void persiapan (integrasiuthentication integrationAuthentication) {string vctoken = integrationAuthentication.getAuthParameter ("vc_token"); String vccode = integrationAuthentication.getAuthParameter ("vc_code"); // verifikasi verifikasi coderesult <boolean> hasil = vccclient.validate (vctoken, vccode, null); if (! result.getData ()) {lempar OAuth2Exception baru ("Kesalahan kode verifikasi"); }} @Override Dukungan Boolean Publik (IntegrationAuthentication IntegrationAuthentication) {return verification_code_auth_type.equals (integrationAuthentication.getAuthType ()); }}VerificationCodeIntegrationAuthenticator mewarisi USERNAMEPASSWORDAuthenticator karena hanya perlu memverifikasi apakah kode verifikasi benar dalam metode persiapan, dan apakah pengguna telah memperolehnya dengan menggunakan nama pengguna dan kata sandi. Namun, jenis otentikasi adalah "VC" sebelum dapat diproses. Mari kita lihat bagaimana login kode verifikasi SMS ditangani:
@ComponentPublic SMSIntegrationAuthenticator memperluas AbstrakPreparable IntegrationAuthenticator mengimplementasikan ApplicationEventPubLisherAware {@Autowired private ucclient ucclient; @Autowired private vccClient vccClient; @Autowired Private PasswordEncoder Kata SandiCoder; Private ApplicationEventPubLisher ApplicationEventPublisher; Private Final Static String SMS_Auth_Type = "SMS"; @Override Public UserVo Authentication (IntegrationAuthentication IntegrationAuthentication) {// Dapatkan Kata Sandi, Nilai Aktual adalah Kode Verifikasi Kata sandi = IntegrationAuthentication.getAuthParameter ("Kata Sandi"); // Dapatkan Nama Pengguna, nilai sebenarnya adalah Username Nomor Ponsel = IntegrationAuthentication.getUserName (); // Publikasikan acara, Anda dapat mendengarkan acara untuk secara otomatis mendaftarkan pengguna this.applicationEventPublisher.publishevent (smsAuthenticateBeforeEvent baru (integrationAuthentication)); // query pengguna melalui nomor ponsel userervo userVo = this.ucclient.finduserbyphonenumber (nama pengguna); if (userVo! = null) {// atur kata sandi sebagai kode verifikasi userVo.setPassword (kata sandicoder.encode (kata sandi)); // Publikasikan acara, Anda dapat mendengarkan acara untuk pemberitahuan pesan this.applicationEventPublish.publishevent (smsAuthenticateCessEvent baru (integrationAuthentication)); } return uservo; } @Override public void persiapan (integrasionutikasi integrasiutikasi) {string smstoken = integrasionuthentication.getAuthParameter ("sms_token"); String smscode = integrationAuthentication.getAuthParameter ("kata sandi"); String username = integrationAuthentication.getAuthParameter ("nama pengguna"); Hasil <boolean> hasil = vccclient.validate (smstoken, smscode, nama pengguna); if (! result.getData ()) {lempar OAuth2Exception baru ("Kesalahan kode verifikasi atau kedaluwarsa"); }} @Override Public Boolean Support (IntegrationAuthentication IntegrationAuthentication) {return sms_auth_type.equals (integrationAuthentication.getAuthType ()); } @Override public void setApplicationEventPublisher (ApplicationEventPublisher ApplicationEventPublisher) {this.applicationEventPublisher = ApplicationEventPublisher; }}SMSIntegrationAuthenticator akan melakukan preprocess kode verifikasi SMS yang masuk untuk menentukan apakah itu ilegal. Jika ilegal, itu akan secara langsung mengganggu login. Jika preprocessing dilewati, informasi pengguna akan diperoleh melalui nomor ponsel saat mendapatkan informasi pengguna, dan kata sandi akan diatur ulang untuk lulus verifikasi kata sandi berikutnya.
Meringkaskan
Dalam solusi ini, penggunaan utama rantai tanggung jawab dan pola desain adaptor untuk menyelesaikan masalah login terintegrasi, meningkatkan skalabilitas, dan tidak mencemari kode sumber pegas. Jika Anda ingin mewarisi login lain, Anda hanya perlu mengimplementasikan integrationAuthenticator khusus.
Alamat Proyek: https://gitee.com/leecho/cola-cloud
Unduh Lokal: Cola-cloud_jb51.rar
Di atas adalah semua konten artikel ini. Saya berharap ini akan membantu untuk pembelajaran semua orang dan saya harap semua orang akan lebih mendukung wulin.com.