相关文章:

  1. OAuth2的定义和运行流程
  2. Spring Security OAuth实现Gitee快捷登录
  3. Spring Security OAuth实现GitHub快捷登录
  4. Spring Security的过滤器链机制
  5. Spring Security OAuth Client配置加载源码分析

文章目录

  • 前言
  • OAuth2AuthorizationRequestRedirectFilter
  • OAuth2LoginAuthenticationFilter
  • DefaultLoginPageGeneratingFilter
  • DefaultLogoutPageGeneratingFilter
  • RequestCacheAwareFilter
  • SecurityContextHolderAwareRequestFilter
  • AnonymousAuthenticationFilter
  • OAuth2AuthorizationCodeGrantFilter
  • SessionManagementFilter
  • ExceptionTranslationFilter
  • FilterSecurityInterceptor

前言

根据前面的示例,我们已经知道启动时会加载18个过滤器,并且已经知道了请求会匹配到DefaultSecurityFilterChain并依次通过这18个过滤器。

DisableEncodeUrlFilter
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CsrfFilter
LogoutFilter
OAuth2AuthorizationRequestRedirectFilter
OAuth2AuthorizationRequestRedirectFilter
OAuth2LoginAuthenticationFilter
DefaultLoginPageGeneratingFilter
DefaultLogoutPageGeneratingFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
OAuth2AuthorizationCodeGrantFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor

我们从OAuth2AuthorizationRequestRedirectFilter开始看,OAuth2开头这非常明显。

OAuth2AuthorizationRequestRedirectFilter

