Spring Security OAuth 2 开发指南

Stella981
• 阅读 1153

Spring Security OAuth 2 开发指南

本文档翻译自http://projects.spring.io/spring-security-oauth/docs/oauth2.html

简介

这是为支持 OAuth 2.0 的用户指南,OAuth1.0很多都不一样,请访问 OAuth1.0指南

该指南分成了两部分,第一部分是OAuth2.0 Provider,第二部分是OAuth2.0 Client。integration testssample apps 是Provider和Client很好的示例源代码。

OAuth 2.0 Provider

OAuth 2.0 Provider机制主要负责暴露OAuth2.0保护的资源。配置包括建立OAuth2.0 Client用来独立的或者代表用户访问受保护的资源。Provider通过管理和验证OAuth2.0 Token来访问受保护的资源。编程时,Provider必须提供一个接口让用户确认Client可以被授权访问他个人的资源(一个授权确认页面)。

OAuth 2.0 Provider 实现

Provider在OAuth2.0中的角色实际上分为授权服务和资源服务,然而很多时候这两个服务在同一个应用中,使用Spring OAuth你也可以将他们分为两个应用,也可以创建多个资源服务并使用同一个授权服务。对Token的请求被Spring MVC控制器端点处理,访问受保护的资源通过标准的Spring Security请求过滤器处理。为了实现OAuth2.0授权服务器,下面的端点在Spring Security过滤器链中是必需的:

要实现OAuth2.0资源服务器,下面的过滤器是必需的:

对于所有的OAuth2.0 Provider特性,可以使用Spring OAuth @Configuration adapters简便的配置。同时也有OAuth的XML命名空间,schema位于http://www.springframework.org/schema/security/spring-security-oauth2.xsd

授权服务器配置

在配置授权服务器的时候,你必须要考虑Client用来从用户那儿获取access token的授权类型(还有authrization code,user credentials,refresh token)。服务器的配置提供了client details service和token service的实现,全局性的启用或禁用机制的某些方面。要注意,每个Client可以专门配置权限能够使用某些授权机制和访问的授权类型。相当于,你的Provider只配置了支持"client credentials"的授权类型,并不意味着一个特定的Client可以授权使用该授权类型。

@EnableAuthorizationServer 注解用来配置OAuth2.0 授权服务器机制,和任意个 @Beans 实现 AuthorizationServerConfigurer (有一些方便的adapter实现类,里面都是空方法)。下面的特性都是委托给各个由Spring创建并注入进 AuthorizationServerConfigurer 的配置器:

  • ClientDetailsServiceConfigurer 一个定义client details service的配置器,client details 可以被初始化或引用一个已经存在的store
  • AuthorizationServerSecurityConfigurer 定义了token端点的安全限制
  • AuthorizationServerEndpointsConfigurer 定义了authorization和token端点,token service

Provider配置的一个重要方面是,authorization code提供给OAuth Client的方式(在authorization code grant中)。authorization code是由OAuth Client重写向用户到授权页面来获得的,在这个页面中,用户可以输入他的用户名密码,并带着authorization code从Provider授权服务器重定向回OAuth Client。这样的示例阐述的OAuth2.0 规范。

在XML中的<authorization-server/>元素以类似的方式来配置OAuth2.0 授权服务器

配置Client Details

ClientDetailsServiceConfigurer(这是你的AuthorizationServerConfigurer的回调)用来定义基于内存或者JDBC的client details service实现,一个client重要的属性有:

  • clientId (必需)客户端id
  • secret (使用信任的客户端时是必需的)客户端密钥,如果有的话
  • scope 客户端被限制的范围,如果scope未定义或为空(默认),客户端不被scope限制
  • authorizedGrantTypes 授权类型是被授权给客户端来使用的,默认是空
  • authroities 授予给客户端的权限(常规的Spring Security权限)

