spring security 核心 -- AbstractSecurityInterceptor

spring security 兩個(gè)核心

  1. authentication 認(rèn)證
  2. authorization(access-control) 授權(quán)(或者說是訪問控制)
    認(rèn)證是聲明主體的過程纹笼,授權(quán)是指確定一個(gè)主體是否允許在你的應(yīng)用程序執(zhí)行一個(gè)動(dòng)作的過程。

spring security 核心流程

這一套核心流程具體每條的實(shí)現(xiàn)由AbstractSecurityInterceptor 實(shí)現(xiàn),也就是說AbstractSecurityInterceptor 只是定義了一些行為荒适,然后這些行為的安排算途,也就是執(zhí)行流程則是由具體的子類所實(shí)現(xiàn)稳懒,AbstractSecurityInterceptor 雖然也叫Interceptor ,但是并沒有繼承和實(shí)現(xiàn)任何和過濾器相關(guān)的類坤塞,具體和過濾器有關(guān)的部分是由子類所定義舌菜。每一種受保護(hù)對(duì)象都擁有繼承自AbstrachSecurityInterceptor的攔截器類萌壳。spring security 提供了兩個(gè)具體實(shí)現(xiàn)類,MethodSecurityInterceptor 將用于受保護(hù)的方法日月,F(xiàn)ilterSecurityInterceptor 用于受保護(hù)的web 請(qǐng)求袱瓮。

public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
        Filter {
...
}
public class MethodSecurityInterceptor extends AbstractSecurityInterceptor implements
        MethodInterceptor {
...
}
  1. 查找當(dāng)前請(qǐng)求里分配的"配置屬性"。
  2. 把安全對(duì)象爱咬,當(dāng)前的Authentication和配置屬性,提交給AccessDecisionManager來進(jìn)行以此認(rèn)證決定尺借。
  3. 有可能在調(diào)用的過程中,對(duì)Authentication進(jìn)行修改。
  4. 允許安全對(duì)象進(jìn)行處理(假設(shè)訪問被允許了)精拟。
  5. 在調(diào)用返回的時(shí)候執(zhí)行配置的AfterInvocationManager燎斩。如果調(diào)用引發(fā)異常,AfterInvocationManager將不會(huì)被調(diào)用。

AbstractSecurityInterceptor 的兩個(gè)實(shí)現(xiàn)都具有一致的邏輯

  1. 先將正在請(qǐng)求調(diào)用的受保護(hù)對(duì)象傳遞給beforeInvocation()方法進(jìn)行權(quán)限鑒定串前。
  2. 權(quán)限鑒定失敗就直接拋出異常了瘫里。
  3. 鑒定成功將嘗試調(diào)用受保護(hù)對(duì)象,調(diào)用完成后荡碾,不管是成功調(diào)用谨读,還是拋出異常,都將執(zhí)行finallyInvocation()坛吁。
  4. 如果在調(diào)用受保護(hù)對(duì)象后沒有拋出異常劳殖,則調(diào)用afterInvocation()铐尚。
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
        Filter {
    // ~ Static fields/initializers
    // =====================================================================================

    private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";

    // ~ Instance fields
    // ================================================================================================

    private FilterInvocationSecurityMetadataSource securityMetadataSource;
    private boolean observeOncePerRequest = true;

    // ~ Methods
    // ========================================================================================================
      ...
    /**
     * Method that is actually called by the filter chain. Simply delegates to the
     * {@link #invoke(FilterInvocation)} method.
     *
     * @param request the servlet request
     * @param response the servlet response
     * @param chain the filter chain
     *
     * @throws IOException if the filter chain fails
     * @throws ServletException if the filter chain fails
     */
    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 && observeOncePerRequest) {
                fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
            }

            InterceptorStatusToken token = super.beforeInvocation(fi);

            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            }
            finally {
                super.finallyInvocation(token);
            }

            super.afterInvocation(token, null);
        }
    }
...
}
public class MethodSecurityInterceptor extends AbstractSecurityInterceptor implements
        MethodInterceptor {
    // ~ Instance fields
    // ================================================================================================

    private MethodSecurityMetadataSource securityMetadataSource;
...
    /**
     * This method should be used to enforce security on a <code>MethodInvocation</code>.
     *
     * @param mi The method being invoked which requires a security decision
     *
     * @return The returned value from the method invocation (possibly modified by the
     * {@code AfterInvocationManager}).
     *
     * @throws Throwable if any error occurs
     */
    public Object invoke(MethodInvocation mi) throws Throwable {
        InterceptorStatusToken token = super.beforeInvocation(mi);

        Object result;
        try {
            result = mi.proceed();
        }
        finally {
            super.finallyInvocation(token);
        }
        return super.afterInvocation(token, result);
    }
...
}