OAuth2 客户端认证核心过滤器,通过重定向到authorization_uri来获取code
该过滤器并没有doFilter()方法,只有doFilterInternal,而doFilter()存在于其父类OncePerRequestFilter过滤器中
如下是核心代码:

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {try {//从request、ClientRegistration中构造出OAuth2的授权请求对象OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request);if (authorizationRequest != null) {//重定向到authorizationUrithis.sendRedirectForAuthorization(request, response, authorizationRequest);return;}}catch (Exception ex) {this.unsuccessfulRedirectForAuthorization(request, response, ex);return;}try {//执行下一个过滤器filterChain.doFilter(request, response);}catch (IOException ex) {throw ex;}catch (Exception ex) {//判断是否有ClientAuthorizationRequiredException,如果有单独处理Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);ClientAuthorizationRequiredException authzEx = (ClientAuthorizationRequiredException) this.throwableAnalyzer.getFirstThrowableOfType(ClientAuthorizationRequiredException.class, causeChain);if (authzEx != null) {try {OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request,authzEx.getClientRegistrationId());if (authorizationRequest == null) {throw authzEx;}this.sendRedirectForAuthorization(request, response, authorizationRequest);this.requestCache.saveRequest(request, response);}catch (Exception failed) {this.unsuccessfulRedirectForAuthorization(request, response, failed);}return;}if (ex instanceof ServletException) {throw (ServletException) ex;}if (ex instanceof RuntimeException) {throw (RuntimeException) ex;}throw new RuntimeException(ex);}
}

OAuth2LoginAuthenticationFilter

说明:核心OAuth登录过滤器,首先从URL中提取code,然后使用code获取access_token,接着借助access_token获取用户信息,最终构建出OAuth2AuthenticationToken认 证对象,表明认证成功。
该过滤器会在授权服务器调用回调接口的时候起作用,本例中回调的URL为/login/oauth2/code/gitee?code=c52e1e1f8ab954d680f4bb78e33ce303b15ed3cb1040bd47089067e9a8ed9ea5&state=k2Ci-0eyt0To1Me_ThgInRX2CPv2EtKSiu5xTrWyN3Y%3D
doFilter代码位于父类AbstractAuthenticationProcessingFilter中,核心代码如下:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws IOException, ServletException {//通过requestMatcher判断request请求是否需要处理if (!requiresAuthentication(request, response)) {chain.doFilter(request, response);return;}try {//获取身份认证结果,并创建认证对象,由子类实现,OAuth2 使用的是OAuth2LoginAuthenticationFilterAuthentication authenticationResult = attemptAuthentication(request, response);if (authenticationResult == null) {// return immediately as subclass has indicated that it hasn't completedreturn;}//对会话进行处理,防止会话固定攻击(session-fixation详情可网上查询)this.sessionStrategy.onAuthentication(authenticationResult, request, response);//认证成功后,是否继续执行后面的过滤器if (this.continueChainBeforeSuccessfulAuthentication) {chain.doFilter(request, response);}//处理认证成功后的处理逻辑,委托给AuthenticationSuccessHandler,此处为SavedRequestAwareAuthenticationSuccessHandlersuccessfulAuthentication(request, response, chain, authenticationResult);}catch (InternalAuthenticationServiceException failed) {this.logger.error("An internal error occurred while trying to authenticate the user.", failed);//认证失败处理unsuccessfulAuthentication(request, response, failed);}catch (AuthenticationException ex) {// Authentication failed//认证失败处理unsuccessfulAuthentication(request, response, ex);}
}

其中attemptAuthentication处的代码量很大,作用是:获取身份认证结果,并创建认证对象,由子类实现,OAuth2 使用的是OAuth2LoginAuthenticationFilter,我们来看看这部分

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException {//从授权服务器回调的URL请求中,将请求参数转换为map格式,key=参数名,value=参数值MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());//根据code、status、error字段判断是否为返回请求if (!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) {OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());}//返回OAuth2AuthorizationRequest,该对象在OAuth2LoginAuthenticationFilter中构建//removeAuthorizationRequest官方的话是,但不太理解为什么要remove//Removes and returns the OAuth2AuthorizationRequest associated to the provided //HttpServletRequest and HttpServletResponse or if not available returns null.OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository.removeAuthorizationRequest(request, response);if (authorizationRequest == null) {OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE);throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());}String registrationId = authorizationRequest.getAttribute(OAuth2ParameterNames.REGISTRATION_ID);ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);if (clientRegistration == null) {OAuth2Error oauth2Error = new OAuth2Error(CLIENT_REGISTRATION_NOT_FOUND_ERROR_CODE,"Client Registration not found with Id: " + registrationId, null);throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());}// @formatter:offString redirectUri = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request)).replaceQuery(null).build().toUriString();// @formatter:on//构建OAuth2授权响应对象OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params,redirectUri);//从request构建一个完整的身份认证信息Object authenticationDetails = this.authenticationDetailsSource.buildDetails(request);//构建OAuth2认证TokenOAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken(clientRegistration,new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));authenticationRequest.setDetails(authenticationDetails);//请求授权服务器的token-uri,进行身份认证,返回完整的OAuth2认证Token//请求user-info-uri,获取资源所有者主体信息//会轮询所有的provider,直到遇到支持的provider,该处为OAuth2LoginAuthenticationProviderOAuth2LoginAuthenticationToken authenticationResult = (OAuth2LoginAuthenticationToken) this.getAuthenticationManager().authenticate(authenticationRequest);//转换成需要返回的OAuth2AuthenticationTokenOAuth2AuthenticationToken oauth2Authentication = this.authenticationResultConverter.convert(authenticationResult);Assert.notNull(oauth2Authentication, "authentication result cannot be null");oauth2Authentication.setDetails(authenticationDetails);//构建最终的OAuth2 授权客户端OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(authenticationResult.getClientRegistration(), oauth2Authentication.getName(),authenticationResult.getAccessToken(), authenticationResult.getRefreshToken());//保存授权相关信息,此处保存到本地this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response);return oauth2Authentication;
}

DefaultLoginPageGeneratingFilter

说明:默认的登录页面
当没有配置自定义登录页时,将使用该默认登录页
如下的doFilter核心代码:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws IOException, ServletException {//当前请求是否是failureUrl指定的地址boolean loginError = isErrorPage(request);//当前请求是否是logoutSuccessUrl指定的地址boolean logoutSuccess = isLogoutSuccess(request);//是否是登录请求,是否是重定向的错误请求,是否是登出地址if (isLoginUrlRequest(request) || loginError || logoutSuccess) {//生成一个默认的登录页String loginPageHtml = generateLoginPageHtml(request, loginError, logoutSuccess);response.setContentType("text/html;charset=UTF-8");response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);response.getWriter().write(loginPageHtml);return;}//否则执行下一个过滤器chain.doFilter(request, response);
}

DefaultLogoutPageGeneratingFilter

说明:默认的登出页面
当请求地址为GET请求,/logout时,生成一个默认的登出页面
核心代码如下:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {//地址是/logoutif (this.matcher.matches(request)) {//生成一个登出页面renderLogout(request, response);}else {if (logger.isTraceEnabled()) {logger.trace(LogMessage.format("Did not render default logout page since request did not match [%s]",this.matcher));}filterChain.doFilter(request, response);}
}

界面如下:

RequestCacheAwareFilter

说明:
从session中获取SavedRequest,如果当前请求信息和SaveRequest信息一致(一般是登录成功后重定向),则返回SavedRequestAwareWrapper的HttpServletRequest包装类

SecurityContextHolderAwareRequestFilter

说明:
通过HttpServletRequestFactory将HttpServletRequest请求包装成SecurityContextHolderAwareRequestWrapper,它实现了HttpServletRequest,并进行了扩展,添加一些额外的方法,比如:getPrincipal()方法等。这样就可以那些需要Principal等参数的Controller就可以接收到对应参数了。除了这个地方的应用,在其他地方,也可以直接调用request#getUserPrincipal()获取对应信息。

AnonymousAuthenticationFilter

说明:
匿名过滤器,如果执行到该过滤器时还没有主体,则创建一个匿名主体

OAuth2AuthorizationCodeGrantFilter

说明:
OAuth2授权码授权过滤器,跟OAuth2LoginAuthenticationFilter很像,不知道在这里的作用是什么?
是为了兜底?

SessionManagementFilter

Session管理过滤器

ExceptionTranslationFilter

处理过滤器链中抛出的AccessDeniedException和AuthenticationException 异常

FilterSecurityInterceptor

对资源的过滤处理
核心代码如下:

public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {//该请求已经运行过该过滤,跳过,执行下一个过滤器if (isApplied(filterInvocation) && this.observeOncePerRequest) {// filter already applied to this request and user wants us to observe// once-per-request handling, so don't re-do security checkingfilterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());return;}// first time this request being called, so perform security checkingif (filterInvocation.getRequest() != null && this.observeOncePerRequest) {filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);}//获取当前的权限配置InterceptorStatusToken token = super.beforeInvocation(filterInvocation);try {filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());}finally {//设置SecurityContextHolderStrategy contextsuper.finallyInvocation(token);}//最后的处理super.afterInvocation(token, null);
}

获取当前 request 对应的权限配置,首先是调用基类的 beforeInvocation 方法 。

看一下基类的 beforeInvocation 方法,从配置好的 SecurityMetadataSource 中获取当前 request 所对应的 ConfigAttribute,即权限信息。

protected InterceptorStatusToken beforeInvocation(Object object) {......Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);if (attributes == null || attributes.isEmpty()) {if (rejectPublicInvocations) {throw new IllegalArgumentException("Secure object invocation "+ object+ " was denied as public invocations are not allowed via this interceptor. "+ "This indicates a configuration error because the "+ "rejectPublicInvocations property is set to 'true'");}......}
}

