#二、核心過濾器及授權原理

一崔赌、springSecurity配置信息詳解

參考:http://www.spring4all.com/article/446

二健芭、springSecurity過濾器詳解

  • 我們已經知道Spring Security使用了springSecurityFillterChian作為了安全過濾的入口慈迈,接下來主要分析一下這個過濾器鏈都包含了哪些關鍵的過濾器痒留,并且各自的使命是什么伸头。
  • springSecurity中過濾鏈及每一個過濾器的作用(按順序)熊锭,同時加粗的部分代表一些核心過濾器碗殷,需要重點了解。

1仿粹、SecurityContextPersistenceFilter :兩個主要職責:請求來臨時吭历,創(chuàng)建SecurityContext安全上下文信息晌区,請求結束時清空SecurityContextHolder朗若。
2、HeaderWriterFilter (文檔中并未介紹遣总,非核心過濾器) 用來給http響應添加一些Header,比如X-Frame-Options, X-XSS-Protection*彤避,X-Content-Type-Options.
3董饰、CsrfFilter 在spring4這個版本中被默認開啟的一個過濾器卒暂,用于防止csrf攻擊也祠,前后端使用json交互需要注意的一個問題诈嘿。
4、LogoutFilter 顧名思義削葱,處理注銷的過濾器
5奖亚、UsernamePasswordAuthenticationFilter 這個會重點分析,表單提交了username和password析砸,被封裝成token進行一系列的認證昔字,便是主要通過這個過濾器完成的,在表單認證的方法中首繁,這是最最關鍵的過濾器作郭。
6、RequestCacheAwareFilter (文檔中并未介紹,非核心過濾器) 內部維護了一個RequestCache,用于緩存request請求
7状土、SecurityContextHolderAwareRequestFilter 此過濾器對ServletRequest進行了一次包裝累驮,使得request具有更加豐富的API
8、AnonymousAuthenticationFilter 匿名身份過濾器,這個過濾器個人認為很重要,需要將它與UsernamePasswordAuthenticationFilter 放在一起比較理解,spring security為了兼容未登錄的訪問,也走了一套認證流程,只不過是一個匿名的身份辈赋。
9篷就、SessionManagementFilter 和session相關的過濾器未辆,內部維護了一個SessionAuthenticationStrategy攘残,兩者組合使用病曾,常用來防止session-fixation protection attack立叛,以及限制同一用戶開啟多個會話的數量
10赁还、ExceptionTranslationFilter 直譯成異常翻譯過濾器朋蔫,還是比較形象的青扔,這個過濾器本身不處理異常黎茎,而是將認證過程中出現的異常交給內部維護的一些類去處理,具體是那些類下面詳細介紹
11、FilterSecurityInterceptor 這個過濾器決定了訪問特定路徑應該具備的權限屏积,訪問的用戶的角色独榴,權限是什么症歇?訪問的路徑需要什么樣的角色和權限苍息?這些判斷和處理都是由該類進行的爆办。

1跨算、SecurityContextPersistenceFilter

  • 用戶在登錄過一次之后,后續(xù)的訪問便是通過sessionId來識別坏瘩,從而認為用戶已經被認證提陶。具體在何處存放用戶信息铅忿,便是在SecurityContextHolder中荧琼,認證相關的信息是如何被存放到其中的被盈,便是通過SecurityContextPersistenceFilter
  • 在Spring Security中汞扎,雖然安全上下文信息被存儲于Session中鲫构,但我們在實際使用中不應該直接操作Session塞耕,而應當使用SecurityContextHolder。
  • 源碼分析:
public class SecurityContextPersistenceFilter extends GenericFilterBean {

   static final String FILTER_APPLIED = "__spring_security_scpf_applied";
   //安全上下文存儲的倉庫
   private SecurityContextRepository repo;

   public SecurityContextPersistenceFilter() {
      //HttpSessionSecurityContextRepository是SecurityContextRepository接口的一個實現類
      //使用HttpSession來存儲SecurityContext
      this(new HttpSessionSecurityContextRepository());
   }