spring security 默認(rèn)的過濾器是FilterSecurityInterceptor。spring security 的方法安全是需要配置啟用的哆姻。
<global-method-security secured-annotations="enabled" />
添加一個(gè)注解到類或者接口的方法中可以限制對(duì)相應(yīng)方法的訪問宣增。spring security 的原生注解支持定義了一套用于該方法的屬性。這些將被傳遞到AccessDecisionManager 用來做實(shí)際的決定:

public interface BankService {

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);

@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();

@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}

啟用 JSR-250 注解使用
<global-method-security jsr250-annotations="enabled" />
這些都是基于標(biāo)準(zhǔn)的矛缨,并允許應(yīng)用簡單的基于角色的約束爹脾,但是沒有Spring Security的原生注解強(qiáng)大。要使用新的基于表達(dá)式的語法箕昭,你可以使用
<global-method-security pre-post-annotations="enabled" />

public interface BankService {

@PreAuthorize("isAnonymous()")
public Account readAccount(Long id);

@PreAuthorize("isAnonymous()")
public Account[] findAccounts();

@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
}

基于表達(dá)式的注解是一個(gè)很好的選擇灵妨,如果你需要定義超過一個(gè)檢查當(dāng)前用戶列表中的角色名稱的簡單的規(guī)則。

也可以使用注解啟用
EnableGlobalMethodSecurity
我們可以在任何使用@Configuration的實(shí)例上落竹,使用@EnableGlobalMethodSecurity注解來啟用基于注解的安全性泌霍。例如下面會(huì)啟用Spring的@Secured注解。

@EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
// ...
}
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class MethodSecurityConfig {
// ...
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig {
// ...
}

AbstractSecurityInterceptor

spring security 的核心是 AbstractSecurityInterceptor這個(gè)過濾器基本上控制著spring security 的整個(gè)流程述召。
spring security 會(huì)用到一些spring framework 提供的基礎(chǔ)功能

  • spring 事件發(fā)布機(jī)制(ApplicationEventPublisher)
  • spring AOP advice 思想
  • spring messageSource 本地消息

spring security 的權(quán)限鑒定是由AccessDecisionManager 接口中的decide() 方法負(fù)責(zé)的

void decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes)  
 throws AccessDeniedException,InsufficientAuthenticationException;

authentication 就是主體對(duì)象朱转,configAttributes 是主體(是受保護(hù)對(duì)象)的配置屬性,至于第二個(gè)對(duì)象就是表示請(qǐng)求的受保護(hù)對(duì)象积暖,基本上來說MethodInvocation(使用AOP)藤为、JoinPoint(使用Aspectj) 和 FilterInvocation(web 請(qǐng)求)三種類型。

AbstractSecurityInterceptor 是一個(gè)實(shí)現(xiàn)了對(duì)受保護(hù)對(duì)象的訪問進(jìn)行攔截的抽象類呀酸。

ConfigAttribute

public interface ConfigAttribute extends Serializable {
    String getAttribute();
}

AccessDecisionManager 的decide() 方法是需要接收一個(gè)受保護(hù)對(duì)象對(duì)應(yīng)的configAttribute集合的凉蜂。一個(gè)configAttribute可能只是一個(gè)簡單的角色名稱琼梆,具體將視AccessDecisionManager的實(shí)現(xiàn)者而定性誉。

一個(gè)"配置屬性"可以看做是一個(gè)字符串,它對(duì)于AbstractSecurityInterceptor使用的類是有特殊含義的。它們由框架內(nèi)接口ConfigAttribute表示茎杂。它們可能是簡單的角色名稱或擁有更復(fù)雜的含義,這就與AccessDecisionManager實(shí)現(xiàn)的先進(jìn)程度有關(guān)了错览。AbstractSecurityInterceptor和配置在一起的 SecurityMetadataSource 用來為一個(gè)安全對(duì)象搜索屬性。通常這個(gè)屬性對(duì)用戶是不可見的煌往。配置屬性將以注解的方式設(shè)置在受保護(hù)方法上倾哺,或者作為受保護(hù)URLs的訪問屬性。例如,當(dāng)我們看到像<intercept-url pattern='/secure/**' access='ROLE_A,ROLE_B'/>命名空間中的介紹,這是說配置屬性ROLE_A和ROLE_B適用于匹配Web請(qǐng)求的特定模式刽脖。在實(shí)踐中,使用默認(rèn)的AccessDecisionManager配置, 這意味著,任何人誰擁有GrantedAuthority只要符合這兩個(gè)屬性將被允許訪問羞海。嚴(yán)格來說,它們只是依賴于AccessDecisionManager實(shí)施的屬性和解釋。使用前綴ROLE_是一個(gè)標(biāo)記,以表明這些屬性是角色,應(yīng)該由Spring Security的RoleVoter前綴被消耗掉曲管。這只是使用AccessDecisionManager的選擇基礎(chǔ)却邓。