这里需要注意一下 rejectPublicInvocations 属性,默认为 false。此属性含义为拒绝公共请求。如果从配置好的 SecurityMetadataSource 中获取不到当前 request 所对应的 ConfigAttribute 时,即认为当前请求为公共请求。如配置 rejectPublicInvocations 属性为 true,则系统会抛出 IllegalArgumentException 异常,即当前请求需要配置权限信息。

接下来,就要判断是否需要进行身份认证了,即调用 authenticateIfRequired 方法。

protected InterceptorStatusToken beforeInvocation(Object object) {......Authentication authenticated = authenticateIfRequired();......
}

而判断及身份认证逻辑也并不复杂,首先会判断当前用户是否已通过身份认证,如果已通过身份认证,则直接返回;如果尚未通过身份认证,则调用身份认证管理器 AuthenticationManager 进行认证,就如同登录时一样。认证通过后,同样会在当前的安全上下文中存储一份认证后的 authentication。

private Authentication authenticateIfRequired() {Authentication authentication = SecurityContextHolder.getContext().getAuthentication();if (authentication.isAuthenticated() && !alwaysReauthenticate) {if (logger.isDebugEnabled()) {logger.debug("Previously Authenticated: " + authentication);}return authentication;}authentication = authenticationManager.authenticate(authentication);// We don't authenticated.setAuthentication(true), because each provider should do// thatif (logger.isDebugEnabled()) {logger.debug("Successfully Authenticated: " + authentication);}SecurityContextHolder.getContext().setAuthentication(authentication);return authentication;
}

