序文
一般的な一般的な認証フレームワークには、アパッチのシロとスプリングファミリースプリングセキュリティが含まれています。今日のマイクロサービス認証に関しては、承認フレームワークを使用して独自の認証サービスを構築する必要があります。今日、首相は首相でした。
Spring Securityは、主に認証(認証、ソリューション、あなたは誰ですか?)とアクセス制御(アクセス制御、つまり、許可されていること、認可としても知られています)を実装します。 Spring Securityは、認証と認証アーキテクチャを分離し、拡張ポイントを提供します。
コアオブジェクト
メインコードは、スプリングセキュリティコアパッケージの下にあります。 Spring Securityを理解するには、内部のコアオブジェクトに注意を払う必要があります。
SecurityContextholder、SecurityContext、および認証
SecurityContextholderは、SecurityContext用のストレージコンテナです。 Threadlocalストレージはデフォルトで使用されます。つまり、SecurityContextメソッドは同じスレッドで使用できます。
SecurityContextは主にアプリケーションの主要な情報を保存し、Spring Securityの認証によって表されます。
校長を取得:
Object Principal = securityContextholder.getContext()。getAuthentication()。getPrincipal(); if(sprince instanceof userdetails){string username =((userdetails)rinpartice).getusername();} els {string username = rincipal.tostring();};}Spring Securityでは、認証定義を確認できます。
パブリックインターフェイス認証は、プリンシパル、シリアル化可能{collection <? adrentadauthority> getauthorities(); / ***通常はパスワード*/ object getcredentials(); /***認証要求に関する追加の詳細を保存します。これらは、IP *アドレス、証明書のシリアル番号などです。 */ object getDetails(); /***認証されているかどうかを識別するために使用されます。ユーザー名とパスワードでログインする場合、通常はユーザー名*/オブジェクトGetPrincipal()です。 / ***それが認証されているかどうか*/ boolean isauthenticated(); void setauthenticated(boolean isauthenticated)は違法なものをスローします;}実際のアプリケーションでは、usernamepasswordauthenticationtokenが通常使用されます。
パブリックアブストラクトクラスの抽象的なAuthenticationTokenは、認証を実装しています、recudentiveScontainer {} public class usernamepasswordauthenticationtokenはabstractAuthenticationToken {}を拡張します一般的な認証プロセスは通常、次のようなものです。UsernAmePassWordAuthenticationTokenを作成し、認証のためにAuthenticationManagerに引き渡します(後で詳しく説明します)。認証が渡された場合、認証情報はSecurityContextholderを介して保存されます。
usernamepasswordauthenticationtoken authenticationToken = new usernamepasswordauthenticationToken(loginvm.getusername()、loginvm.getPassword());認証承認= this.authenticationmanager.authenticate(auteputiontoken); securitycontextholder.getContextion()。
userdetailsおよびuserdetailsService
userdetailsは、プリンシパルを表すために使用されるSpring Securityの重要なインターフェイスです。
Public Interface userDetails extends serializable { / ***ユーザー認証情報は、役割* / collection <として理解できます<? adrentadauthority> getauthorities(); / ** *ユーザーパスワード * * @returnパスワード */ string getPassword(); / *** username**/ string getUsername(); boolean isaccountnonexpired(); boolean isaccountnonlocked(); boolean iscredentialsnonexpired(); boolean isEnabled();}userdetailsは、認証に必要な情報を提供します。実際には、自分でユーザーデテールを実装して、電子メール、モバイル、その他の情報などの追加情報を追加できます。
認証では、プリンシパルは通常ユーザー名です。 userdetailsServiceを介してプリンシパルを介してユーザーデテールを取得できます。
public interface userdetailsService {userdetails loaduserbyusername(string username)throws usernamenotfoundexception;}許可
userdetailsで述べたように、ardentauthorityは、chole_administratorやrole_hr_supervisorなどの役割として理解できます。
まとめ
認証認定
AuthenticationManager
認証は、主にAuthenticationManagerインターフェイスを通じて達成されます。これには、1つの方法のみが含まれています。
public interface authenticationmanager {authentication authentication(authentication authentication)slows authenticationexception;}Authentate()メソッドは主に3つのことを行います。
AuthenticationExceptionはランタイムの例外であり、通常、アプリケーションによって一般的な方法で処理されます。ユーザーコードは通常、具体的にキャッチして処理する必要はありません。
AuthenticationManagerのデフォルトの実装はProviderManagerであり、認証を実装するためにAuthenticationProviderインスタンスのセットを委任します。
AuthenticationProviderとAuthenticationManagerは認証に似ており、どちらも認証を含んでいますが、発信者が特定の認証タイプをサポートするかどうかを照会するための追加のメソッドサポートがあります。
Public Interface AuthenticationProvider {Authentication Authentication(Authentication Authentication)Slows AuthenticationException;ブールサポート(クラス<?>認証);}ProviderManagerには、authenticationProvidersのセットが含まれています。認証を実行すると、プロバイダーを横断してからサポートを呼び出します。サポートされている場合、現在のプロバイダーを通過する認証メソッドを実行します。プロバイダーが正常に認証されている場合は、壊してください。
Public Authentication Authentication(Authentication Authentication)Slows AuthenticationException {class <?認証> totest = authentication.getClass();認証Exception lastException = null;認証結果= null; boolean debug = logger.isdebugenabled(); for(authenticationProviderプロバイダー:getProviders()){if(!provider.supports(totest)){contion; } if(debug){logger.debug( "" + provider.getClass()。getName())を使用した認証試行試行} try {result = provider.authenticate(authentication); if(result!= null){copydetails(authentication、result);壊す; }} catch(AccountStatusexception e){prepereexception(e、authentication); // SEC-546:認証障害が//無効なアカウントステータススローEに起因している場合、追加のプロバイダーのポーリングを避けます。 } catch(internalAuthEnticationServiceException e){preateexception(e、authentication); eを投げる; } catch(authenticationexception e){lastexception = e; }} if(result == null && parent!= null){//親の試行を許可します。 try {result = parent.authenticate(authentication); } catch(providernotfoundexception e){//親を呼び出す前に他の例外が発生しなかった場合、以下にスローするときに無視する// }} if(result!= null){if(erasecredentionsafterauthentication &&(result instanceof credentientscontainer)){//認証は完了しました。認証から資格情報やその他の秘密データ//を削除する((recurtentiveScontainer)result).erasecredentials(); } eventpublisher.publishauthenticationsucces(result);返品結果; } //親はnullでした、または認証しませんでした(または例外をスローします)。 if(lastException == null){lastException = new ProvidernotFoundException(message.getMessage( "providermanager.providernotfound"、new object [] {totest.getName()}、 "{0}"); } prepereeException(lastException、authentication); lastExceptionを投げます。 }上記のコードからわかるように、ProviderManagerにはオプションの親がいます。親が空でない場合、parent.authenticate(認証)が呼び出されます
AuthenticationProvider
AuthenticationProviderには多くの実装があります。あなたが最も心配しているのは、通常、abstractuserdetailsauthenticationProviderから継承されたdaouthenticationProviderです。コアは、ユーザーデテールを介して認証を実装することです。 DaoAuthenticationProviderはデフォルトで自動的にロードされ、手動で構成する必要はありません。
まず、AbstractUserdetailsauthenticationProviderを見て、最もコア認証を見てみましょう。
Public Authentication Authentication(Authentication Authentication)Slows AuthenticationException {// bat usernamepasswordauthenticatoken assert.isinstanceof(usernamepasswordauthenticationtoken.class.class、authentication、mesages.getmessage( "abstract ustailsauthenticationprovider.onysupports" // get username string username =(authentication.getPrincipal()== null)? 「none_provided」:authentication.getname(); boolean cachewasused = true; // cache user = this.usercache.getuserfromcache(username)からuserdetailsを取得します。 if(user == null){cachewasused = false; try {// retiveuser abstractメソッドを取得するユーザーユーザー= retiveUser(username、(usernamepasswordauthentication)認証); } catch(usernamenotfoundexception notfound){logger.debug( "user '" + username + "' 'not found"); if(hideusernotfoundexceptions){new badcredentialsexception(message.getMessage( "abststractuserdetailsauthenticationProvider.badcredentials"、 "bad credentials")); } else {throw notfound; }} assert.notnull(user、 "retrieveuserはnullを返しました - インターフェイス契約の違反」); } try {// pre-check、defaultpreauthenticationChecks、ユーザーがロックされているかどうか、またはアカウントがpreAuthenticationChecks.check(user)に利用可能かどうかを確認します。 //要約方法、カスタムチェックAddationAuthenticationChecks(user、(usernamepasswordauthentication)認証); } catch(authenticationexception exception){if(cachewasused){//問題があったので、チェックしてから再試行//最新のデータ(つまり、キャッシュからではない)を使用しています。 user = retiveUser(username、(usernamepasswordauthentication)authentication); preuthenticationChecks.check(user); AdittionAuthenticationChecks(user、(usernamepasswordauthentication)認証); } else {スロー例外; }} // defaultpostauthenticationChecksを確認してください。 if(!cachewasused){this.usercache.putuserincache(user); } object principaltoreturn = user; if(forceprincipalastring){rignationtoreturn = user.getUsername(); } return createSuccessAuthentication(spinitaltoreturn、authentication、user); }上記のテストは、主にユーザーデテールの実装に基づいており、ユーザーの獲得と検証ロジックは特定のクラスによって実装されます。デフォルトの実装はDaoAuthenticationProviderです。このクラスのコアは、開発者がユーザーデテールサービスを提供してユーザーデテールとパスワードエンコーダーを取得して、パスワードが有効かどうかを確認できるようにすることです。
Private userDetailsService userDetailsService;プライベートPasswordEncoder PasswordEncoder;
特定の実装を確認するには、RetrieveUserを確認して、ユーザーを直接呼び出してユーザーを取得します。
保護された最終userdetails RetrieveUser(String username、usernamepasswordauthenticationtoken認証)Slows exhindicationException {userdetails loadeduser; try {loadeduser = this.getuserdetailsservice()。loaduserbyusername(username); } catch(usernamenotfoundexception notfound){if(authentication.getCreDentions()!= null){string presentedpassword = authentication.getCreDentions()。toString(); PasswordEncoder.ispasswordValid(usernotfoundencodedpassword、presentedpassword、null); }スローしない; } catch(Exception RepositoryProblem){新しいinternalAuthEnticationServiceException(repositoryProblem.getMessage()、RepositoryProblem); } if(loadeduser == null){new new internalAuthentionserviceException( "userdetailsserviceがnullを返しました。これはインターフェイス契約違反です"); } loadeduserを返します。 }検証を見てみましょう:
保護されているvoid aditionalAuthenticationChecks(userdetails userdetails、usernamepasswordauthenticationetoken認証)Sthrows authenticationexception {object salt = null; if(this.saltsource!= null){salt = this.saltsource.getSalt(userdetails); } if(authentication.getcredentials()== null){logger.debug( "認証障害:資格情報なし");新しいbadcredentialsexception(messages.getMessage( "abstractuserdetailsauthenticationprovider.badcredentials"、 "bad credentials"))を投げます。 } //ユーザーのパスワード文字列を取得しますpresshedpassword = authentication.getCreDentials()。toString(); // PasswordEncoderの後のパスワードがuserdetailsのパスワードと同じかどうかを比較しますif(!passhipencoder.ispasswordvalid(userdetails.getPassWord()、presentedpassword、salt)){ogger.debug( "認証障害:パスワードは保存値と一致しません");新しいbadcredentialsexception(messages.getMessage( "abstractuserdetailsauthenticationprovider.badcredentials"、 "bad credentials"))を投げます。 }}概要:認証をカスタマイズするには、DaoAuthenticationProviderを使用するには、PasswordEncoderとuserdetailsServiceを提供するだけです。
認証マネージャーをカスタマイズします
Spring Securityは、Builder Class AuthenticationManagerBuilderを提供します。これにより、カスタム認証をすばやく実装できます。
公式ソースコードの説明を参照してください:
SecurityBuilderは、AuthenticationManagerの作成に使用されていました。メモリ認証、LDAP認証、JDBCベースの認証、UserDetailsServiceの追加、AuthenticationProviderの追加を簡単に構築できます。
AuthenticationManagerBuilderを使用して、AuthenticationManagerを構築することができます。これは、メモリベースの認証、LDAP認証、JDBC認証を作成し、UserDetailsServiceとAuthenticationProviderを追加できます。
簡単な使用:
@configuration@enablewebsecurity@enableglobalmethodsecurity(prepostenabled = true、securedenabled = true)public class applicationsecurity extends websecurityconfigureradapter {public managerbuilder eusitivitiationManagerbuilder、usersersserssersprovede、corkensersvider、 corsfilter、securityproblemsupport subpport){this.authenticationmanagerbuilder = authenticationmanagerbuilder; this.userdetailsService = userdetailsService; this.tokenprovider = tokenprovider; this.corsfilter = corsfilter; this.problemsupport = resumpupport; } @PostConstruct public void init(){try {authenticationManagerBuilder .UserDetailsService(userDetailsService).PassWordENCoder(PasswordEncoder()); } catch(Exception e){新しいbeanInitializationexception( "セキュリティ構成に失敗した"、e); }} @Override Protected void configure(httpsecurity http)スロー例外{http .addfilterbeforter(corsfilter、usernamepasswordauthenticationfilter.class).exceptionhandling().authenticationEntrypoint(expferentsupport).accessenied handler(expbesuntry(expresonfort).cesseniedler().conport()。 .disable().headers().frameoptions().disable().and().sessionmanagement().sessioncreationpolicy(sessioncreationpolicy.stateless).and().Antorizequests().AntMatchers().AntMatchers( "/API/Register")。 .AntMatchers( "/api/authenticate") .AntMatchers( "/api/**") .AntMatchers( "/swagger-resources/configuration/ui")。pimitall().AntMatchers( "/swagger-ui/index.html")。hasauthority(AltialitiesConstants.admin)。 }}承認とアクセス制御
認証が成功したら、AccessedecisionManagerを通じて実装される承認を継続できます。フレームワークには3つの実装があり、デフォルトは肯定ベースであり、AccessDecisionVoterを介して行われます。
public void decision(Authentication Authentication、object object、collection <configattribute> configattributes)throws AccessDeniedException {int deny = 0; // Traversal DecisionVoter for(AccessDecisionVoter投票者:getDecisionVoters()){//投票int result = mote.vote(authentication、object、configattributes); if(logger.isdebugenabled()){logger.debug( "投票者:" +投票者 + "、returned:" + result); } switch(result){case accessdecisionvoter.access_granted:return; case AccessDecisionVoter.access_denied:deny ++;壊す;デフォルト:break; }} // veto if(deny> 0){throw new AccessDeniedException(message.getMessage( "AbstractAccessDecisionManager.Accessdenied"、 "アクセスは拒否されます"); } //これを取得するために、すべてのAccessDecisionVoterがCheckAllowifallabstaindecisions()を棄却しました。 }AccessDecisionVoterをご覧ください。
boolean supports(configattribute attribute); boolean supports(class <?> clazz); int投票(認証認証、sオブジェクト、collection <configattribute> attributes);
オブジェクトは、ユーザーがアクセスしたいリソースであり、設定はオブジェクトを満たす必要がある条件です。通常、ペイロードは、role_adminなどの文字列です。それでは、Rolevoterの実装を見てみましょう。コアは、認証から付与承認を抽出し、条件が満たされているかどうかを構成と比較することです。
public boolean supports(configattribute attribute){if((actiont.getAttribute()!= null)&& attibut.getattribute()。startswith(getRolePrefix()){return true; } else {return false; }} public boolean supports(class <?> clazz){return true; } public int投票(Authentication Authentication、object object、collection <configattribute> attributes){if(authentication == null){return Access_denied; } int result = access_abstain; // grantedauthority情報コレクションを取得<?拡張authedauthority>当局=抽出(認証); for(configAttribute attribute:attributes){if(this.supports(属性)){//デフォルトで拒否されたアクセスresult = access_denied; //(付与承認当局:当局)のマッチング許可当局を見つけようとする{//一致する権限があるかどうかを判断}}}} return result; }ここで私は尋ねなければなりません、configattributeはどこから来たのですか?実際、それは上記のアプリケーションセキュリティの構成にあります。
Webセキュリティを実装する方法
WebレイヤーのSpring Security(UIおよびHTTPバックエンドの場合)はサーブレットフィルターに基づいており、次の図は、単一のHTTP要求のハンドラーの典型的な階層を示しています。
Spring Securityは、フィルターを単一のフィルターとしてFilterChainProxyを介してWebレイヤーに登録され、プロキシ内のフィルター。
FilterChainProxyは、フィルター容器に相当します。 VirtualFilterChainを介して、各内部フィルターが順番に呼び出されます。
public void dofilter(servletRequest request、servletResponse応答、フィルターチェーンチェーン)IoException、servletexception {boolean clearContext = request.getAttribute(filter_applied)== null; if(clearContext){try {request.setattribute(filter_applied、boolean.true); dofilterinternal(リクエスト、応答、チェーン); }最後に{securitycontextholder.clearcontext(); request.removeattribute(filter_applied); }} else {dofilterinternal(request、response、chain); }} private void dofilterinternal(servletrequest request、servletresponse response、filterchain chain)は、ioexception、servledRequest fwrequest = firewall .getfirewalledrequest((httpservletrequest)リクエスト)をスローします。 httpservletResponse fwResponse = firewall .getFireWallEdResponse((httpservletResponse)応答); list <filter> filters = getFilters(fwRequest); if(filters == null || filters.size()== 0){if(logger.isdebugenabled()){logger.debug(urlutils.buildrequesturl(fwrequest) +(filters == null?」 } fwrequest.reset(); Chain.dofilter(fwrequest、fwresponse);戻る; } VirtualFilterChain VFC = new VirtualFilterChain(fwRequest、チェーン、フィルター); vfc.dofilter(fwrequest、fwresponse); } private static class virtualfilterchainを実装している{private final finalchain originalchain;プライベート最終リスト<filter>追加フィルター。プライベートファイナルファイアウォールレクエストFirewalledRequest;プライベートファイナルINTサイズ。 private int currentposition = 0; Private VirtualFilterChain(FireWalledRequest FireWalledRequest、FilterChain Chain、List <filter> AdditionalFilters){this.OriginalChain = Chain; this.additionalfilters = adthideFilters; this.size = adlidationfilters.size(); this.firewalledRequest = firewalledRequest; } public void dofilter(servletRequest request、servletResponse応答)IoException、servletexception {if(currentPosition == size){if(logger.isdebugenabled()){logger.debug(urlutils.buildrequesturl(urlutils.buildrequesturl) } //パスストライピングを非アクティブ化するときにセキュリティフィルターチェーンを終了します。 originalchain.dofilter(リクエスト、応答); } else {currentPosition ++; Filter nextfilter = aditionalfilters.get(currentPosition -1); if(logger.isdebugenabled()){logger.debug(urlutils.buildrequesturl(firewalledRequest) + "at position" + currentposition + "of" + size + "of" + size + "of" + size + "追加フィルター: '" + nextfilter.getclass()。 } nextfilter.dofilter(request、response、this); }}}}参照してください
https://spring.io/guides/topicals/spring-security-architecture/
https://docs.spring.io/spring-security/site/docs/5.0.5.Release/reference/htmlsingle/#overall-architecture
要約します
上記は、この記事のコンテンツ全体です。この記事の内容には、すべての人の研究や仕事に特定の参照値があることを願っています。ご質問がある場合は、メッセージを残してコミュニケーションをとることができます。 wulin.comへのご支援ありがとうございます。