   public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        if (request.getAttribute("__spring_security_scpf_applied") != null) {
// ensure that filter is only applied once per request
            chain.doFilter(request, response);
        } else {
            boolean debug = this.logger.isDebugEnabled();
            request.setAttribute("__spring_security_scpf_applied", Boolean.TRUE);
            if (this.forceEagerSessionCreation) {
                HttpSession session = request.getSession();
                if (debug && session.isNew()) {
                    this.logger.debug("Eagerly created session: " + session.getId());
                }
            }
//包裝request溺欧,response
            HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
 //從Session中獲取安全上下文信息
            SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
            boolean var13 = false;

            try {
                var13 = true;
//請求開始,設置安全上下文信息控汉,這樣就避免了用戶直接從Session中獲取安全上下文信息                SecurityContextHolder.setContext(contextBeforeChainExecution);
                chain.doFilter(holder.getRequest(), holder.getResponse());
                var13 = false;
            } finally {
//過程操作失敗森逮,清空session
                if (var13) {
                    SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
                    SecurityContextHolder.clearContext();
                    this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
                    request.removeAttribute("__spring_security_scpf_applied");
                    if (debug) {
                        this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
                    }

                }
            }
//請求結束后,清空安全上下文信息
            SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
            SecurityContextHolder.clearContext();
            this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
            request.removeAttribute("__spring_security_scpf_applied");
            if (debug) {
                this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
            }

        }
    }

  • 上面可以知道,過濾器一般負責核心的處理流程,而具體的業(yè)務實現乎折,通常交給其中聚合的其他實體類。存儲安全上下文和讀取安全上下文的工作完全委托給了HttpSessionSecurityContextRepository處理侄榴,SecurityContextPersistenceFilter和HttpSessionSecurityContextRepository配合使用饲齐,構成了Spring Security整個調用鏈路的入口福青,為什么將它放在最開始的地方也是顯而易見的,后續(xù)的過濾器中大概率會依賴Session信息和安全上下文信息。

2瞒窒、UsernamePasswordAuthenticationFilter

  • 表單認證是最常用的一個認證方式匿辩,一個最直觀的業(yè)務場景便是允許用戶在表單中輸入用戶名和密碼進行登錄然走,而這背后的UsernamePasswordAuthenticationFilter,在整個Spring Security的認證體系中則扮演著至關重要的角色。UsernamePasswordAuthenticationFilter主要肩負起了調用身份認證器噪珊,校驗身份的作用选酗,具體可參照下面的時序圖:


    UsernamePasswordAuthenticationFilter認證時序圖
  • 源碼分析:
 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
// //獲取表單中的用戶名和密碼
            String username = this.obtainUsername(request);
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }

            username = username.trim();
// //組裝成username+password形式的token
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
//Allow subclasses to set the "details" property
            this.setDetails(request, authRequest);
//交給內部的AuthenticationManager去認證栓拜,并返回認證信息
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

UsernamePasswordAuthenticationFilter本身的代碼只包含了上述這么一個方法饲漾,非常簡略,而在其父類AbstractAuthenticationProcessingFilter中包含了大量的細節(jié)缕溉,值得我們分析,整個流程理解起來也并不難考传,主要就是內部調用了authenticationManager完成認證,根據認證結果執(zhí)行successfulAuthentication或者unsuccessfulAuthentication证鸥,無論成功失敗僚楞,一般的實現都是轉發(fā)或者重定向等處理.

public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
      implements ApplicationEventPublisherAware, MessageSourceAware {
    //包含了一個身份認證器
    private AuthenticationManager authenticationManager;
    //用于實現remeberMe
    private RememberMeServices rememberMeServices = new NullRememberMeServices();
    private RequestMatcher requiresAuthenticationRequestMatcher;
    //這兩個Handler很關鍵,分別代表了認證成功和失敗相應的處理器
    private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
    private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
...
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        ...
        Authentication authResult;
        try {
            //此處實際上就是調用UsernamePasswordAuthenticationFilter的attemptAuthentication方法
            authResult = attemptAuthentication(request, response);
            if (authResult == null) {
                //子類未完成認證枉层,立刻返回
                return;
            }
//認證成功后泉褐,處理一些與session相關的方法 
            sessionStrategy.onAuthentication(authResult, request, response);
        }
        //在認證過程中可以直接拋出異常,在過濾器中鸟蜡,就像此處一樣膜赃,進行捕獲
        catch (InternalAuthenticationServiceException failed) {
            //內部服務異常
            unsuccessfulAuthentication(request, response, failed);
            return;
        }
        catch (AuthenticationException failed) {
            //認證失敗
            unsuccessfulAuthentication(request, response, failed);
            return;
        }
        //認證成功
        if (continueChainBeforeSuccessfulAuthentication) {
            chain.doFilter(request, response);
        }
        //注意,認證成功后過濾器把authResult結果也傳遞給了成功處理器
        successfulAuthentication(request, response, chain, authResult);
    }
}
  • 總結整個過程的四個步驟

