本文主要研究一下几种自定义spring security的方式

主要方式

  • 自定义UserDetailsService
  • 自定义passwordEncoder
  • 自定义filter
  • 自定义AuthenticationProvider
  • 自定义AccessDecisionManager
  • 自定义securityMetadataSource
  • 自定义access访问控制
  • 自定义authenticationEntryPoint
  • 自定义多个WebSecurityConfigurerAdapter

自定义UserDetailsService

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//......
@Bean
@Override
protected UserDetailsService userDetailsService(){
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("demoUser1").password("123456")
.authorities("ROLE_USER","read_x").build());
manager.createUser(User.withUsername("admin").password("123456")
.authorities("ROLE_ADMIN").build());
return manager;
}
}

通过重写userDetailsService()方法自定义userDetailsService。这里展示的是InMemoryUserDetailsManager。
spring security内置了JdbcUserDetailsManager,可以自行扩展

自定义passwordEncoder

自定义密码的加密方式,实例如下

//......

@Bean
public DaoAuthenticationProvider authenticationProvider() {
final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(encoder());
return authProvider;
}

@Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder(11);
}

}" title="" data-original-title="复制">

@Configuration

@EnableWebSecurity

public class SecurityConfig extends WebSecurityConfigurerAdapter {
<span class="hljs-comment">//......</span>

<span class="hljs-meta">@Bean</span>
public <span class="hljs-type">DaoAuthenticationProvider</span> authenticationProvider() {
<span class="hljs-keyword">final</span> <span class="hljs-type">DaoAuthenticationProvider</span> authProvider = <span class="hljs-keyword">new</span> <span class="hljs-type">DaoAuthenticationProvider</span>();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(encoder());
<span class="hljs-keyword">return</span> authProvider;
} <span class="hljs-meta">@Bean</span>
public <span class="hljs-type">PasswordEncoder</span> encoder() {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-type">BCryptPasswordEncoder</span>(<span class="hljs-number">11</span>);
}

}

自定义filter

自定义filter离不开对spring security内置filter的顺序的认知:

Standard Filter Aliases and Ordering

spring security内置的各种filter顺序如下:

Alias Filter Class Namespace Element or Attribute
CHANNEL_FILTER ChannelProcessingFilter http/intercept-url@requires-channel
SECURITY_CONTEXT_FILTER SecurityContextPersistenceFilter http
CONCURRENT_SESSION_FILTER ConcurrentSessionFilter session-management/concurrency-control
HEADERS_FILTER HeaderWriterFilter http/headers
CSRF_FILTER CsrfFilter http/csrf
LOGOUT_FILTER LogoutFilter http/logout
X509_FILTER X509AuthenticationFilter http/x509
PRE_AUTH_FILTER AbstractPreAuthenticatedProcessingFilter Subclasses N/A
CAS_FILTER CasAuthenticationFilter N/A
FORM_LOGIN_FILTER UsernamePasswordAuthenticationFilter http/form-login
BASIC_AUTH_FILTER BasicAuthenticationFilter http/http-basic
SERVLET_API_SUPPORT_FILTER SecurityContextHolderAwareRequestFilter http/@servlet-api-provision
JAAS_API_SUPPORT_FILTER JaasApiIntegrationFilter http/@jaas-api-provision
REMEMBER_ME_FILTER RememberMeAuthenticationFilter http/remember-me
ANONYMOUS_FILTER AnonymousAuthenticationFilter http/anonymous
SESSION_MANAGEMENT_FILTER SessionManagementFilter session-management
EXCEPTION_TRANSLATION_FILTER ExceptionTranslationFilter http
FILTER_SECURITY_INTERCEPTOR FilterSecurityInterceptor http
SWITCH_USER_FILTER SwitchUserFilter N/A

内置的认证filter

  • UsernamePasswordAuthenticationFilter

参数有username,password的,走UsernamePasswordAuthenticationFilter,提取参数构造UsernamePasswordAuthenticationToken进行认证,成功则填充SecurityContextHolder的Authentication

  • BasicAuthenticationFilter