client details 可以在运行的应用中被更新,通过直接访问底层的store(比如直接访问JdbcClientDetailsService中的数据表)或者通过ClientDetailsManager接口(它也同时实现了ClientDetailsService

注意:JDBC service的schema没有包含在当前库中(因为在练习中有太多你可能想使用的东西了),但这里有一个示例可以帮你开始 test code in github

管理Token

AuthorizationServerTokenServices 接口定义了管理OAuth2.0 token必需的操作,如下:

  • 当access token被创建时,认证信息必须被存储了,这样后续的资源服务器接收到的access token才能引用到。
  • access token被用来加载认证信息(用来授权access token的创建)

当创建自定义的 AuthorizationServerTokenServices 实现时,你可以考虑使用 DefaultTokenServices 它包含很多可嵌入的策略来改变access token的格式和存储。默认的,通过随机数创建token并把处理token持久化的一切委托给了 TokenStore 。默认的store是 in-memory implementation ,但还有其他一些实现。这里是一些对他们的讨论:

  • 默认的InMemoryTokenStore 对于独立的server(低访问量且失败时不需要热切换到备份服务器)表现的很完美了,大多数工程以这种形式开始,也可以在开发模式下使用,以简单无依赖的方式启动server。
  • JdbcTokenStore 是相关的JDBC 版本,它把token数据存储到关系型数据库中。如果你可以在多个server间共享数据库,或者你想扩展单一server的实例,或者认证和资源服务器有多个组件,就使用JDBC版本。要使用JdbcTokenStore 你必需在classpath中加入"spring-jdbc"
  • sotre中的 JSON Web Token (JWT) version 会编码所有关于授权的数据到token中(那么完全不需要后台数据存储,这是一个显著的优势)。一个缺点是你不能轻易的废除token,所以他们经常被授权较短的过期时间,废除是通过refresh token处理的。另一个缺点是如果你存储了大量用户凭证信息,token会变得很大。JwtTokenStore 并不是真正的存储,它不持久化任何数据,但他在 DefaultTokenServices 中的token值和认证信息间的转换中扮演着同样的角色

注意:JDBC service的schema没有包含在当前库中(因为在练习中有太多你可能想使用的东西了),但这里有一个示例可以帮你开始 test code in github。请确保使用了 @EnableTransactionManagement 避免在创建token时client apps间竞争相同行产生冲突。同时注意示例schema中包含明确的 PRIMARY KEY 声明,在并发环境中这也是必需的。

JWT Tokens

要使用JWT token你需要在授权服务器中配置JwtTokenStore,资源服务器同样需要解码token,那么 JwtTokenStore 依赖一个 JwtAccessTokenConverter,所以授权服务器和资源服务器需要同样的实现。token默认是被签名处理的,所以资源服务器也必须能够验证签名,所以它或者需要与授权服务器有相同的密钥(共享密钥或对称密钥),或者它需要匹配授权服务器私钥(签名密钥)的公钥(验证密钥)。授权服务器通过 /oauth/token_key 端点来暴露公钥,它默认是安全的,访问规则是"denyAll()"。你可以打开它,通过注入标准的SpEL表达式到 AuthorizationServerSecurityConfigurer (比如,"permitAll()"或许是合理的,因为它是一个公钥)

要使用 JwtTokenStore 你需要在classpath中引入"spring-security-jwt"(你同样可以在Spring OAuth github仓库上找到它,但它有一个不同的发布周期)

授权类型

AuthorizationEndpoint 支持的授权类型可以通过 AuthorizationServerEndpointsConfigurer 来配置。除了password类型(要开启它,请查看下面的详情),其他所有类型都默认是支持的。下面的属性影响grant type:

  • authenticationManager 通过注入一个AuthenticationManager 来开启password授权
  • userDetailsService 如果你注入了一个 UserDetailsService 或者配置了一个全局的(比如在GlobalAuthenticationManagerConfigurer中),那么refresh token授权会包含一个在user detail上的检查,来确保账户仍然是激活的
  • authorizationCodeServices 为auth code授权定义了authorization code service(AuthorizationCodeServices实例)
  • implicitGrantService 在隐式授权时管理state
  • tokenGranter TokenGranter对象(控制授权的所有方面,并忽略上面属性)

在XML中授权类型在authorization-server的子元素中

配置端点URL

AuthorizationServerEndpointsConfigurer 有一个pathMapping()方法,它接受两个参数:

  • 端点的默认URL路径(框架实现)
  • 想要的自定义路径(以"/"开头)

框架提供的URL路径有:

  • /oauth/authorize authorization 端点
  • /oauth/token token 端点
  • /oauth/confirm_access 用户在这里提交授权批准
  • /oauth/error 用来在授权服务器中展示错误
  • /oauth/check_token 资源服务器用来解码access token
  • /oauth/token_key 在使用JWT token时,暴露公钥以验证token

注意:授权端点 /oauth/authorize(或者它映射的其他路径)必须使用Spring Security进行保护,使他只能被认证的用户访问。比如使用标准的Spring Security WebSecurityConfigurer

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests().antMatchers("/login").permitAll().and()
    // default protection for all resources (including /oauth/authorize)
        .authorizeRequests()
            .anyRequest().hasRole("USER")
    // ... more configuration, e.g. for form login
}