1.判斷filter是否可以處理當前的請求揉忘,如果不可以則放行交給下一個filter
2.調用抽象方法attemptAuthentication進行驗證跳座,該方法由子類UsernamePasswordAuthenticationFilter實現
3.認證成功以后,回調一些與 session 相關的方法泣矛;
4.認證成功以后疲眷,認證成功后的相關回調方法;認證成功以后您朽,認證成功后的相關回調方法狂丝;

protected void successfulAuthentication(HttpServletRequest request,
         HttpServletResponse response, FilterChain chain, Authentication authResult)
         throws IOException, ServletException {

     if (logger.isDebugEnabled()) {
         logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
                 + authResult);
     }

     SecurityContextHolder.getContext().setAuthentication(authResult);

     rememberMeServices.loginSuccess(request, response, authResult);

     // Fire event
     if (this.eventPublisher != null) {
         eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                 authResult, this.getClass()));
     }

     successHandler.onAuthenticationSuccess(request, response, authResult);
 }

3、AnonymousAuthenticationFilter

  • 匿名認證過濾器哗总,可能有人會想:匿名了還有身份美侦?我自己對于Anonymous匿名身份的理解是Spirng Security為了整體邏輯的統一性,即使是未通過認證的用戶魂奥,也給予了一個匿名身份菠剩。而AnonymousAuthenticationFilter該過濾器的位置也是非常的科學的,它位于常用的身份認證過濾器(如UsernamePasswordAuthenticationFilter耻煤、BasicAuthenticationFilter具壮、RememberMeAuthenticationFilter)之后,意味著只有在上述身份過濾器執(zhí)行完畢后哈蝇,SecurityContext依舊沒有用戶信息棺妓,AnonymousAuthenticationFilter該過濾器才會有意義——基于用戶一個匿名身份挑庶。
public class AnonymousAuthenticationFilter extends GenericFilterBean implements
      InitializingBean {

   private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
   private String key;
   private Object principal;
   private List<GrantedAuthority> authorities;


   //自動創(chuàng)建一個"anonymousUser"的匿名用戶,其具有ANONYMOUS角色
   public AnonymousAuthenticationFilter(String key) {
      this(key, "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
   }

   /**
    *
    * @param key key用來識別該過濾器創(chuàng)建的身份
    * @param principal principal代表匿名用戶的身份
    * @param authorities authorities代表匿名用戶的權限集合
    */
   public AnonymousAuthenticationFilter(String key, Object principal,
         List<GrantedAuthority> authorities) {
      Assert.hasLength(key, "key cannot be null or empty");
      Assert.notNull(principal, "Anonymous authentication principal must be set");
      Assert.notNull(authorities, "Anonymous authorities must be set");
      this.key = key;
      this.principal = principal;
      this.authorities = authorities;
   }

   ...

   public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
         throws IOException, ServletException {
      //過濾器鏈都執(zhí)行到匿名認證過濾器這兒了還沒有身份信息掀抹,塞一個匿名身份進去
      if (SecurityContextHolder.getContext().getAuthentication() == null) {
         SecurityContextHolder.getContext().setAuthentication(
               createAuthentication((HttpServletRequest) req));
      }
      chain.doFilter(req, res);
   }

   protected Authentication createAuthentication(HttpServletRequest request) {
     //創(chuàng)建一個AnonymousAuthenticationToken
      AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,
            principal, authorities);
      auth.setDetails(authenticationDetailsSource.buildDetails(request));

      return auth;
   }
   ...
}