header里头有Authorization,而且value是以Basic开头的,则走BasicAuthenticationFilter,提取参数构造UsernamePasswordAuthenticationToken进行认证,成功则填充SecurityContextHolder的Authentication

  • AnonymousAuthenticationFilter

给没有登陆的用户,填充AnonymousAuthenticationToken到SecurityContextHolder的Authentication

定义自己的filter

可以像UsernamePasswordAuthenticationFilter或者AnonymousAuthenticationFilter继承GenericFilterBean,或者像BasicAuthenticationFilter继承OncePerRequestFilter。
关于GenericFilterBean与OncePerRequestFilter的区别可以见这篇spring mvc中的几类拦截器对比

自定义filter主要完成功能如下:

  • 提取认证参数
  • 调用认证,成功则填充SecurityContextHolder的Authentication,失败则抛出异常

实例

private final AuthenticationManager authenticationManager;

public DemoAuthFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;

String token = httpServletRequest.getHeader(&quot;app_token&quot;);
if(StringUtils.isEmpty(token)){
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, &quot;invalid token&quot;);
return ;
}

try {
Authentication auth = authenticationManager.authenticate(new WebToken(token));
SecurityContextHolder.getContext().setAuthentication(auth);
filterChain.doFilter(servletRequest, servletResponse);
} catch (AuthenticationException e) {
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
}
}

}" title="" data-original-title="复制">

public class DemoAuthFilter extends GenericFilterBean {
<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-type">AuthenticationManager</span> authenticationManager;

public <span class="hljs-type">DemoAuthFilter</span>(<span class="hljs-type">AuthenticationManager</span> authenticationManager) {
<span class="hljs-keyword">this</span>.authenticationManager = authenticationManager;
} <span class="hljs-meta">@Override</span>
public void doFilter(<span class="hljs-type">ServletRequest</span> servletRequest, <span class="hljs-type">ServletResponse</span> servletResponse, <span class="hljs-type">FilterChain</span> filterChain) <span class="hljs-keyword">throws</span> <span class="hljs-type">IOException</span>, <span class="hljs-type">ServletException</span> {
<span class="hljs-type">HttpServletRequest</span> httpServletRequest = (<span class="hljs-type">HttpServletRequest</span>) servletRequest;
<span class="hljs-type">HttpServletResponse</span> httpServletResponse = (<span class="hljs-type">HttpServletResponse</span>) servletResponse; <span class="hljs-type">String</span> token = httpServletRequest.getHeader(<span class="hljs-string">"app_token"</span>);
<span class="hljs-keyword">if</span>(<span class="hljs-type">StringUtils</span>.isEmpty(token)){
httpServletResponse.sendError(<span class="hljs-type">HttpServletResponse</span>.<span class="hljs-type">SC_UNAUTHORIZED</span>, <span class="hljs-string">"invalid token"</span>);
<span class="hljs-keyword">return</span> ;
} <span class="hljs-keyword">try</span> {
<span class="hljs-type">Authentication</span> auth = authenticationManager.authenticate(<span class="hljs-keyword">new</span> <span class="hljs-type">WebToken</span>(token));
<span class="hljs-type">SecurityContextHolder</span>.getContext().setAuthentication(auth);
filterChain.doFilter(servletRequest, servletResponse);
} <span class="hljs-keyword">catch</span> (<span class="hljs-type">AuthenticationException</span> e) {
httpServletResponse.sendError(<span class="hljs-type">HttpServletResponse</span>.<span class="hljs-type">SC_UNAUTHORIZED</span>, e.getMessage());
}
}

}

设置filter顺序

上面定义完filter之后,然后就要将它放置到filterChain中

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//......
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new DemoAuthFilter(authenticationManager()), BasicAuthenticationFilter.class);
http.csrf().disable();
http.logout().disable();
http.sessionManagement().disable();
}
}

这里把他添加在BasicAuthenticationFilter之前,当然可以根据情况直接替换UsernamePasswordAuthenticationFilter

