<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                ## 動手實現一個IP_Login 在開始這篇文章之前,我們似乎應該思考下為什么需要搞清楚Spring Security的內部工作原理?按照第二篇文章中的配置,一個簡單的表單認證不就達成了嗎?更有甚者,為什么我們不自己寫一個表單認證,用過濾器即可完成,大費周章引入Spring Security,看起來也并沒有方便多少。對的,在引入Spring Security之前,我們得首先想到,是什么需求讓我們引入了Spring Security,以及為什么是Spring Security,而不是shiro等等其他安全框架。我的理解是有如下幾點: * 在前文的介紹中,Spring Security支持防止csrf攻擊,session-fixation protection,支持表單認證,basic認證,rememberMe…等等一些特性,有很多是開箱即用的功能,而大多特性都可以通過配置靈活的變更,這是它的強大之處。 * Spring Security的兄弟的項目Spring Security SSO,OAuth2等支持了多種協議,而這些都是基于Spring Security的,方便了項目的擴展。 * SpringBoot的支持,更加保證了Spring Security的開箱即用。 * 為什么需要理解其內部工作原理?一個有自我追求的程序員都不會滿足于淺嘗輒止,如果一個開源技術在我們的日常工作中十分常用,那么我偏向于閱讀其源碼,這樣可以讓我們即使排查不期而至的問題,也方便日后需求擴展。 * Spring及其子項目的官方文檔是我見過的最良心的文檔!相比較于Apache的部分文檔 * 這一節,為了對之前分析的Spring Security源碼和組件有一個清晰的認識,介紹一個使用IP完成登錄的簡單demo。 ### 5.1 定義需求 在表單登錄中,一般使用數據庫中配置的用戶表,權限表,角色表,權限組表…這取決于你的權限粒度,但本質都是借助了一個持久化存儲,維護了用戶的角色權限,而后給出一個/login作為登錄端點,使用表單提交用戶名和密碼,而后完成登錄后可自由訪問受限頁面。 在我們的IP登錄demo中,也是類似的,使用IP地址作為身份,內存中的一個ConcurrentHashMap維護IP地址和權限的映射,如果在認證時找不到相應的權限,則認為認證失敗。 實際上,在表單登錄中,用戶的IP地址已經被存放在Authentication.getDetails()中了,完全可以只重寫一個AuthenticationProvider認證這個IP地址即可,但是,本demo是為了厘清Spring Security內部工作原理而設置,為了設計到更多的類,我完全重寫了IP過濾器。 ### 5.2 設計概述 我們的參考完全是表單認證,在之前章節中,已經了解了表單認證相關的核心流程,將此圖再貼一遍: ![](https://box.kancloud.cn/65ea8158ec5c03f1f64f9dd7e446f773_701x397.png) 在IP登錄的demo中,使用IpAuthenticationProcessingFilter攔截IP登錄請求,同樣使用ProviderManager作為全局AuthenticationManager接口的實現類,將ProviderManager內部的DaoAuthenticationProvider替換為IpAuthenticationProvider,而UserDetailsService則使用一個ConcurrentHashMap代替。更詳細一點的設計: * IpAuthenticationProcessingFilter–>UsernamePasswordAuthenticationFilter * IpAuthenticationToken–>UsernamePasswordAuthenticationToken * ProviderManager–>ProviderManager * IpAuthenticationProvider–>DaoAuthenticationProvider * ConcurrentHashMap–>UserDetailsService ### 5.3 IpAuthenticationToken ~~~ public class IpAuthenticationToken extends AbstractAuthenticationToken { private String ip; public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public IpAuthenticationToken(String ip) { super(null); this.ip = ip; super.setAuthenticated(false);//注意這個構造方法是認證時使用的 } public IpAuthenticationToken(String ip, Collection<? extends GrantedAuthority> authorities) { super(authorities); this.ip = ip; super.setAuthenticated(true);//注意這個構造方法是認證成功后使用的 } @Override public Object getCredentials() { return null; } @Override public Object getPrincipal() { return this.ip; } } ~~~ 兩個構造方法需要引起我們的注意,這里設計的用意是模仿的UsernamePasswordAuthenticationToken,第一個構造器是用于認證之前,傳遞給認證器使用的,所以只有IP地址,自然是未認證;第二個構造器用于認證成功之后,封裝認證用戶的信息,此時需要將權限也設置到其中,并且setAuthenticated(true)。這樣的設計在諸多的Token類設計中很常見。 ### 5.4 IpAuthenticationProcessingFilter ~~~ public class IpAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter { //使用/ipVerify該端點進行ip認證 IpAuthenticationProcessingFilter() { super(new AntPathRequestMatcher("/ipVerify")); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException { //獲取host信息 String host = request.getRemoteHost(); //交給內部的AuthenticationManager去認證,實現解耦 return getAuthenticationManager().authenticate(new IpAuthenticationToken(host)); } } ~~~ AbstractAuthenticationProcessingFilter這個過濾器在前面一節介紹過,是UsernamePasswordAuthenticationFilter的父類,我們的IpAuthenticationProcessingFilter也繼承了它 構造器中傳入了/ipVerify作為IP登錄的端點 attemptAuthentication()方法中加載請求的IP地址,之后交給內部的AuthenticationManager去認證 ### 5.5 IpAuthenticationProvider ~~~ public class IpAuthenticationProvider implements AuthenticationProvider { final static Map<String, SimpleGrantedAuthority> ipAuthorityMap = new ConcurrenHashMap(); //維護一個ip白名單列表,每個ip對應一定的權限 static { ipAuthorityMap.put("127.0.0.1", new SimpleGrantedAuthority("ADMIN")); ipAuthorityMap.put("10.236.69.103", new SimpleGrantedAuthority("ADMIN")); ipAuthorityMap.put("10.236.69.104", new SimpleGrantedAuthority("FRIEND")); } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { IpAuthenticationToken ipAuthenticationToken = (IpAuthenticationToken) authentication; String ip = ipAuthenticationToken.getIp(); SimpleGrantedAuthority simpleGrantedAuthority = ipAuthorityMap.get(ip); //不在白名單列表中 if (simpleGrantedAuthority == null) { return null; } else { //封裝權限信息,并且此時身份已經被認證 return new IpAuthenticationToken(ip, Arrays.asList(simpleGrantedAuthority)); } } //只支持IpAuthenticationToken該身份 @Override public boolean supports(Class<?> authentication) { return (IpAuthenticationToken.class .isAssignableFrom(authentication)); } } ~~~ return new IpAuthenticationToken(ip, Arrays.asList(simpleGrantedAuthority));使用了IpAuthenticationToken的第二個構造器,返回了一個已經經過認證的IpAuthenticationToken。 ### 5.6 配置WebSecurityConfigAdapter ~~~ @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { //ip認證者配置 @Bean IpAuthenticationProvider ipAuthenticationProvider() { return new IpAuthenticationProvider(); } //配置封裝ipAuthenticationToken的過濾器 IpAuthenticationProcessingFilter ipAuthenticationProcessingFilter(AuthenticationManager authenticationManager) { IpAuthenticationProcessingFilter ipAuthenticationProcessingFilter = new IpAuthenticationProcessingFilter(); //為過濾器添加認證器 ipAuthenticationProcessingFilter.setAuthenticationManager(authenticationManager); //重寫認證失敗時的跳轉頁面 ipAuthenticationProcessingFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/ipLogin?error")); return ipAuthenticationProcessingFilter; } //配置登錄端點 @Bean LoginUrlAuthenticationEntryPoint loginUrlAuthenticationEntryPoint(){ LoginUrlAuthenticationEntryPoint loginUrlAuthenticationEntryPoint = new LoginUrlAuthenticationEntryPoint ("/ipLogin"); return loginUrlAuthenticationEntryPoint; } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/", "/home").permitAll() .antMatchers("/ipLogin").permitAll() .anyRequest().authenticated() .and() .logout() .logoutSuccessUrl("/") .permitAll() .and() .exceptionHandling() .accessDeniedPage("/ipLogin") .authenticationEntryPoint(loginUrlAuthenticationEntryPoint()) ; //注冊IpAuthenticationProcessingFilter 注意放置的順序 這很關鍵 http.addFilterBefore(ipAuthenticationProcessingFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.authenticationProvider(ipAuthenticationProvider()); } } ~~~ WebSecurityConfigAdapter提供了我們很大的便利,不需要關注AuthenticationManager什么時候被創建,只需要使用其暴露的configure(AuthenticationManagerBuilder auth)便可以添加我們自定義的ipAuthenticationProvider。剩下的一些細節,注釋中基本都寫了出來。 ### 5.7 配置SpringMVC ~~~ @Configuration public class MvcConfig extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/home").setViewName("home"); registry.addViewController("/").setViewName("home"); registry.addViewController("/hello").setViewName("hello"); registry.addViewController("/ip").setViewName("ipHello"); registry.addViewController("/ipLogin").setViewName("ipLogin"); } } ~~~ 頁面的具體內容和表單登錄基本一致,可以在文末的源碼中查看。 ### 5.8 運行效果 #### 成功的流程 http://127.0.0.1:8080/ 訪問首頁,其中here鏈接到的地址為:http://127.0.0.1:8080/hello 點擊here,由于http://127.0.0.1:8080/hello是受保護資源,所以跳轉到了校驗IP的頁面。此時若點擊Sign In by IP按鈕,將會提交到/ipVerify端點,進行IP的認證。 登錄校驗成功之后,頁面被成功重定向到了原先訪問的 #### 失敗的流程 注意此時已經注銷了上次的登錄,并且,使用了localhost(localhost和127.0.0.1是兩個不同的IP地址,我們的內存中只有127.0.0.1的用戶,沒有localhost的用戶) 點擊here后,由于沒有認證過,依舊跳轉到登錄頁面 此時,我們發現使用localhost,并沒有認證成功,符合我們的預期 ### 5.9 總結 一個簡單的使用Spring Security來進行驗證IP地址的登錄demo就已經完成了,這個demo主要是為了更加清晰地闡釋Spring Security內部工作的原理設置的,其本身沒有實際的項目意義,認證IP其實也不應該通過Spring Security的過濾器去做,退一步也應該交給Filter去做(這個Filter不存在于Spring Security的過濾器鏈中),而真正項目中,如果真正要做黑白名單這樣的功能,一般選擇在網關層或者nginx的擴展模塊中做。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看