其實對比AnonymousAuthenticationFilter和UsernamePasswordAuthenticationFilter就可以發(fā)現一些門道了,UsernamePasswordAuthenticationToken對應AnonymousAuthenticationToken锥腻,他們都是Authentication的實現類,而Authentication則是被SecurityContextHolder(SecurityContext)持有的性芬,一切都被串聯在了一起峡眶。

4、ExceptionTranslationFilter

  • ExceptionTranslationFilter異常轉換過濾器位于整個springSecurityFilterChain的后方植锉,用來轉換整個鏈路中出現的異常辫樱,將其轉化,顧名思義俊庇,轉化以意味本身并不處理狮暑。一般其只處理兩大類異常:AccessDeniedException訪問異常和AuthenticationException認證異常
  • 這個過濾器非常重要,因為它將Java中的異常和HTTP的響應連接在了一起辉饱,這樣在處理異常時搬男,我們不用考慮密碼錯誤該跳到什么頁面,賬號鎖定該如何彭沼,只需要關注自己的業(yè)務邏輯缔逛,拋出相應的異常便可。如果該過濾器檢測到AuthenticationException溜腐,則將會交給內部的AuthenticationEntryPoint去處理,如果檢測到AccessDeniedException瓜喇,需要先判斷當前用戶是不是匿名用戶挺益,如果是匿名訪問,則和前面一樣運行AuthenticationEntryPoint乘寒,否則會委托給AccessDeniedHandler去處理望众,而AccessDeniedHandler的默認實現,是AccessDeniedHandlerImpl伞辛。所以ExceptionTranslationFilter內部的AuthenticationEntryPoint是至關重要的烂翰,顧名思義:認證的入口點。

5蚤氏、FilterSecurityInterceptor

  • 由什么控制哪些資源是受限的甘耿,這些受限的資源需要什么權限,需要什么角色…這一切和訪問控制相關的操作竿滨,都是由FilterSecurityInterceptor完成的佳恬。也就是說FilterSecurityInterceptor和鑒權有關。
  • FilterSecurityInterceptor的工作流程

FilterSecurityInterceptor從SecurityContextHolder中獲取Authentication對象于游,然后比對用戶擁有的權限和資源所需的權限毁葱。前者可以通過Authentication對象直接獲得,而后者則需要引入我們之前一直未提到過的兩個類:SecurityMetadataSource贰剥,AccessDecisionManager倾剿。

  • 源碼:
public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        invoke(fi);
    }

public void invoke(FilterInvocation fi) throws IOException, ServletException {
        if ((fi.getRequest() != null)
                && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
                && observeOncePerRequest) {
            // filter already applied to this request and user wants us to observe
            // once-per-request handling, so don't re-do security checking
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        }
        else {
            // first time this request being called, so perform security checking
            if (fi.getRequest() != null) {
                fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
            }
            #1. before invocation重要
            InterceptorStatusToken token = super.beforeInvocation(fi);

            try {
                #2. 可以理解開始請求真正的 /persons 服務
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            }
            finally {
                super.finallyInvocation(token);
            }
            #3. after Invocation
            super.afterInvocation(token, null);
        }
    }
  • 總結整個過程為三個步驟

1、before invocation重要
2蚌成、請求真正的 /persons 服務
3前痘、after Invocation

(1)before invocation: AccessDecisionManager

protected InterceptorStatusToken beforeInvocation(Object object) {
        ...

        Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
                .getAttributes(object);

        ...
        Authentication authenticated = authenticateIfRequired();

        // Attempt authorization
        try {
            #1.重點
            this.accessDecisionManager.decide(authenticated, object, attributes);
        }
        catch (AccessDeniedException accessDeniedException) {
            publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,accessDeniedException));

            throw accessDeniedException;
        }
        ...
    }

attributes和object 是什么凛捏?發(fā)現object為當前請求的 url:/persons, 那么getAttributes方法就是使用當前的訪問資源路徑去匹配我們自己定義的匹配規(guī)則


image.png

三、授權

Spring Security默認使用AffirmativeBased實現 AccessDecisionManager 的 decide 方法來實現授權际度。

