为什么80%的码农都做不了架构师?>>>   

基本流程

Spring Security认证过程的发起

(引用 http://blog.csdn.net/kaikai8552/article/details/3932370)

发起的条件:
      用户访问资源时,发生认证异常(AuthenticationException)或授权异常(AccessDeniedException),ExceptionTranslationFilter通过调用AuthenticationEntryPoint的commence方法发起认证过程。如果ExceptionTranslationFilter接收到的是授权异常,并且当前认证过的票据不是匿名票据(AnonymousAuthenticationToken),将不会发起认证过程,而是交给AccessDeniedHandler处理(一般会直接提示用户拒绝访问)。

发起过程:
      发起认证之前,Spring Security会将用户之前的请求信息保存在session里。还会清空SecurityContextHolder中的票据。AuthenticationEntryPoint将用户定向到认证的入口,收集认证信息。认证入口一般是个页面,需要用户输入用户名和密码,也有其他方式的入口。收集到认证信息之后,重新提交认证请求。认证信息会再次通过过滤器,由AuthenticationManager认证。AuthenticationEntryPoint是用户提供凭证的入口,真正的认证是由过滤器来完成。(但是DigestProcessingFilter的实现,是直接使用了一个userDetailsService,这是个特例并且这个过滤器也没有继承SpringSecurityFilter)

下面是Spring Security提供的几个AuthenticationEntryPoint的实现。

AuthenticationProcessingFilterEntryPoint
      会生成一个用于认证的表单,收集认证信息。生成的页面的URL缺省值是/j_spring_security_check,可以配置自己指定的URL。只有以这个URL结尾的请求收集上来的认证信息,才会被AuthenticationProcessingFilter用来认证。用户提交的用户名和密码之后转交AuthenticationManager处理认证。

BasicProcessingFilterEntryPoint
      将会向浏览器发送一个RFC2616规定的basic认证头信息。浏览器在识别到认证头之后,会弹出对话框,收集用户名和密码。浏览器会将收集到的用户名和密码,打包成RFC标准的认证响应,发送到服务器。在下一次请求到达BasicProcessingFilter时,过滤器会提取认证信息,并由AuthenticationManager处理认证。

DigestProcessingFilterEntryPoint
      和Basic认证类似,会向浏览器发送一个RFC2616标准的digest认证头信息。浏览器在收到认证头后,会弹出对话框,
收集用户名和密码。浏览器会将收集到的用户名和密码,打包成RFC标准的认证响应,发送到服务器。在下一次请求到达DigestProcessingFilter时,过滤器会提取认证信息,并由AuthenticationManager处理认证。

PreAuthenticatedProcessingFilterEntryPoint
      简单的向浏览器发送一个错误页面,不做任何处理。

FilterChain Filter Order

The order that filters are defined in the chain is very important. Irrespective of which filters you are actually using, the order should be as follows:

  1. ChannelProcessingFilter, because it might need to redirect to a different protocol

  2. ConcurrentSessionFilter, because it doesn't use any SecurityContextHolder functionality but needs to update the SessionRegistry to reflect ongoing requests from the principal。检查Session超时,更新最近访问信息。

  3. SecurityContextPersistenceFilter, so a SecurityContext can be set up in the SecurityContextHolder at the beginning of a web request, and any changes to the SecurityContext can be copied to the HttpSession when the web request ends (ready for use with the next web request)。从Session中加载认证信息,请求完成后,保存认证信息。

  4. Authentication processing mechanisms - UsernamePasswordAuthenticationFilter, CasAuthenticationFilter, BasicAuthenticationFilter etc - so that theSecurityContextHolder can be modified to contain a valid Authentication request token

  5. The SecurityContextHolderAwareRequestFilter, if you are using it to install a Spring Security aware HttpServletRequestWrapper into your servlet container

  6. RememberMeAuthenticationFilter, so that if no earlier authentication processing mechanism updated the SecurityContextHolder, and the request presents a cookie that enables remember-me services to take place, a suitable remembered Authentication object will be put there

  7. AnonymousAuthenticationFilter, so that if no earlier authentication processing mechanism updated the SecurityContextHolder, an anonymousAuthentication object will be put there

  8. ExceptionTranslationFilter, to catch any Spring Security exceptions so that either an HTTP error response can be returned or an appropriateAuthenticationEntryPoint can be launched

  9. FilterSecurityInterceptor, to protect web URIs and raise exceptions when access is denied

顺序定义如下:

enum SecurityFilters {FIRST (Integer.MIN_VALUE),CHANNEL_FILTER,CONCURRENT_SESSION_FILTER,SECURITY_CONTEXT_FILTER,LOGOUT_FILTER,X509_FILTER,PRE_AUTH_FILTER,CAS_FILTER,FORM_LOGIN_FILTER,OPENID_FILTER,LOGIN_PAGE_FILTER,DIGEST_AUTH_FILTER,BASIC_AUTH_FILTER,REQUEST_CACHE_FILTER,SERVLET_API_SUPPORT_FILTER,JAAS_API_SUPPORT_FILTER,REMEMBER_ME_FILTER,ANONYMOUS_FILTER,SESSION_MANAGEMENT_FILTER,EXCEPTION_TRANSLATION_FILTER,FILTER_SECURITY_INTERCEPTOR,SWITCH_USER_FILTER,LAST (Integer.MAX_VALUE);private static final int INTERVAL = 100;private final int order;private SecurityFilters() {order = ordinal() * INTERVAL;}private SecurityFilters(int order) {this.order = order;}public int getOrder() {return order;}
}

一、The Security Namespace

1、<http … />

(1) 如果声明了该元素,则会检查是否创建了FilterChainProxy,如果没有,则创建name为”springSecurityFilterChain”的FilterChainProxy Bean,并初始化FilterChainProxy.filterChains属性。

(2) 对于每个http元素,会创建SecurityFilterChain对象。

a. 并会依次使用HttpConfigurationBuilder、AuthenticationConfigBuilder创建相关的Filter,如果子元素有custom-filter,则创建自定义Filter,将所有的filter添加到SecurityFilterChain对象中。

HttpConfigurationBuilder:

创建FilterSecurityInterceptor Bean。多个<interceptor-url …/>会为每个元素会注册一个Map.Entry<RequestMatcher,SecurityConfig>。FilterSecurityInterceptor 默认的access-decision-manager-ref 为AffirmativeBased实例,包含RoleVoter,AuthenticatedVoter两个voter

b. 为SecurityFilterChain创建filterChainMatcher:

String requestMatcherRef = element.getAttribute(ATT_REQUEST_MATCHER_REF);String filterChainPattern = element.getAttribute(ATT_PATH_PATTERN);if (StringUtils.hasText(requestMatcherRef)) {if (StringUtils.hasText(filterChainPattern)) {pc.getReaderContext().error("You can't define a pattern and a request-matcher-ref for the " +"same filter chain", pc.extractSource(element));}filterChainMatcher = new RuntimeBeanReference(requestMatcherRef);} else if (StringUtils.hasText(filterChainPattern)) {filterChainMatcher = MatcherType.fromElement(element).createMatcher(filterChainPattern, null);} else {filterChainMatcher = new RootBeanDefinition(AnyRequestMatcher.class);}

(3) 默认情况下filter情况

a. ChannelProcessingFilter (CHANNEL_FILTER) ,根据intercept-url的requires-channel属性,来建立matcher与channel关联。如果有配置,则创建filter,否则不创建

b. ConcurrentSessionFilter( CONCURRENT_SESSION_FILTER),http元素配置了session-management子元素时,且session-management配置了concurrency-control子元素时创建该filter

c. SecurityContextPersistenceFilter (SECURITY_CONTEXT_FILTER),如果sessionPolicy为stateless,则使用 NullSecurityContextRepository;否则使用HttpSessionSecurityContextRepository存储认证信息。默认该filter是配置的

d. LogoutFilter (LOGOUT_FILTER),如果配置了子元素logout或者http设置了autoConfig为true,会创建该filter。当用户访问logout url(如果不配置默认为/j_spring_security_logout)时,分别调用SecurityContextLogoutHandler、rememberMeServices(http子元素配置的remember-me元素)、CookieClearingLogoutHandler(配置了delete-cookie属性)

e.J2eePreAuthenticatedProcessingFilter (PRE_AUTH_FILTER)。如果http配置了子元素jee

f. UsernamePasswordAuthenticationFilter (FORM_LOGIN_FILTER)。配置了form-login子元素或者authConfig为true时创建。默认登录处理的URL为/j_spring_security_check。如果请求的URI为/j_spring_security_check,则调用authenticationManager进行认证。

g. DefaultLoginPageGeneratingFilter (LOGIN_PAGE_FILTER),如果http配置了form-login,但form-login没有提供login-page属性,则会输出spring实现的登陆页面。

h. BasicAuthenticationFilter(BASIC_AUTH_FILTER),如果http配置了http-basic子元素或者autoConfig为true。从http header中读取认证信息,如果SecurityContextHolder已经包含认证完成的Authentication,则不需要进行认证。否则调用authenticationManager进行认证。

i. SecurityContextHolderAwareRequestFilter(SERVLET_API_FILTER)。如果配置了servlet-api-provision为false,则不会创建。否则创建该filter。该filter会使用SecurityContextHolderAwareRequestWrapper封装request。

j. RememberMeAuthenticationFilter (REMEMBER_ME_FILTER)。如果配置了dataSource,则使用PersistentTokenBasedRememberMeServices,否则使用TokenBasedRememberMeServices。PersistentTokenBasedRememberMeServices从数据库中,读取remember me的认证信息,AccountStatusUserDetailsChecker检查账户状态;调用authenticationManager进行认证,认证通过后,保存到SecurityContextHolder中。

k. AnonymousAuthenticationFilter (ANONYMOUS_FILTER)。 如果http配置了anonymous 子元素且enable为false,则不会创建该元素。如果SecurityContextHolder中没有Authentication对象,则创建AnonymousAuthenticationToken,保存到SecurityContextHolder中。

l. SessionManagementFilter (SESSION_MANAGEMENT_FILTER) 。 http元素配置了session-management子元素时。且session-management配置了concurrency-control子元素;或session-fixation-protection(默认为migrateSession)不为none;或配置了invalid-session-url;或配置了session-authentication-strategy-ref。concurrency-control使用ConcurrentSessionControlStrategy处理认证成功后的操作,除了SessionFixationProtectionStrategy的功能,另外会注册新生成的session到sessionRegister中。其他使用SessionFixationProtectionStrategy,每次创建新的session,复制数据,用于更改sessionId,这样保护sessionId被恶意使用:

/*** Called when a user is newly authenticated.* <p>* If a session already exists, and matches the session Id from the client, a new session will be created, and the* session attributes copied to it (if {@code migrateSessionAttributes} is set).* If the client's requested session Id is invalid, nothing will be done, since there is no need to change the* session Id if it doesn't match the current session.* <p>* If there is no session, no action is taken unless the {@code alwaysCreateSession} property is set, in which* case a session will be created if one doesn't already exist.*/public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {boolean hadSessionAlready = request.getSession(false) != null;if (!hadSessionAlready && !alwaysCreateSession) {// Session fixation isn't a problem if there's no sessionreturn;}// Create new session if necessaryHttpSession session = request.getSession();if (hadSessionAlready && request.isRequestedSessionIdValid()) {// We need to migrate to a new sessionString originalSessionId = session.getId();if (logger.isDebugEnabled()) {logger.debug("Invalidating session with Id '" + originalSessionId +"' " + (migrateSessionAttributes ?"and" : "without") +  " migrating attributes.");}Map<String, Object> attributesToMigrate = extractAttributes(session);session.invalidate();session = request.getSession(true); // we now have a new sessionif (logger.isDebugEnabled()) {logger.debug("Started new session: " + session.getId());}if (originalSessionId.equals(session.getId())) {logger.warn("Your servlet container did not change the session ID when a new session was created. You will" +" not be adequately protected against session-fixation attacks");}transferAttributes(attributesToMigrate, session);onSessionChange(originalSessionId, session, authentication);}}

m. ExceptionTranslationFilter (EXCEPTION_TRANSLATION_FILTER),从exception cause chain中,提取AuthenticationException或AccessDeniedException。

n. FilterSecurityInterceptor  (FILTER_INTECEPTOR_FILTER) 。最重要的filter,用户的认证就是改filter实现的。进行认证与权限检查:

Authentication authenticated = authenticateIfRequired();// Attempt authorizationtry {this.accessDecisionManager.decide(authenticated, object, attributes);}catch (AccessDeniedException accessDeniedException) {publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException));throw accessDeniedException;}

2、authentication-manager

默认会创建ProviderManager Bean,包含了配置的AuthenticationProvider的Bean实例

3、authentication-provider

默认创建DaoAuthenticationProvider bean

DaoAuthenticationProvider中,additionalAuthenticationChecks包含了对密码的校验

protected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {Object salt = null;if (this.saltSource != null) {salt = this.saltSource.getSalt(userDetails);}if (authentication.getCredentials() == null) {logger.debug("Authentication failed: no credentials provided");throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);}String presentedPassword = authentication.getCredentials().toString();if (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {logger.debug("Authentication failed: password does not match stored value");throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), userDetails);}}

二、Spring Security OAuth2

Spring Security OAuth2 包含如下标签:

registerBeanDefinitionParser("authorization-server", new AuthorizationServerBeanDefinitionParser());
registerBeanDefinitionParser("resource-server", new ResourceServerBeanDefinitionParser());
registerBeanDefinitionParser("client-details-service", new ClientDetailsServiceBeanDefinitionParser());
registerBeanDefinitionParser("client", new ClientBeanDefinitionParser());
registerBeanDefinitionParser("resource", new ResourceBeanDefinitionParser());
registerBeanDefinitionParser("rest-template", new RestTemplateBeanDefinitionParser());
registerBeanDefinitionParser("expression-handler", new ExpressionHandlerBeanDefinitionParser());
registerBeanDefinitionParser("web-expression-handler", new WebExpressionHandlerBeanDefinitionParser());

Spring Security OAuth2流程

(1) 在Spring 配置文件中,为 API资源添加http元素,该元素需要添加resourceServerFilter,同时必须设置create-session="never":

<custom-filter ref="resourceServerFilter" before="PRE_AUTH_FILTER" />

resourceServerFilter添加了OAuth2AuthenticationProcessingFilter,并使用OAuth2AuthenticationManager进行认证过程,如果resourceServerFilter不指定entry-point-ref属性,则默认使用OAuth2AuthenticationEntryPoint处理结果

OAuth2AuthenticationProcessingFilter负责从request中提取token信息,生成PreAuthenticatedAuthenticationToken,调用OAuth2AuthenticationManager.authentication进行校验。

(2) Client访问资源时,resourceServer会进行OAuth 2.0 规范中要求的认证。如果token为空,则调用接下来的filter,不会在SecurityContextHolder中添加任何Authentication。如果使用token没有查询到对应的OAuth2Authentication,则抛出InvalidTokenException;接着调用tokenService中的loadAuthentication(token),并检查resourceId是否在允许的resourceIds中。

client与user之间的授权关联是使用OAuth2Authentication表示的,其中分别包含了clientAuthentication(AuthorizationRequest)与userAuthentication(Authentication)。是通过指定的resource-server中配置的authentication-manager进行校验的。

(3) 如果没有关联的accessToken,不会生成任何Authentication对象,则在FilterSecurityInterceptor 中,则会抛出AuthenticationCredentialsNotFoundException,通过配置的entry-point-ref,返回错误信息。

(4) Client收到错误信息后,会请求/oauth/authorize,获取authorization code。获取成功后,继续请求/oauth/token获取accessToken

(5) 访问/oauth/token时,需要验证client,使用ClientCredentialsTokenEndpointFilter。在经过过滤器后,会到处理的Tokenpoint。

a.ClientCredentialsTokenEndpointFilter什么作用?

校验client_id与client_secret,并生成UsernamePasswordAuthenticationToken,并调用SecurityContextHolder.getContext().setAuthentication(authResult);保存认证结果

b. 哪儿校验grant code?

在AuthorizationCodeTokenGranter类中:

@Overrideprotected OAuth2Authentication getOAuth2Authentication(AuthorizationRequest authorizationRequest) {Map<String, String> parameters = authorizationRequest.getAuthorizationParameters();String authorizationCode = parameters.get("code");String redirectUri = parameters.get("redirect_uri");if (authorizationCode == null) {throw new OAuth2Exception("An authorization code must be supplied.");}AuthorizationRequestHolder storedAuth = authorizationCodeServices.consumeAuthorizationCode(authorizationCode);if (storedAuth == null) {throw new InvalidGrantException("Invalid authorization code: " + authorizationCode);}AuthorizationRequest pendingAuthorizationRequest = storedAuth.getAuthenticationRequest();if (pendingAuthorizationRequest.getRedirectUri() != null&& !pendingAuthorizationRequest.getRedirectUri().equals(redirectUri)) {throw new RedirectMismatchException("Redirect URI mismatch.");}String pendingClientId = pendingAuthorizationRequest.getClientId();String clientId = authorizationRequest.getClientId();if (clientId != null && !clientId.equals(pendingClientId)) {// just a sanity check.throw new InvalidClientException("Client ID mismatch");}// Secret is not required in the authorization request, so it won't be available// in the pendingAuthorizationRequest. We do want to check that a secret is provided// in the token request, but that happens elsewhere.Map<String, String> combinedParameters = new HashMap<String, String>(storedAuth.getAuthenticationRequest().getAuthorizationParameters());// Combine the parameters adding the new ones last so they override if there are any clashescombinedParameters.putAll(parameters);// Similarly scopes are not required in the token request, so we don't make a comparison here, just// enforce validity through the AuthorizationRequestFactory.DefaultAuthorizationRequest outgoingRequest = new DefaultAuthorizationRequest(pendingAuthorizationRequest);outgoingRequest.setAuthorizationParameters(combinedParameters);Authentication userAuth = storedAuth.getUserAuthentication();return new OAuth2Authentication(outgoingRequest, userAuth);}

1、authorization-server

该元素负责创建AuthorizationEndpoint与TokenEndpoint

首先设置authorizationEndpointUrl与tokenEndpointUrl,如果设置了这两个属性,则注册EndpointValidationFilter Bean,该Bean会分别将设置的两个URL重定向到AuthorizationEndpoint与TokenEndpoint。如果指定了这两个属性,则需要在web.xml中配置名称为oauth2EndpointUrlFilter的DelegatingFilterProxy Filter

AuthorizationEndpoint

该类声明如下

@FrameworkEndpoint
@SessionAttributes("authorizationRequest")
@RequestMapping(value = "/oauth/authorize")
public class AuthorizationEndpoint extends AbstractEndpoint implements InitializingBean {

服务于/oauth/authorize

默认的TokenGranter为CompositeTokenGranter以下的每个元素会为AuthorizationEndpoint添加一种类型的TokenGranter

<oauth:authorization-server client-details-service-ref="clientDetails" token-services-ref="tokenServices"user-approval-handler-ref="userApprovalHandler"><oauth:authorization-code /> <!--AuthorizationCodeTokenGranter  可以指定的属性有:disabled: Boolean value specifying whether the authorization code mechanism is disabled. This effectively disables the authorization code grant mechanism.services-ref: The reference to the bean that defines the authorization code services (instance of org.springframework.security.oauth2.provider.code.AuthorizationCodeServices)user-approval-page: The URL of the page that handles the user approval form.approval-parameter-name: The name of the form parameter that is used to indicate user approval of the client authentication request.--><oauth:implicit /><oauth:refresh-token /><oauth:client-credentials /><oauth:password /></oauth:authorization-server>

2、resource-server

定义OAuth2AuthenticationProcessingFilter,使用指定的authentication-manager进行校验。可以指定resource-id属性,用来标识资源ID,对资源进行分类。在用户授权时,会记录资源ID,标识此次授权可以访问那些资源。

----------------------------------------------------------------

学习部分:

1、ListBeanFactory

使用BeanFactory机制,把bean作为List注入其他Bean

BeanDefinition listFactoryBean = new RootBeanDefinition(ListFactoryBean.class);listFactoryBean.getPropertyValues().add("sourceList", new ManagedList());pc.registerBeanComponent(new BeanComponentDefinition(listFactoryBean, BeanIds.FILTER_CHAINS));

2、HandlerMapping

在DispatcherServlet中,initHandlerMappings会读取applicationContext中定义的所有的HandlerMapping及其子类型的Bean,注册进来,从而可以根据request获得HandlerMapping。这样就可以任意扩展HandlerMapping,OAuth机制就扩展了HandlerMapping (FrameworkEndpointHandlerMapping),通过@FrameworkEndpoint来标识Bean为Handler

转载于:https://my.oschina.net/guoxf1/blog/73121

Spring Web Application Security相关推荐

  1. Understanding Spring Web Application Architecture: The Classic Way--转载

    原文地址:http://www.petrikainulainen.net/software-development/design/understanding-spring-web-applicatio ...

  2. Web Application Security 网络应用程序安全 - (二)2010年网络安全威胁排行榜TOP 10...

    貌似距离我的上一篇关于Web Application Security的文章(Web Application Security 网络应用程序安全 - (一)启航)已经过了很久很久了,这段时间主要都在忙 ...

  3. 2020-4-24 Open Web Application Security Project (OWASP)

    英文 中文 The Open Web Application Security Project (OWASP) is a nonprofit foundation that works to impr ...

  4. Spring Web(第一部分)

    1. Spring Web MVC Spring Web MVC是在Servlet API上构建的原始Web框架,从一开始就包含在Spring框架中.其正式名称"Spring Web MVC ...

  5. Spring系列之Spring Web MVC-20

    目录 Spring Web MVC DispatcherServlet 上下文层次结构 特殊Bean Web MVC 配置 程序配置 工作原理 异常 视图解析 配置 重定向 转发 内容协商 过滤器 F ...

  6. ModSecurity web application firewall (WAF) Research - .Little Hann

    转载地址:http://bluereader.org/article/97681813 catalog 引言 OWASP ModSecurity Core Rule Set (CRS) Project ...

  7. ModSecurity web application firewall (WAF) Research

    catalog 0. 引言1. OWASP ModSecurity Core Rule Set (CRS) Project2. Installation mod_security forApache3 ...

  8. 【文献翻译】Web应用防火墙:网络安全模型和配置​​​​​​​-Web Application Firewall: Network Security Models and Configuration

    Web应用防火墙:网络安全模型和配置 Web Application Firewall: Network Security Models and Configuration 摘要 部署Web应用程序防 ...

  9. 8.1.4 Authentication in a Web Application

    8.1.4 Authentication in a Web Application Now let's explore the situation where you are using Spring ...

最新文章

  1. JavaScript(六)函数
  2. phpstorm中的快捷键
  3. Win 8 自定义设置面版
  4. github 使用总结-----转
  5. Java集合细节(三):subList的缺陷
  6. zookeeper的名词复盘-集群角色
  7. python-ConfigParser模块【读写配置文件】
  8. 智能外呼系统相关资料总结
  9. Python字符串常用函数使用详解(内附详细案例)
  10. jfinal项目tomcat下部署
  11. 0编译器详解_C++ typeid关键字详解
  12. 全球130多个国家的货币代码对照表
  13. 你所不知道的我国交通工程早期经历了怎样的发展?
  14. 可以放游戏网站云服务器,游戏网站选择哪个云服务器好?游戏服务器配置方案?...
  15. Pr播放视频没声音,音频硬件显示不工作怎么办?
  16. Arduino使用RFID模块来储存卡信息实现智能门锁(MF RC522)
  17. dnSpy反编译工具调试netcore项目
  18. 使用SpEL表达式来获取SpringData Jpa在更新数据时传递的对象参数的属性
  19. 微信 for Mac 3.0.0.1来袭 可以在电脑上刷朋友圈,附下载地址
  20. 大数据需要学习哪些知识

热门文章

  1. Postgres 9.5的特性及未来发展方向
  2. SmartCode Release 4
  3. netbean创建jsf项目
  4. 怎么修改存储路径_修改桌面文件默认存储位置的正确方式及注意事项
  5. 仿苹果手机闹钟_你会用苹果手机吗?这些快捷指令快速设置一下,好用度提升10倍...
  6. JMeter 阶梯式加压测试插件 Concurrency Thread Group
  7. Jzoj5234 外星人的路径
  8. 应用程序下载地址汇总
  9. 《JS权威指南学习总结--第八章 函数》
  10. 读取cc2530节点的设备类型、协调器、路由器、终端。