RunAsManager

RunAsManagerImpl構(gòu)建新的Authentication的核心代碼如下所示。

    public Authentication buildRunAs(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {

        List<GrantedAuthority> newAuthorities = new ArrayList<GrantedAuthority>();

        for (ConfigAttribute attribute : attributes) {

            if (this.supports(attribute)) {

                GrantedAuthority extraAuthority = newSimpleGrantedAuthority(getRolePrefix() + attribute.getAttribute());

                newAuthorities.add(extraAuthority);

            }

        }

        if (newAuthorities.size() == 0) {

            returnnull;

        }

        // Add existing authorities

        newAuthorities.addAll(authentication.getAuthorities());

        returnnew RunAsUserToken(this.key, authentication.getPrincipal(), authentication.getCredentials(),

                newAuthorities, authentication.getClass());

    }

在某些情況下你可能會(huì)想替換保存在SecurityContext中的Authentication院水。這可以通過RunAsManager來實(shí)現(xiàn)的腊徙。在AbstractSecurityInterceptor的beforeInvocation()方法體中简十,在AccessDecisionManager鑒權(quán)成功后,將通過RunAsManager在現(xiàn)有Authentication基礎(chǔ)上構(gòu)建一個(gè)新的Authentication撬腾,如果新的Authentication不為空則將產(chǎn)生一個(gè)新的SecurityContext螟蝙,并把新產(chǎn)生的Authentication存放在其中。這樣在請(qǐng)求受保護(hù)資源時(shí)從SecurityContext中獲取到的Authentication就是新產(chǎn)生的Authentication民傻。待請(qǐng)求完成后會(huì)在finallyInvocation()中將原來的SecurityContext重新設(shè)置給SecurityContextHolder胰默。AbstractSecurityInterceptor默認(rèn)持有的是一個(gè)對(duì)RunAsManager進(jìn)行空實(shí)現(xiàn)的NullRunAsManager。此外漓踢,Spring Security對(duì)RunAsManager有一個(gè)還有一個(gè)非空實(shí)現(xiàn)類RunAsManagerImpl初坠,其在構(gòu)造新的Authentication時(shí)是這樣的邏輯:如果受保護(hù)對(duì)象對(duì)應(yīng)的ConfigAttribute中擁有以“RUN_AS_”開頭的配置屬性,則在該屬性前加上“ROLE_”彭雾,然后再把它作為一個(gè)GrantedAuthority賦給將要?jiǎng)?chuàng)建的Authentication(如ConfigAttribute中擁有一個(gè)“RUN_AS_ADMIN”的屬性碟刺,則將構(gòu)建一個(gè)“ROLE_RUN_AS_ADMIN”的GrantedAuthority),最后再利用原Authentication的principal薯酝、權(quán)限等信息構(gòu)建一個(gè)新的Authentication進(jìn)行返回半沽;如果不存在任何以“RUN_AS_”開頭的ConfigAttribute,則直接返回null吴菠。

AfterInvocationManager

按照下面安全對(duì)象執(zhí)行和返回的方式-可能意味著完全的方法調(diào)用或過濾器鏈的執(zhí)行-在AbstractSecurityInterceptor得到一個(gè)最后的機(jī)會(huì)來處理調(diào)用者填。這種狀態(tài)下AbstractSecurityInterceptor對(duì)有可能修改返回對(duì)象感興趣。你可能想讓它發(fā)生做葵,因?yàn)轵?yàn)證決定不能“關(guān)于如何在”一個(gè)安全對(duì)象調(diào)用占哟。高可插拔性,AbstractSecurityInterceptor通過控制AfterInvocationManager在實(shí)際需要的時(shí)候修改對(duì)象。這里類實(shí)際上可能替換對(duì)象酿矢,或者拋出異常榨乎,或者什么也不做。如果調(diào)用成功后瘫筐,檢查調(diào)用才會(huì)執(zhí)行蜜暑。如果出現(xiàn)異常,額外的檢查將被跳過策肝。