public void decide(Authentication authentication, Object object,
            Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
        int deny = 0;
        #1.調用AccessDecisionVoter 進行vote(投票)
        for (AccessDecisionVoter voter : getDecisionVoters()) {
            int result = voter.vote(authentication, object, configAttributes);

            if (logger.isDebugEnabled()) {
                logger.debug("Voter: " + voter + ", returned: " + result);
            }

            switch (result) {
            #1.1只要有voter投票為ACCESS_GRANTED葵袭,則通過 直接返回
            case AccessDecisionVoter.ACCESS_GRANTED://1
                return;
            @#1.2只要有voter投票為ACCESS_DENIED,則記錄一下
            case AccessDecisionVoter.ACCESS_DENIED://-1
                deny++;

                break;

            default:
                break;
            }
        }

        if (deny > 0) {
        #2.如果有兩個及以上AccessDecisionVoter(姑且稱之為投票者吧)都投ACCESS_DENIED乖菱,則直接就不通過了
            throw new AccessDeniedException(messages.getMessage(
                    "AbstractAccessDecisionManager.accessDenied", "Access is denied"));
        }

        // To get this far, every AccessDecisionVoter abstained
        checkAllowIfAllAbstainDecisions();
    }
  • 總結整個過程

1坡锡、調用AccessDecisionVoter 進行vote(投票)
2、只要有投通過(ACCESS_GRANTED)票窒所,則直接判為通過鹉勒。
3、如果沒有投通過則 deny++ ,最后判斷if(deny>0 拋出AccessDeniedException(未授權)

認證授權整個流程

參考:
http://www.spring4all.com/article/447
https://github.com/qiuzhangwei/logback

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末吵取,一起剝皮案震驚了整個濱河市禽额,隨后出現的幾起案子,更是在濱河造成了極大的恐慌皮官,老刑警劉巖脯倒,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異捺氢,居然都是意外死亡藻丢,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門摄乒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悠反,“玉大人,你說我怎么就攤上這事馍佑≌瘢” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵拭荤,是天一觀的道長茵臭。 經常有香客問我,道長舅世,這世上最難降的妖魔是什么笼恰? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮歇终,結果婚禮上社证,老公的妹妹穿的比我還像新娘。我一直安慰自己评凝,他們只是感情好追葡,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般宜肉。 火紅的嫁衣襯著肌膚如雪匀钧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天谬返,我揣著相機與錄音之斯,去河邊找鬼。 笑死遣铝,一個胖子當著我的面吹牛佑刷,可吹牛的內容都是我干的。 我是一名探鬼主播酿炸,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼瘫絮,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了填硕?” 一聲冷哼從身側響起麦萤,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎扁眯,沒想到半個月后壮莹,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡姻檀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年命满,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片施敢。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡周荐,死狀恐怖狭莱,靈堂內的尸體忽然破棺而出僵娃,到底是詐尸還是另有隱情,我是刑警寧澤腋妙,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布默怨,位于F島的核電站,受9級特大地震影響骤素,放射性物質發(fā)生泄漏匙睹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一济竹、第九天 我趴在偏房一處隱蔽的房頂上張望痕檬。 院中可真熱鬧,春花似錦送浊、人聲如沸梦谜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽唁桩。三九已至闭树,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間荒澡,已是汗流浹背报辱。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留单山,地道東北人碍现。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像饥侵,于是被迫代替她去往敵國和親鸵赫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理躏升,服務發(fā)現辩棒,斷路器,智...
    卡卡羅2017閱讀 134,652評論 18 139
  • 參考文章 spring security 極客學院spring security 博客園Spring securi...
    spilledyear閱讀 2,408評論 0 7
  • 前面的部分膨疏,我們關注了Spring Security是如何完成認證工作的一睁,但是另外一部分核心的內容:過濾器,一直沒...
    郭藝賓閱讀 552評論 0 3
  • 惡毒女配洗白記1:我是惡毒女配佃却? “寧馨兒真是惡心啊” “就是者吁,嫉妒蘇婉家境不她好就推她下水” ...
    kair星閱讀 1,520評論 1 2
  • 我的寒假從2018年1月1日開始复凳,也是從那天起,我的生活變得異常煩躁灶泵。 我把工作日鬧鐘都關掉了育八,但依舊...
    我不想做混世大魔王了閱讀 322評論 2 0