注意:如果你的授权服务器同时也是资源服务器,还有另一个低优先级的安全过滤器链控制API资源。那些被access token保护的请求,就要让他们的路径_不能在面向用户的主过滤器链中匹配上_,那么就要确保在上面的WebSecurityConfigurer 中包含一个request matcher来挑选出非API资源的路径.

在Spring OAuth @Configuration 配置下,token端点默认是受保护的,支持client secret的HTTP Basic认证。但在XML中并非这样(必须明确它是受保护的)。

在XML中 <authorization-server/> 元素以类似的方式用一些属性来改变默认的端点URL。 /check_token 端点必须明确启用(使用 check-token-enabled 属性)

自定义UI

授权服务器的大多数端点被机器主要使用,但还有一些资源需要UI,这些资源有 /oauth/confirm_access/oauth/error。在框架中他们使用白板页面的实现,大多现实世界中的授权服务器实现要提供他们自己的页面,来控制样式和内容。你只需要用Spring MVC 控制器的@RequestMappings提供这些端点,那框架的默认实现在dispatcher中就会处于低优先级。在/oauth/confirm_access 端点中,会给你在session中提供一个 AuthorizationRequest 对象,它里面有查找用户批准授权的所有数据(默认实现是 WhitelabelApprovalEndpoint ,可以参考这个类)。你可以从 AuthorizationRequest 获取所有数据并展示成你喜欢的样子,然后用户要做的就是提交关于批准或者拒绝授权的信息到 /oauth/authorize 。请求参数直接传给 AuthorizationEndpoint 中的 UserApprovalHandler ,你可以任意的拦截数据。默认的 UserApprovalHandler 依赖于你是否在AuthorizationServerEndpointsConfigurer 中提供了 ApprovalStore(提供了则使用 ApprovalStoreUserApprovalHandler,否则使用TokenStoreUserApprovalHandler)。标准的批准处理器接受下面这些:

  • TokenStoreUserApprovalHandler: a simple yes/no decision via user_oauth_approval equals to "true" or "false".

  • TokenStoreUserApprovalHandler 一个简单的yes/no决定,通过 user_oauth_approval 等于"true"或"false"实现

  • ApprovalStoreUserApprovalHandler 一个scope.*的参数键集合,"*" 等于被请求的scope。参数值可以是"true"或者"approved"(如果用户批准了),否则认为用户拒绝那个scope的授权。授权至少要有一个scope被批准。

注意:不要忘了包含CSRF保护你的为用户提供的表单。Spring Security期望一个默认叫作"_csrf"的参数(他在request attribute中提供了值)。参考Spring Security用户指南获取更多相关信息,或者查看指导中的白板页面实现。

强制 SSL

普通的HTTP可以适用于测试,但一个授权服务器应该只通过SSL使用。你可以在安全的容器中运行应用,或者放置在代理后面,并且在你正确设置了代理和容器后,它能良好工作(这跟OAuth2没什么关系)。你可能也想通过Spring Security的 requiresChannel() 限制来保护端点。对于 /authorize 端点,由你把它当作普通应用安全一样去设置。对于 /token 端点,AuthorizationServerEndpointsConfigurer 中有一个标志,你可以使用 sslOnly() 方法设置。这两种情况,安全通道设置都是可选的,但当Spring Security发现一个请求处于非安全通道时,会重定向到它认为安全的通道。

自定义错误处理