AbstractSecurityInterceptor 中的一些方法

  1. afterPropertiesSet()
public void afterPropertiesSet() throws Exception {
  Assert.notNull(getSecureObjectClass(),
                "Subclass must provide a non-null response to getSecureObjectClass()");
  Assert.notNull(this.messages, "A message source must be set");
Assert.notNull(this.authenticationManager, "An AuthenticationManager is required");
Assert.notNull(this.accessDecisionManager, "An AccessDecisionManager is required");
Assert.notNull(this.runAsManager, "A RunAsManager is required");
Assert.notNull(this.obtainSecurityMetadataSource(),
                "An SecurityMetadataSource is required");
   ...
}

主要是對(duì)類的屬性進(jìn)行校驗(yàn)肛捍。

  1. beforeInvocation()
protected InterceptorStatusToken beforeInvocation(Object object) {
        Assert.notNull(object, "Object was null");
        ...
                // 獲取Object 配置屬性
        Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
                .getAttributes(object);
                ...
                // authentication 必須存在
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            credentialsNotFound(messages.getMessage(
                    "AbstractSecurityInterceptor.authenticationNotFound",
                    "An Authentication object was not found in the SecurityContext"),
                    object, attributes);
        }
        Authentication authenticated = authenticateIfRequired();
        // Attempt authorization
        try {
            this.accessDecisionManager.decide(authenticated, object, attributes);
        }
        catch (AccessDeniedException accessDeniedException) {
            publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
                    accessDeniedException));
            throw accessDeniedException;
        }
                ...
        if (publishAuthorizationSuccess) {
            publishEvent(new AuthorizedEvent(object, attributes, authenticated));
        }
        // Attempt to run as a different user
        Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
                attributes);
        if (runAs == null) {
            if (debug) {
                logger.debug("RunAsManager did not change Authentication object");
            }
            // no further work post-invocation
            return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
                    attributes, object);
        }else {
            if (debug) {
                logger.debug("Switching to RunAs Authentication: " + runAs);
            }
            SecurityContext origCtx = SecurityContextHolder.getContext();
            SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
            SecurityContextHolder.getContext().setAuthentication(runAs);
            // need to revert to token.Authenticated post-invocation
            return new InterceptorStatusToken(origCtx, true, attributes, object);
        }
    }

   /**
     * Checks the current authentication token and passes it to the AuthenticationManager
     */
    private Authentication authenticateIfRequired() {
        Authentication authentication = SecurityContextHolder.getContext()
                .getAuthentication();
          // 如果authentication已經(jīng)被驗(yàn)證過了并且總是重新驗(yàn)證為false
        if (authentication.isAuthenticated() && !alwaysReauthenticate) {
            if (logger.isDebugEnabled()) {
                logger.debug("Previously Authenticated: " + authentication);
            }
              // 則跳過驗(yàn)證
            return authentication;
        }
         // 否則 authenticationManager 執(zhí)行驗(yàn)證
        authentication = authenticationManager.authenticate(authentication);

        // We don't authenticated.setAuthentication(true), because each provider should do
        // that
        SecurityContextHolder.getContext().setAuthentication(authentication);

        return authentication;
    }

這個(gè)方法實(shí)現(xiàn)了對(duì)訪問對(duì)象的權(quán)限校驗(yàn),內(nèi)部使用了AccessDecisionManager 和 AuthenticationManager

  1. finallyInvocation()
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
        if (token == null) {
            // public object
            return returnedObject;
        }

        finallyInvocation(token); // continue to clean in this method for passivity

        if (afterInvocationManager != null) {
            // Attempt after invocation handling
            try {
                returnedObject = afterInvocationManager.decide(token.getSecurityContext()
                        .getAuthentication(), token.getSecureObject(), token
                        .getAttributes(), returnedObject);
            }
            catch (AccessDeniedException accessDeniedException) {
                AuthorizationFailureEvent event = new AuthorizationFailureEvent(
                        token.getSecureObject(), token.getAttributes(), token
                                .getSecurityContext().getAuthentication(),
                        accessDeniedException);
                publishEvent(event);

                throw accessDeniedException;
            }
        }

        return returnedObject;
    }

這個(gè)方法實(shí)現(xiàn)了對(duì)返回結(jié)果的處理之众,在注入了AfterInvocationManager的情況下默認(rèn)會(huì)調(diào)用其decide()的方法

  1. finallyInvocation()