http.addFilterAt(new DemoAuthFilter(authenticationManager()),UsernamePasswordAuthenticationFilter.class);

自定义AuthenticationProvider

AuthenticationManager接口有个实现ProviderManager相当于一个provider chain,它里头有个List<AuthenticationProvider> providers,通过provider来实现认证。

toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();

    for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
} //......
try {
result = provider.authenticate(authentication); if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
}
catch (InternalAuthenticationServiceException e) {
prepareException(e, authentication);
throw e;
}
catch (AuthenticationException e) {
lastException = e;
}
} //......
}" title="" data-original-title="复制"></span>
</div>
</div><pre class="hljs java"><code><span class="hljs-function"><span class="hljs-keyword">public</span> Authentication <span class="hljs-title">authenticate</span><span class="hljs-params">(Authentication authentication)</span>
<span class="hljs-keyword">throws</span> AuthenticationException </span>{
Class&lt;? extends Authentication&gt; toTest = authentication.getClass();
AuthenticationException lastException = <span class="hljs-keyword">null</span>;
Authentication result = <span class="hljs-keyword">null</span>;
<span class="hljs-keyword">boolean</span> debug = logger.isDebugEnabled(); <span class="hljs-keyword">for</span> (AuthenticationProvider provider : getProviders()) {
<span class="hljs-keyword">if</span> (!provider.supports(toTest)) {
<span class="hljs-keyword">continue</span>;
} <span class="hljs-comment">//......</span>
<span class="hljs-keyword">try</span> {
result = provider.authenticate(authentication); <span class="hljs-keyword">if</span> (result != <span class="hljs-keyword">null</span>) {
copyDetails(authentication, result);
<span class="hljs-keyword">break</span>;
}
}
<span class="hljs-keyword">catch</span> (AccountStatusException e) {
prepareException(e, authentication);
<span class="hljs-comment">// SEC-546: Avoid polling additional providers if auth failure is due to</span>
<span class="hljs-comment">// invalid account status</span>
<span class="hljs-keyword">throw</span> e;
}
<span class="hljs-keyword">catch</span> (InternalAuthenticationServiceException e) {
prepareException(e, authentication);
<span class="hljs-keyword">throw</span> e;
}
<span class="hljs-keyword">catch</span> (AuthenticationException e) {
lastException = e;
}
} <span class="hljs-comment">//......</span>
}</code></pre>

AuthenticationProvider通过supports方法来标识它是否能够处理这个类型的Authentication。
AnonymousAuthenticationFilter构造的是AnonymousAuthenticationToken,由AnonymousAuthenticationProvider来处理

authentication) {
return (AnonymousAuthenticationToken.class.isAssignableFrom(authentication));
}
} " title="" data-original-title="复制">

public class AnonymousAuthenticationProvider implements AuthenticationProvider,
MessageSourceAware {
//......
public boolean supports(Class<?> authentication) {
return (AnonymousAuthenticationToken.class.isAssignableFrom(authentication));
}
}

UsernamePasswordAuthenticationFilter,BasicAuthenticationFilter构造的是UsernamePasswordAuthenticationToken,由DaoAuthenticationProvider(其父类为AbstractUserDetailsAuthenticationProvider)来处理

authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication));
}
} " title="" data-original-title="复制">

public abstract class AbstractUserDetailsAuthenticationProvider implements
AuthenticationProvider, InitializingBean, MessageSourceAware {
//......
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication));
}
}

像上面我们自定义了WebToken,其实例如下:

可以实现Authentication接口,或者继承AbstractAuthenticationToken

private final String token;

public WebToken(String token) {
super(null);
this.token = token;
}

@Override
public Object getCredentials() {
return this.token;
}

@Override
public Object getPrincipal() {
return null;
}

}" title="" data-original-title="复制">