在授权服务器中,错误处理使用标准的Spring MVC特性,在端点中标注 @ExceptionHandler 方法。你也可以为端点提供一个 WebResponseExceptionTranslator,这是一个很好的方式来改变响应内容。对于token端点,异常的展示委托给 HttpMesssageConverters (可以在MVC配置中增加),对于authorization端点 ,异常委托给OAuth错误页面(/oauth/error)。白板错误端点用来响应HTML,但你可能要提供自定义的实现(比如,只需要增加@Controller@RequestMapping("/oauth/error")

映射用户角色到Scope

有时不通过client指定的scope来限制token的scope是有用处的,但也要根据用户自己的权限。如果你在 AuthorizationEndpoint 中使用 DefaultOAuth2RequestFactory,你可以设置 checkUserScopes=true 标识来限制允许的scope只能是匹配用户的角色。你也可以注入一个 OAuth2RequestFactoryTokenEndpoint 中(password授权类型) ,但同时你还要设置一个 TokenEndpointAuthenticationFilter (只需要在HTTP BasicAuthenticationFilter 后增加该filter即可)。当然了,你也可以实现你自己的规则,通过映射scope到角色,并设置自己的 OAuth2RequestFactory 实现。 AuthorizationServerEndpointsConfigurer 允许你注入一个 OAuth2RequestFactory,如果你使用了 @EnableAuthorizationServer,你可以利用该特性设置工厂。

资源服务器配置

资源服务器(可以与授权服务器是同一应用,也可以分开)提供对OAuth2 token保护的资源。Spring OAuth提供了Spring Security认证过滤器实现这个保护功能。你可以在一个 @Configuration 类上标注 @EnableResourceServer 来开启,然后使用 ResourceServerConfigurer 来配置它(如果有需要)。可以配置下面这些特性:

  • tokenServices 该bean定义了token service(ResourceServerTokenServices 的实例)
  • resourceId 资源id(可选,建议配置,授权服务器会去验证)
  • 资源服务器的其他扩展点(比如,tokenExtractor 从请求中抽取token)
  • 对保护资源配置request matchers(默认是保护全部资源)
  • 对保护资源的访问规则(默认是普通的"authenticated")
  • 其他在Spring Security配置中被 HttpSecurity 允许的资源的自定义配置

@EnableResourceServer 注解会自动增加一个 OAuth2AuthenticationProcessingFilter 过滤器到Sprin Security过滤器链中

在XML中有 <resource-server/> 元素,它的id属性可以当作servlet Filter 的id,被手动增加到标准的Spring Security链中

ResourceServerTokenServices 是与授权服务器合作的另一重要部分。如果资源服务器和授权服务器在同一应用中,同时你使用了 DefaultTokenServices,那你就不用考虑太多,因为它实现了所有必需的接口,自动保持一致。如果两个服务器是分开的,那就要确保匹配授权服务器的功能,提供一个知道如何正确解码token的 ResourceServerTokenServices 。在资源服务器中,通常使用 DefaultTokenServices,主要是通过设置 TokenStore(后台存储或配置编码)提供可选择性。也可以使用 RemoteTokenServices,这是一个Spring OAuth特性(不属于规范中的部分),通过对授权服务器的HTTP请求(/oauth/check_token)来解码token。如果资源服务器没有大量访问(每个请求都需要授权服务器的验证)时,或者你缓存了结果时,RemoteTokenServices 非常方便。要使用 /oauth/check_token 端点,你必须在 AuthorizationServerSecurityConfigurer 中修改暴露它的访问规则(默认是"denyAll()"),比如:

@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
    oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')").checkTokenAccess(
            "hasAuthority('ROLE_TRUSTED_CLIENT')");
}

在示例中,我们配置了 /oauth/check_token/oauth/token_key 端点(信任的Client可以得到公钥来验证JWT)。这两个端点使用client credentials授权类型被HTTP Basic认证进行保护。

配置一个 OAuth-Aware 表达式处理器

你可能想要使用Spring Security的基于表达式的访问控制@EnableResourceServer 创建时,表达式处理器会默认注册。表达式包括_#oauth2.clientHasRole_, #oauth2.clientHasAnyRole, 和 #oath2.denyClient 可以用来提供基于oauth client角色的访问(更多表达式查看 OAuth2SecurityExpressionMethods)。在XML中,你可以在<http/>元素的安全配置下,使用 expression-handler 元素注册一个oauth-aware表达式处理器。

OAuth 2.0 Client

OAuth 2.0 client机制负责访问OAuth2.0保护的资源。配置涉及到确立用户可能访问到的相关的受保护资源。Client可能还需要提供为用户存储authorization code 和 access token的机制。

配置受保护资源

受保护的资源(或者称为"远程资源")可以被OAuth2ProtectedResourceDetails的bean定义,受保护资源有以下属性:

  • id 资源id,它只被Client用来查找资源,OAuth协议中不会使用它。还被当作bean的id来使用。
  • clientId OAuth client的id,这是OAuth Provider给你的Client定义的id
  • clientSecret 与资源相关的密钥,密钥默认不为空
  • accessTokenUri OAuth Provider提供的access token的端点URL
  • scope 逗号分隔的列表,指定访问资源的范围,默认不会指定scope
  • clientAuthenticationScheme 你的Client访问access token端点时使用的认证scheme。建议的值有:"http_basic" 和 "form",默认是"http_basic"(参考OAuth2规范的2.1章节)