然后,使用获取到的 ConfigAttribute ,继续调用访问控制器 AccessDecisionManager 对当前请求进行鉴权。

protected InterceptorStatusToken beforeInvocation(Object object) {......// Attempt authorizationtry {this.accessDecisionManager.decide(authenticated, object, attributes);}catch (AccessDeniedException accessDeniedException) {publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,accessDeniedException));throw accessDeniedException;}if (debug) {logger.debug("Authorization successful");}if (publishAuthorizationSuccess) {publishEvent(new AuthorizedEvent(object, attributes, authenticated));}
}

注意,无论鉴权通过或是不通后,Spring Security 框架均使用了观察者模式,来通知其它Bean,当前请求的鉴权结果。
如果鉴权不通过,则会抛出 AccessDeniedException 异常,即访问受限,然后会被 ExceptionTranslationFilter 捕获,最终解析后调转到对应的鉴权失败页面。

如果鉴权通过,AbstractSecurityInterceptor 通常会继续请求。但是,在极少数情况下,用户可能希望使用不同的 Authentication 来替换 SecurityContext 中的 Authentication。该身份认证就会由 RunAsManager 来处理。这在某些业务场景下可能很有用,录入服务层方法需要调用远程系统并呈现不同的身份。因为 Spring Security 会自动将安全标识从一个服务器传播到另一个服务器(假设使用的是正确配置的 RMI 或 HttpInvoker 远程协议客户端),这就可能很有用。

在 AccessDecisionManager 鉴权成功后,将通过 RunAsManager 在现有 Authentication 基础上构建一个新的Authentication,如果新的 Authentication 不为空则将产生一个新的 SecurityContext,并把新产生的Authentication 存放在其中。这样在请求受保护资源时从 SecurityContext中 获取到的 Authentication 就是新产生的 Authentication。

protected InterceptorStatusToken beforeInvocation(Object object) {......// Attempt to run as a different userAuthentication runAs = this.runAsManager.buildRunAs(authenticated, object,attributes);if (runAs == null) {if (debug) {logger.debug("RunAsManager did not change Authentication object");}// no further work post-invocationreturn 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-invocationreturn new InterceptorStatusToken(origCtx, true, attributes, object);}
}

注意,AbstractSecurityInterceptor 默认持有的是 RunAsManager 的空实现 NullRunAsManager。

public abstract class AbstractSecurityInterceptor implements InitializingBean,ApplicationEventPublisherAware, MessageSourceAware {......private RunAsManager runAsManager = new NullRunAsManager();......}

待请求完成后会在 finallyInvocation() 中将原来的 SecurityContext 重新设置给SecurityContextHolder。

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());}
}

然而,无论正常调用,亦或是请求异常等,都会触发 finallyInvocation()。