public class WebToken extends AbstractAuthenticationToken {
<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-type">String</span> token;

public <span class="hljs-type">WebToken</span>(<span class="hljs-type">String</span> token) {
<span class="hljs-keyword">super</span>(<span class="hljs-literal">null</span>);
<span class="hljs-keyword">this</span>.token = token;
} <span class="hljs-meta">@Override</span>
public <span class="hljs-type">Object</span> getCredentials() {
<span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.token;
} <span class="hljs-meta">@Override</span>
public <span class="hljs-type">Object</span> getPrincipal() {
<span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
}

}

这里就自定义一下支持这类WebToken的AuthenticationProvider

AuthenticationProvider要实现的功能就是根据参数来校验是否可以登录通过,不通过则抛出异常;通过则获取其GrantedAuthority填充到authentication中
如果是继承了AbstractAuthenticationToken,则是填充其authorities属性
前面自定义的DemoAuthFilter会在登陆成功之后,将authentication写入到SecurityContextHolder的context中
可以实现AuthenticationProvider接口,或者继承AbstractUserDetailsAuthenticationProvider(默认集成了preAuthenticationChecks以及postAuthenticationChecks)

authenticationClass) {
return return (WebToken.class
.isAssignableFrom(authenticationClass));
}
}" title="" data-original-title="复制">

@Service
public class MyAuthProvider implements AuthenticationProvider {
//...
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//......
}
@Override
public boolean supports(Class<?> authenticationClass) {
return return (WebToken.class
.isAssignableFrom(authenticationClass));
}
}

自定义AccessDecisionManager

前面有filter处理了登录问题,接下来是否可访问指定资源的问题就由FilterSecurityInterceptor来处理了。而FilterSecurityInterceptor是用了AccessDecisionManager来进行鉴权。

AccessDecisionManager的几个实现:

  • AffirmativeBased(spring security默认使用)

只要有投通过(ACCESS_GRANTED)票,则直接判为通过。如果没有投通过票且反对(ACCESS_DENIED)票在1个及其以上的,则直接判为不通过。

  • ConsensusBased(少数服从多数)

通过的票数大于反对的票数则判为通过;通过的票数小于反对的票数则判为不通过;通过的票数和反对的票数相等,则可根据配置allowIfEqualGrantedDeniedDecisions(默认为true)进行判断是否通过。

  • UnanimousBased(反对票优先)

无论多少投票者投了多少通过(ACCESS_GRANTED)票,只要有反对票(ACCESS_DENIED),那都判为不通过;如果没有反对票且有投票者投了通过票,那么就判为通过.

实例

其自定义方式之一可以参考聊聊spring security的role hierarchy,展示了如何自定义AccessDecisionVoter。

自定义securityMetadataSource

主要是通过ObjectPostProcessor来实现自定义,具体实例可参考spring security动态配置url权限

自定义access访问控制

对authorizeRequests的控制,可以使用permitAll,anonymous,authenticated,hasAuthority,hasRole等等

                .antMatchers("/login","/css/**", "/js/**","/fonts/**","/file/**").permitAll()