不同的授权类型有不同的 OAuth2ProtectedResourceDetails 的具体实现(比如,ClientCredentialsResource 对应 "client_credentials" 授权类型)。授权类型要求用户授权有进一步的属性:

  • userAuthorizationUri 用户如果需要授权访问某个资源,会被重定向去的URI。注意该属性并不总是需要的,依赖于OAuth2协议的支持情况。

在XML中,有 <resource/> 元素用来创建 OAuth2ProtectedResourceDetails 类型的bean,它包含上面所有的属性。

Client 配置

使用 @EnableOAuth2Client 来配置OAuth2.0 Client非常简单,只需要做两件事情:

  • 创建一个过滤器bean(定义id为oauth2ClientContextFilter)来存储当前请求和上下文。在请求期间需要认证时,它管理着来自和去往OAuth认证URI的重定向。

  • 在request范围中创建一个AccessTokenRequest类型的bean,在authorization code 或 implicit 授权类型时持有每个用户相关的state以免冲突。

该过滤器需要被装配到应用中(比如,使用 Servlet initializer 或者 web.xml 配置一个名为 "oauth2ClientContextFilter" 的 DelegatingFilterProxy

AccessTokenRequest 可以像下面这样在 OAuth2RestTemplate 中使用:

@Autowired
private OAuth2ClientContext oauth2Context;

@Bean
public OAuth2RestTemplate sparklrRestTemplate() {
    return new OAuth2RestTemplate(sparklr(), oauth2Context);
}

OAuth2ClientContext为你放到了session 范围中,为不同用户保持各自的state。如果不是这样,你需要自己在应用中管理这些数据结构,映射请求到用户,并给每个用户关联独立的 OAuth2ClientContext 实例。

在XML中,有 <client/> 元素,id属性用作servlet Filter的bean id,在使用 @Configuration 注解时,必须被映射到一个 DelegatingFilterProxy(以id属性名为名称)。

访问受保护的资源

一旦你提供了所有关于资源的配置,就可以访问那些资源了。推荐访问资源的方法是使用Spring 3中介绍的RestTemplate。Spring Security OAuth 提供了一个RestTemplate 扩展,只需要提供一个OAuth2ProtectedResourceDetails实例。和用户token(authorization code授权类型)一起使用,可以认为是@EnableOAuth2Client配置(相当于XML中的<oauth:rest-template/>),它创建了一些request和session范围的对象,避免不同用户间的冲突。

一般来说,Webb应用不应该使用password授权类型,如果你赞成使用AuthorizationCodeResourceDetails 就避免使用ResourceOwnerPasswordResourceDetails。如果强烈要求使用password授权,那可以同样的方式配置 OAuth2RestTemplate,并把凭证信息添加到 AccessTokenRequest 中(这是一个Map并且是临时性的)而不是添加到 ResourceOwnerPasswordResourceDetails 中(这个对象是在所有access token间共享的)。

在Client持久化Token

Client_不需要_持久化token,但不想每次Client重启后,都要求用户去批准一个新的token时,持久化token是比较友好的。ClientTokenServices 接口定义了要持久化OAuth2.0 token需要的操作。框架提供了JDBC的实现,但如果可以,你最好实现自己的service来存储access token,并在数据库中关联认证实例。如果要使用这个特性,你需要给 OAuth2RestTemplate 提供专门的TokenProvider

@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
public OAuth2RestOperations restTemplate() {
    OAuth2RestTemplate template = new OAuth2RestTemplate(resource(), new DefaultOAuth2ClientContext(accessTokenRequest));
    AccessTokenProviderChain provider = new AccessTokenProviderChain(Arrays.asList(new AuthorizationCodeAccessTokenProvider()));
    provider.setClientTokenServices(clientTokenServices());
    return template;
}

为外部的OAuth2 Provider定制Client

一些外部的OAuth2 Provider(比如Facebook)没有正确的实现规范,或者实现的规范的比Spring Security OAuth要老的版本。在你的Client应用中要使用那些provider,你可能需要适应客户端基础设施的各个部分。

以使用Facebook为例,在 tonr2 应用中有一个Facebook特性(你需要修改应用的配置,添加你自己的,有效的clientId和secret,这些在Facebook网站都很容易生成)。

Facebook的tokne响应中包含一个非不标准的JSON条目来表示token的过期时间(使用 expires,而不是expires_in),如果你要在应用中使用过期时间,必须使用自定义的 OAuth2SerializationService 手动解码。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Easter79 Easter79
3年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这