public void invoke(FilterInvocation fi) throws IOException, ServletException {if ((fi.getRequest() != null)......}else {......try {fi.getChain().doFilter(fi.getRequest(), fi.getResponse());}finally {// 无论是否成功、抛异常与否,均会执行super.finallyInvocation(token);}// 正常请求结束,最后也会执行(afterInvocation 内部会调用finallyInvocation )super.afterInvocation(token, null);}
}

即便是正常执行结束,依然会执行 finallyInvocation()(afterInvocation 内部会调用finallyInvocation )。

protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {......finallyInvocation(token); // continue to clean in this method for passivity......
}

此外,Spring Security 对 RunAsManager 有一个还有一个非空实现类 RunAsManagerImpl,其构造新 Authentication 的逻辑如下:

如果受保护对象对应的 ConfigAttribute 中拥有以“RUN_AS_”开头的配置属性,则在该属性前加上“ROLE_”,然后再把它作为一个 SimpleGrantedAuthority 赋给将要创建的 Authentication(如ConfigAttribute 中拥有一个“RUN_AS_ADMIN”的属性,则将构建一个“ROLE_RUN_AS_ADMIN”的SimpleGrantedAuthority),最后再利用原 Authentication 的 principal、权限等信息构建一个新的 Authentication 并返回;如果不存在任何以“RUN_AS_”开头的 ConfigAttribute,则直接返回null。

public Authentication buildRunAs(Authentication authentication, Object object,Collection<ConfigAttribute> attributes) {List<GrantedAuthority> newAuthorities = new ArrayList<>();for (ConfigAttribute attribute : attributes) {if (this.supports(attribute)) {GrantedAuthority extraAuthority = new SimpleGrantedAuthority(getRolePrefix() + attribute.getAttribute());newAuthorities.add(extraAuthority);}}if (newAuthorities.size() == 0) {return null;}// Add existing authoritiesnewAuthorities.addAll(authentication.getAuthorities());return new RunAsUserToken(this.key, authentication.getPrincipal(),authentication.getCredentials(), newAuthorities,authentication.getClass());
}

AccessDecisionManager 是在访问受保护的对象之前判断用户是否拥有该对象的访问权限。然而,有时候我们可能会希望在请求执行完成后对返回值做一些修改或者权限校验,当然,也可以简单的通过AOP来实现这一功能。

同样的,Spring Security 提供了 AfterInvocationManager 接口,它允许我们在受保护对象访问完成后对返回值进行修改或者进行权限校验,权限校验不通过时抛出 AccessDeniedException,并使用观察者模式通知其它Bean。

protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {......if (afterInvocationManager != null) {......catch (AccessDeniedException accessDeniedException) {AuthorizationFailureEvent event = new AuthorizationFailureEvent(token.getSecureObject(), token.getAttributes(), token.getSecurityContext().getAuthentication(),accessDeniedException);publishEvent(event);throw accessDeniedException;}}......
}

其将由 AbstractSecurityInterceptor 的子类进行调用,如默认子类 FilterSecurityInterceptor 。

需要特别注意的是,AfterInvocationManager 需要在受保护对象成功被访问后才能执行。

类似于AuthenticationManager,AfterInvocationManager 同样也有一个默认的实现类AfterInvocationProviderManager,其中有一个由 AfterInvocationProvider 组成的集合属性。

public class AfterInvocationProviderManager implements AfterInvocationManager,InitializingBean {......private List<AfterInvocationProvider> providers;......
}

非常有趣的是,AfterInvocationProvider 与 AfterInvocationManager 具有相同的方法定义。此一来,在调用AfterInvocationProviderManager 中的方法时,实际上就是依次调用其中成员属性 providers 中的AfterInvocationProvider 接口对应的方法。

public Object decide(Authentication authentication, Object object,Collection<ConfigAttribute> config, Object returnedObject)throws AccessDeniedException {Object result = returnedObject;for (AfterInvocationProvider provider : providers) {result = provider.decide(authentication, object, config, result);}return result;
}

而 AfterInvocationProvider 的默认实现类 PostInvocationAdviceProvider 中的 PostInvocationAuthorizationAdvice,其默认实现类 ExpressionBasedPostInvocationAdvice,不正是对应着后置权限注解 @PostAuthorize 吗?

最后,关于 FILTER_APPLIED 常量,在 FilterSecurityInterceptor 中是这么使用的:

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 checkingfi.getChain().doFilter(fi.getRequest(), fi.getResponse());}else {// first time this request being called, so perform security checkingif (fi.getRequest() != null && observeOncePerRequest) {fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);}......}
}

其主要作用,是用于阻止请求的重复安全检查。

原理也简单,第一次执行时,检查 request 中 FILTER_APPLIED 属性值为空,则放入值;后续该 request 再次请求时,FILTER_APPLIED 属性值不为空,代表已经进行过安全检查,则该请求直接通过,不再重复进行安全检查。

Spring Security内置过滤器详解相关推荐

  1. Python内置函数详解——总结篇

      引 言 国庆期间下定决心打算学习Python,于是下载安装了开发环境.然后问题就来了,怎么开始呢?纠结一番,还是从官方帮助文档开始吧.可是全是英文啊,英语渣怎么破?那就边翻译边看边实践着做吧(顺便 ...

  2. 序列内置方法详解(string/list/tuple)

    一.常用方法集合 1.1.string,字符串常用方法 以下举例是python2.7测试: 函数名称 作用 举例 str.capitalize() 字符串第一个字符如果是字母,则把字母替换为大写字母. ...

  3. python的装饰器迭代器与生成器_python3 装饰器、列表生成器、迭代器、内置方法详解等(第四周)...

    前言: 为什么要学习python3? 原因: 1.学习一门语言能力 2.通过该语言能力完成测试自动化以及独立完成自测框架知识 那么我要做什么呢? 1.每天花十个小时完成python3的学习 要在什么地 ...

  4. python3 内置函数详解

    内置函数详解 abs(x) 返回数字的绝对值,参数可以是整数或浮点数,如果参数是复数,则返回其大小. # 如果参数是复数,则返回其大小.>>> abs(-25) 25>> ...

  5. for循环与内置方法详解

    ''' for循环与内置方法详解 ''' # 循环:重复(按照某种规律的)做一件事情# lt = [1, 2, 3, 4] # # ind = 0 # # while True: # print(lt ...

  6. python列表的内置方法_Python内置方法详解

    1. 字符串内置方法详解 为何要有字符串?相对于元组.列表等,对于唯一类型的定义,字符串具有最简单的形式. 字符串往往以变量接收,变量名.可以查看所有的字符串的内置方法,如: 1> count: ...

  7. html内置时间对象,JavaScript中的常用事件,以及内置对象详解

    原标题:JavaScript中的常用事件,以及内置对象详解 今天是刘小爱自学Java的第81天. 感谢你的观看,谢谢你. 话不多说,开始今天的学习: 学前端有一个非常权威的组织,也就是w3c,其有个专 ...

  8. python内置函数教程_Python内置函数详解

    此文参考python文档,然后结合自己的理解,写下来,一方面方便自己,让自己好好学习,顺便回忆回忆:另一方面,让喜欢的盆友也参考一下. 经查询,3.6版本总共有68个内置函数,主要分类如下: 数学运算 ...

  9. 数字内置方法详解(int/long/float/complex)

    一.常用方法 1.1.int 以下是Python2.7的int内置函数: 序号 函数名 作用 举例 1 int.bit_length() 二进制存储这个整数至少需要多少bit(位). >> ...

最新文章

  1. 参考答案:03 向量空间
  2. [Android] 底部菜单布局+PopupWindows实现弹出菜单功能(初级篇)
  3. webp 格式转 png 格式的一种便捷方式
  4. 前端学习(1313):get请求参数
  5. 微机计算机原理姚向华课后答案,微型计算机操作系统
  6. HashiCorp Vault 1.0开源自动解封特性,新增Batch令牌
  7. 微型计算机由5大部分,微机原理答案 (5)
  8. Python稳基修炼的经典案例15(计算机二级、初学者必会字符格式处理)
  9. 2d 蓝图_“二渲三”打破传统思维!Netflix冲奥动画会推动2D动画变革吗?
  10. php如何制作列表翻页,php 生成翻页链接(页码)列表的函数
  11. 百度回应“抄袭天猫精灵”;ofo 押金退完需 12 年;VS Code 1.36 发布 | 极客头条...
  12. 很WEB很2.0---ThunderBird
  13. CSS 居中 可随着浏览器变大变小而居中
  14. 文件 - 介绍 含PEM文件
  15. 佳能Canon imageCLASS MF236n 一体机驱动
  16. win11怎么进安全模式,win11进入安全模式的方法
  17. 熟悉VBA的编程环境---VBE
  18. c语言三维空间间绕坐标轴变换,浙江大学软件学院三维动画与交互技术考试概念拾掇...
  19. (最新最详细)安装ubuntu18.04
  20. 海信A5无法进入系统,无法进入recovery,无法卡刷,无法进入edl,无法进入9008

热门文章

  1. 阿里云短信验证-PHP
  2. 转载:艺用人体解剖(学习用书)(中央美术学院基础教学)
  3. 基于redis简单实现网站访问量计数
  4. 泛型编程、STL的概念、STL模板思想及其六大组件的关系,以及泛型编程(GP)、STL、面向对象编程(OOP)、C++之间的关系
  5. zblog php getlist,zblog php调用自定义文章列表函数GetList()介绍
  6. [620]使用Python实现一个按键精灵
  7. Docker---(9)Docker中容器无法停止无法删除
  8. 企业级实战——品优购电商系统开发- 67 .品牌下拉列表-静态
  9. 开展一个深度学习项目
  10. 数学建模PPT(一)