.antMatchers("/anonymous*").anonymous()
.antMatchers("/session").authenticated()
.antMatchers("/login/impersonate").hasAuthority("ROLE_ADMIN")
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/auth/*").hasAnyRole("ADMIN","USER")

这些都是利用spring security内置的表达式。像hasAuthority等,他们内部还是使用access方法来实现的。因此我们也可以直接使用access,来实现最大限度的自定义。

实例

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(&quot;/login/**&quot;,&quot;/logout/**&quot;)
.permitAll()
.anyRequest().access(&quot;@authService.canAccess(request,authentication)&quot;);
}

}" title="" data-original-title="复制">

@EnableWebSecurity

public class SecurityConfig extends WebSecurityConfigurerAdapter {
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">protected</span> void configure(<span class="hljs-type">HttpSecurity</span> http) <span class="hljs-keyword">throws</span> <span class="hljs-type">Exception</span> {
http
.authorizeRequests()
.antMatchers(<span class="hljs-string">"/login/**"</span>,<span class="hljs-string">"/logout/**"</span>)
.permitAll()
.anyRequest().access(<span class="hljs-string">"@authService.canAccess(request,authentication)"</span>);
}

}

这个就有点像使用spring EL表达式,实现实例如下

public boolean canAccess(HttpServletRequest request, Authentication authentication) {
Object principal = authentication.getPrincipal();
if(principal == null){
return false;
}

if(authentication instanceof AnonymousAuthenticationToken){
//check if this uri can be access by anonymous
//return
}

Set<String> roles = authentication.getAuthorities()
.stream()
.map(e -> e.getAuthority())
.collect(Collectors.toSet());
String uri = request.getRequestURI();
//check this uri can be access by this role

return true;

}

}" title="" data-original-title="复制">

@Component

public class AuthService {
<span class="hljs-keyword">public</span> <span class="hljs-built_in">boolean</span> canAccess(HttpServletRequest request, Authentication authentication) {
<span class="hljs-built_in">Object</span> principal = authentication.getPrincipal();
<span class="hljs-keyword">if</span>(principal == <span class="hljs-literal">null</span>){
<span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
} <span class="hljs-keyword">if</span>(authentication <span class="hljs-keyword">instanceof</span> AnonymousAuthenticationToken){
<span class="hljs-comment">//check if this uri can be access by anonymous</span>
<span class="hljs-comment">//return</span>
} Set&lt;<span class="hljs-built_in">String</span>&gt; roles = authentication.getAuthorities()
.stream()
.map(e -&gt; e.getAuthority())
.collect(Collectors.toSet());
<span class="hljs-built_in">String</span> uri = request.getRequestURI();
<span class="hljs-comment">//check this uri can be access by this role</span> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>; }

}

自定义authenticationEntryPoint

比如你想给basic认证换个realmName,除了再spring security配置中指定

security.basic.realm=myrealm

也可以这样

public static BasicAuthenticationEntryPoint createBasicAuthEntryPoint(String realmName){
BasicAuthenticationEntryPoint entryPoint = new BasicAuthenticationEntryPoint();
entryPoint.setRealmName(realmName);
return entryPoint;
}" title="" data-original-title="复制"></span>
</div>
</div><pre class="hljs typescript"><code> httpBasic().authenticationEntryPoint(createBasicAuthEntryPoint(<span class="hljs-string">"myrealm"</span>))

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> BasicAuthenticationEntryPoint createBasicAuthEntryPoint(<span class="hljs-built_in">String</span> realmName){
BasicAuthenticationEntryPoint entryPoint = <span class="hljs-keyword">new</span> BasicAuthenticationEntryPoint();
entryPoint.setRealmName(realmName);
<span class="hljs-keyword">return</span> entryPoint;
}</code></pre>

自定义多个WebSecurityConfigurerAdapter

spring security使用antMatchers不支持not的情况,因此可以自定义多个WebSecurityConfigurerAdapter,利用order优先级来实现匹配的覆盖,具体可以参考这篇文章Multiple Entry Points in Spring Security

小结

还有其他自定义的方式,等后续有发现再补上。

doc

原文地址:https://segmentfault.com/a/1190000012560773

posted @
2019-06-14 10:32 
星朝 
阅读(...) 
评论(...) 
编辑 
收藏

spring security自定义指南的更多相关文章

  1. Spring Security 自定义登录认证(二)

    一.前言 本篇文章将讲述Spring Security自定义登录认证校验用户名.密码,自定义密码加密方式,以及在前后端分离的情况下认证失败或成功处理返回json格式数据 温馨小提示:Spring Se ...

  2. (二)spring Security 自定义登录页面与校验用户

    文章目录 配置 security 配置下 MVC 自定义登录页面 自定义一个登陆成功欢迎页面 效果图 小结: 使用 Spring Boot 的快速创建项目功能,勾选上本篇博客需要的功能:web,sec ...

  3. 解决Spring Security自定义filter重复执行问题

    今天做项目的时候,发现每次拦截器日志都会打两遍,很纳闷,怀疑是Filter被执行了两遍.结果debug之后发现还真是!记录一下这个神奇的BUG! 问题描述 项目中使用的是Spring-security ...

  4. 【JavaEE】SSH+Spring Security自定义Security的部分处理策略

    本文建立在 SSH与Spring Security整合 一文的基础上,从这篇文章的example上做修改,或者从 配置了AOP 的example上做修改皆可.这里主要补充我在实际使用Spring Se ...

  5. Spring Security自定义GrantedAuthority前缀

    如果我们正使用Spring Security提供默认的基于方法的权限认证注解如下: @PreAuthorize("hasAnyRole('ADMIN', 'USER')") pub ...

  6. Spring Boot整合Spring Security自定义登录实战

    本文主要介绍在Spring Boot中整合Spring Security,对于Spring Boot配置及使用不做过多介绍,还不了解的同学可以先学习下Spring Boot. 本demo所用Sprin ...

  7. Spring Security 自定义 登陆 权限验证

    转载于:https://www.jianshu.com/p/6b8fb59b614b 项目简介 基于Spring Cloud 的项目,Spring Cloud是在Spring Boot上搭建的所以按照 ...

  8. 02 spring security 自定义用户认证流程

    1. 自定义登录页面 (1)首先在static目录下面创建login.html       注意: springboot项目默认可以访问resources/resources, resources/s ...

  9. Spring Security 自定义登录页面

    SpringMVC + Spring Security,自定义登录页面登录验证 学习参考:http://www.mkyong.com/spring-security/spring-security-f ...

随机推荐

  1. spss命令数据整理中compute与record命令的区别

    spss命令数据整理中compute与record命令的区别 record修改存在的变量,或者生成新的变量 spss变量定义说明 1.Name:变量名,定义规则与其它软件中的雷同,如第一个字符必须为字 ...

  2. 为何需要apigee edge

    http://apigee.com/docs/gateway-services/content/what-apigee-edge 越来越多的服务商在网上提供服务,通过各种标准的接口对来自手机.电脑.p ...

  3. iOS 警告收录及科学快速的消除方法

    http://www.cocoachina.com/ios/20150914/13287.html 作者:董铂然 授权本站转载. 前言:现在你维护的项目有多少警告?看着几百条警告觉得心里烦么?你真的觉 ...

  4. Xcode 中的Bundle versions string, short 和 Bundle version 区别

    Bundle version is the internal version number of your app. Short version string is the publically vi ...

  5. XCode4 App Store提交小结

    本文建立在你的应用程序已开发完成的基础上 本文以理清流程为主 本文的内容以Distribution为准,但是所附的参考资料也有对Ad Hoc的说明 三种证书(Development.Distribut ...

  6. JavaScript 错误

    try 语句测试代码块的错误. catch 语句处理错误. throw 语句创建自定义错误. JavaScript 错误 当 JavaScript 引擎执行 JavaScript 代码时,会发生各种错 ...

  7. [HLSL]HLSL 入门参考 (dx11龙书附录B译文)

    原文:[HLSL]HLSL 入门参考 (dx11龙书附录B译文) HLSL 高级着色语言 参考文档 龙书DirectX12现已推出中文版,其附录B的高级着色器语言参考的翻译质量比本文更高,有条件的读者 ...

  8. 重磅开源|AOP for Flutter开发利器——AspectD

    https://github.com/alibaba-flutter/aspectd 问题背景 随着Flutter这一框架的快速发展,有越来越多的业务开始使用Flutter来重构或新建其产品.但在我们 ...

  9. 9-1进程,进程池和socketserver

    一 进程: # 什么是进程 : 运行中的程序,计算机中最小的资源分配单位# 程序开始执行就会产生一个主进程# python中主进程里面启动一个进程 —— 子进程# 同时主进程也被称为父进程# 父子进程 ...

  10. oracle函数 LPAD(c1,n[,c2])

    [功能]在字符串c1的左边用字符串c2填充,直到长度为n时为止 [参数]C1 字符串 n 追加后字符总长度 c2 追加字符串,默认为空格 [返回]字符型 [说明]如果c1长度大于n,则返回c1左边n个 ...