/**
     * Cleans up the work of the <tt>AbstractSecurityInterceptor</tt> after the secure
     * object invocation has been completed. This method should be invoked after the
     * secure object invocation and before afterInvocation regardless of the secure object
     * invocation returning successfully (i.e. it should be done in a finally block).
     *
     * @param token as returned by the {@link #beforeInvocation(Object)} method
     */
    protected void finallyInvocation(InterceptorStatusToken token) {
        if (token != null && token.isContextHolderRefreshRequired()) {
            if (logger.isDebugEnabled()) {
                logger.debug("Reverting to original Authentication: "
                        + token.getSecurityContext().getAuthentication());
            }

            SecurityContextHolder.setContext(token.getSecurityContext());
        }
    }

方法用于實(shí)現(xiàn)受保護(hù)對(duì)象請(qǐng)求完畢后的一些清理工作拙毫,主要是如果在beforeInvocation()中改變了SecurityContext,則在finallyInvocation()中需要將其恢復(fù)為原來的SecurityContext棺禾,該方法的調(diào)用應(yīng)當(dāng)包含在子類請(qǐng)求受保護(hù)資源時(shí)的finally語句塊中

AbstractSecurityInterceptor 和它的相關(guān)對(duì)象

security-interception.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缀蹄,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌袍患,老刑警劉巖坦康,帶你破解...
    沈念sama閱讀 222,590評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異诡延,居然都是意外死亡滞欠,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門肆良,熙熙樓的掌柜王于貴愁眉苦臉地迎上來筛璧,“玉大人,你說我怎么就攤上這事惹恃∝舶” “怎么了?”我有些...
    開封第一講書人閱讀 169,301評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵巫糙,是天一觀的道長朗儒。 經(jīng)常有香客問我,道長参淹,這世上最難降的妖魔是什么醉锄? 我笑而不...
    開封第一講書人閱讀 60,078評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮浙值,結(jié)果婚禮上恳不,老公的妹妹穿的比我還像新娘。我一直安慰自己开呐,他們只是感情好烟勋,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,082評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著筐付,像睡著了一般卵惦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上家妆,一...
    開封第一講書人閱讀 52,682評(píng)論 1 312
  • 那天鸵荠,我揣著相機(jī)與錄音,去河邊找鬼伤极。 笑死,一個(gè)胖子當(dāng)著我的面吹牛姨伤,可吹牛的內(nèi)容都是我干的哨坪。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼乍楚,長吁一口氣:“原來是場噩夢啊……” “哼当编!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起徒溪,我...
    開封第一講書人閱讀 40,098評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤忿偷,失蹤者是張志新(化名)和其女友劉穎金顿,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鲤桥,經(jīng)...
    沈念sama閱讀 46,638評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡揍拆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,701評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了茶凳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嫂拴。...
    茶點(diǎn)故事閱讀 40,852評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖贮喧,靈堂內(nèi)的尸體忽然破棺而出筒狠,到底是詐尸還是另有隱情,我是刑警寧澤箱沦,帶...
    沈念sama閱讀 36,520評(píng)論 5 351
  • 正文 年R本政府宣布辩恼,位于F島的核電站,受9級(jí)特大地震影響谓形,放射性物質(zhì)發(fā)生泄漏运挫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,181評(píng)論 3 335
  • 文/蒙蒙 一套耕、第九天 我趴在偏房一處隱蔽的房頂上張望谁帕。 院中可真熱鬧,春花似錦冯袍、人聲如沸匈挖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽儡循。三九已至,卻和暖如春征冷,著一層夾襖步出監(jiān)牢的瞬間择膝,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評(píng)論 1 274
  • 我被黑心中介騙來泰國打工检激, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留肴捉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,279評(píng)論 3 379
  • 正文 我出身青樓叔收,卻偏偏與公主長得像齿穗,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子饺律,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,851評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理窃页,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,715評(píng)論 18 139
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架脖卖,建立于...
    Hsinwong閱讀 22,442評(píng)論 1 92
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,865評(píng)論 6 342
  • 1.1 spring IoC容器和beans的簡介 Spring 框架的最核心基礎(chǔ)的功能是IoC(控制反轉(zhuǎn))容器乒省,...
    simoscode閱讀 6,723評(píng)論 2 22
  • 前言 本章內(nèi)容: ??Spring Security介紹 ??使用Servlet規(guī)范中的Filter保護(hù)Web應(yīng)用...
    Chandler_玨瑜閱讀 7,184評(píng)論 0 68