Arhn - архитектура программирования

Бесконечный цикл в пользовательском приложении Spring Security

Мы пытаемся заменить существующий базовый логин Spring Security на REST-API в приложении с открытым исходным кодом, чтобы получить настраиваемый логин с токеном. Я прочитал сообщение в блоге на эту тему: http://javattitude.com/2014/06/07/spring-security-custom-token-based-rest-authentication/

Когда запрос не имеет заголовка с именем «Cookie», я получаю правильный ответ 401 - неавторизованный ответ (ожидаемое поведение). Когда запрос имеет действительный токен, я получаю бесконечный цикл, вызывающий java.lang.StackOverflowError:

    Exception in thread "http-bio-8080-exec-45" java.lang.StackOverflowError
        at org.apache.tomcat.util.http.NamesEnumerator.<init>(MimeHeaders.java:402)
        at org.apache.tomcat.util.http.MimeHeaders.names(MimeHeaders.java:228)
        at org.apache.catalina.connector.Request.getHeaderNames(Request.java:2108)
        at org.apache.catalina.connector.RequestFacade.getHeaderNames(RequestFacade.java:726)
        at javax.servlet.http.HttpServletRequestWrapper.getHeaderNames(HttpServletRequestWrapper.java:103)
        at javax.servlet.http.HttpServletRequestWrapper.getHeaderNames(HttpServletRequestWrapper.java:103)
        at javax.servlet.http.HttpServletRequestWrapper.getHeaderNames(HttpServletRequestWrapper.java:103)
        at javax.servlet.http.HttpServletRequestWrapper.getHeaderNames(HttpServletRequestWrapper.java:103)
        at javax.servlet.http.HttpServletRequestWrapper.getHeaderNames(HttpServletRequestWrapper.java:103)
        at javax.servlet.http.HttpServletRequestWrapper.getHeaderNames(HttpServletRequestWrapper.java:103)
at org.activiti.rest.security.CustomTokenAuthenticationFilter.attemptAuthentication(CustomTokenAuthenticationFilter.java:43)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:211)
    at org.activiti.rest.security.CustomTokenAuthenticationFilter.doFilter(CustomTokenAuthenticationFilter.java:86)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:65)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:166)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:749)
    at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:487)
    at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:412)
    at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:339)
    at org.springframework.security.web.firewall.RequestWrapper$FirewalledRequestAwareRequestDispatcher.forward(RequestWrapper.java:132)
    at org.activiti.rest.security.TokenSimpleUrlAuthenticationSuccessHandler.onAuthenticationSuccess(TokenSimpleUrlAuthenticationSuccessHandler.java:30)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.successfulAuthentication(AbstractAuthenticationProcessingFilter.java:331)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.successfulAuthentication(AbstractAuthenticationProcessingFilter.java:298)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:235)
    at org.activiti.rest.security.CustomTokenAuthenticationFilter.doFilter(CustomTokenAuthenticationFilter.java:86)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:65)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:101)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:166)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:749)
    at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:487)
    at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:412)
    at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:339)
    at org.springframework.security.web.firewall.RequestWrapper$FirewalledRequestAwareRequestDispatcher.forward(RequestWrapper.java:132)
    at org.activiti.rest.security.TokenSimpleUrlAuthenticationSuccessHandler.onAuthenticationSuccess(TokenSimpleUrlAuthenticationSuccessHandler.java:30)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.successfulAuthentication(AbstractAuthenticationProcessingFilter.java:331)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.successfulAuthentication(AbstractAuthenticationProcessingFilter.java:298)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:235)
    at org.activiti.rest.security.CustomTokenAuthenticationFilter.doFilter(CustomTokenAuthenticationFilter.java:86)

Моя конфигурация безопасности Spring выглядит следующим образом:

@Configuration
@EnableWebSecurity
@EnableWebMvcSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

  @Bean
  public AuthenticationProvider authenticationProvider() {
    return new BasicAuthenticationProvider();
  }

  @Autowired
  AuthenticationProvider basicAuthenticationProvider;

  @Bean
  public CustomTokenAuthenticationFilter customTokenAuthenticationFilter(){
      System.out.println("+++ create new CustomTokenAuthenticationFilter for path=/**");
      return new CustomTokenAuthenticationFilter("/**");
  };

  @Autowired
  CustomTokenAuthenticationFilter customTokenAuthenticationFilter;

  @Override
  protected void configure(HttpSecurity http) throws Exception {
      System.out.println("init of http security START");
     http
     .authenticationProvider(authenticationProvider())
     .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
     .csrf().disable()
     .authorizeRequests()
       .anyRequest().authenticated()
     .and()//.authenticationProvider(basicAuthenticationProvider);
     .addFilterBefore(customTokenAuthenticationFilter,  BasicAuthenticationFilter.class)
     .httpBasic();
       //.and().addFilter(filter);
    System.out.println("init of http security DONE");
  }
}

Я уже пытался изменить сопоставление URL-адресов с /** на /activiti-rest/**, но затем снова срабатывает обычная аутентификация.

Это мой пользовательский фильтр аутентификации токена:

public class CustomTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private static final Logger logger = LoggerFactory.getLogger(CustomTokenAuthenticationFilter.class);
    public CustomTokenAuthenticationFilter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
        super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(defaultFilterProcessesUrl));
        setAuthenticationManager(new NoOpAuthenticationManager());
        setAuthenticationSuccessHandler(new TokenSimpleUrlAuthenticationSuccessHandler());
    }


    public final String HEADER_SECURITY_TOKEN = "Cookie";//"LdapToken"; 


    /**
     * Attempt to authenticate request - basically just pass over to another method to authenticate request headers 
     */
    @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        Enumeration<String> headerNames = request.getHeaderNames();
        int i = 0;
        while (headerNames.hasMoreElements()){
            String key = (String) headerNames.nextElement();
            String value = request.getHeader(key);
            System.out.println("+++ key["+i+"]" +key);
            System.out.println("+++ val["+i+"]" +value);
            i++;
        }
        String token = request.getHeader(HEADER_SECURITY_TOKEN);
        logger.info("token found:"+token);
        System.out.println("+++ token found:"+token);
        AbstractAuthenticationToken userAuthenticationToken = authUserByToken(token);
        if(userAuthenticationToken == null) throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));
        System.out.println("+++ userAuthenticationToken:"+userAuthenticationToken.toString());
        return userAuthenticationToken;
    }


    /**
     * authenticate the user based on token
     * @return
     */
    private AbstractAuthenticationToken authUserByToken(String token) {
        if(token==null) {
            System.out.println("+++ i shouldn't be null +++");
            return null;
        }
        AbstractAuthenticationToken authToken = new JWTAuthenticationToken(token);
        try {
            return authToken;
        } catch (Exception e) {
            System.out.println(e);
            logger.error("Authenticate user by token error: ", e);
        }
        return authToken;
    }


    @Override
    public void doFilter(ServletRequest req, ServletResponse res,
            FilterChain chain) throws IOException, ServletException {
        System.out.println("++++++++++++++++++++++++++++++ doFilter ");
        super.doFilter(req, res, chain);
    }
}

И мой обработчик Custom Success. Я думаю, что это вызывает бесконечный цикл, но я не могу понять, почему:

public class TokenSimpleUrlAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    @Override
    protected String determineTargetUrl(HttpServletRequest request,
            HttpServletResponse response) {
        System.out.println("+++ yuhuuu determineTargetUrl+++");
        String context = request.getContextPath();
        String fullURL = request.getRequestURI();
        String url = fullURL.substring(fullURL.indexOf(context)+context.length());
        return url;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
            HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException {
        System.out.println("+++ yuhuuu onAuthenticationSuccess+++");
        String url = determineTargetUrl(request,response);
        request.getRequestDispatcher(url).forward(request, response);
    }
}

Все остальные классы (NoOpAuthenticationManager и RestAuthenticationEntryPoint) точно такие же, как в этом блоге.

Было бы здорово, если бы кто-нибудь мог подсказать мне, что может вызвать этот бесконечный цикл. Как я уже сказал, это происходит только тогда, когда в запросе есть действительный токен.

Спасибо и с наилучшими пожеланиями Бен


  • Что произойдет, если вы удалите код, который печатает заголовки? 06.02.2015
  • Я думаю, что проблема в сопоставлении URL. Я использую /** как URL-шаблон. Например, я называю REST-URL localhost:8080/activiti-rest/service. Мой CustomFilter вызывается, потому что он соответствует /**. request.getRequestDispatcher(url).forward(запрос, ответ); имеет в качестве параметра URL в этом случае /service, который также соответствует URL-шаблону. Итак, мой CustomAuthenticationFilter вызывается снова. Возникает петля. Но я понятия не имею, как это исправить, поскольку все REST-URL должны быть защищены. 06.02.2015
  • Если вы готовы изучить альтернативный подход, вы можете посмотреть это приложение, который избавляется от настраиваемых фильтров и использует базовую инфраструктуру Spring Security для защиты конечных точек REST API. 07.02.2015

Ответы:


1

ваш подход к кодированию действителен. Однако я могу предложить вам несколько иной, но работающий подход. Прежде чем я начну объяснять решение, вот код:

WebSecurityConfig.java

@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().
    antMatchers("/restapi").hasRole("USER")
    .and().addFilterBefore(new SsoTokenAuthenticationFilter(authenticationManager()), BasicAuthenticationFilter.class).httpBasic()
    .and().authorizeRequests().antMatchers("/**").permitAll().anyRequest().authenticated();
}

@Override
protected void configure(AuthenticationManagerBuilder auth)
        throws Exception {
    // The order is important! During runtime Spring Security tries to find Provider-Implementations that
    // match the UsernamePasswordAuthenticationToken (which will be created later..). We must make sure
    // that daoAuthenticationProvider matches first. Why? Hard to explain, I figured it out with the debugger.
    auth.authenticationProvider(daoAuthenticationProvider());
    auth.authenticationProvider(tokenAuthenticationProvider());

}

@Bean
public AuthenticationProvider tokenAuthenticationProvider() {
    return new SsoTokenAuthenticationProvider();
}

@Bean
public AuthenticationProvider daoAuthenticationProvider() {
    // DaoAuthenticationProvider requires a userDetailsService object to be attached.
    // So we build one. This replaces the AuthenticationConfiguration, which is commented out below

    // Build the userDetailsService
    User userThatMustMatch = new User("michael", "password", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_RESTUSER"));
    Collection<UserDetails> users = new ArrayList<>();
    users.add(userThatMustMatch);
    InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager(users);  

    // Create the DaoAuthenticationProvider that will handle all HTTP BASIC AUTH requests
    DaoAuthenticationProvider daoAuthProvider = new DaoAuthenticationProvider();
    daoAuthProvider.setUserDetailsService(userDetailsService);
    return daoAuthProvider;
}

SsoTokenAuthenticationFilter.java

public class SsoTokenAuthenticationFilter extends GenericFilterBean {

public final String HEADER_SECURITY_COOKIE = "LdapToken"; 

private AuthenticationManager authenticationManager;
private AuthenticationDetailsSource<HttpServletRequest,?> ssoTokenAuthenticationDetailsSource = new SsoTokenWebAuthenticationDetailsSource();

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

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    HttpServletResponse httpResponse = (HttpServletResponse) response;

    // check if SSO token is available. If not, pass down to next filter in chain
    try {
        Cookie[] cookies = httpRequest.getCookies();
        if (cookies == null){
            chain.doFilter(request, response);
            return;
        }
        Cookie ssoCookie = null;
        for (int i = 0; i < cookies.length; i++) {
            if (cookies[i].getName().equals("ssoToken"))
                ssoCookie = cookies[i];
            }
        if (ssoCookie == null){
            chain.doFilter(request, response);
            return;
        }

        // SSO token found, now authenticate and afterwards pass down to next filter in chain
        authenticateWithSsoToken(httpRequest);
        logger.debug("now the AuthenticationFilter passes down to next filter in chain");
        chain.doFilter(request, response);
    } catch (InternalAuthenticationServiceException internalAuthenticationServiceException) {
        SecurityContextHolder.clearContext();
        logger.error("Internal authentication service exception", internalAuthenticationServiceException);
        httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
    } catch (AuthenticationException authenticationException) {
        SecurityContextHolder.clearContext();
        logger.debug("No or invalid SSO token");
        httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage());
    } 
}

private void authenticateWithSsoToken(HttpServletRequest request) throws IOException {
    System.out.println("+++ authenticateWithSSOToken +++");
    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(null, null, AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_RESTUSER"));
    authRequest.setDetails(ssoTokenAuthenticationDetailsSource.buildDetails(request));        

    // Delegate authentication to SsoTokenAuthenticationProvider, he will call the SsoTokenAuthenticationProvider <-- because of the configuration in WebSecurityConfig.java
    Authentication authResult = authenticationManager.authenticate(authRequest);
}}

SsoTokenAuthenticationProvider.java

public class SsoTokenAuthenticationProvider implements AuthenticationProvider {

public SsoTokenAuthenticationProvider() {

}

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    SsoTokenWebAuthenticationDetails ssoTokenWebAuthenticationDetails = null;
    WebAuthenticationDetails webWebAuthenticationDetails = (WebAuthenticationDetails)authentication.getDetails();

    if (! (webWebAuthenticationDetails instanceof SsoTokenWebAuthenticationDetails)){
        // ++++++++++++++++++++++++
        // BASIC authentication....
        // ++++++++++++++++++++++++
        UsernamePasswordAuthenticationToken emptyToken = new UsernamePasswordAuthenticationToken(null, null);
        emptyToken.setDetails(null);
        return emptyToken; //return null works, too.
    }

    // ++++++++++++++++++++++++
    // LDAP authentication....
    // ++++++++++++++++++++++++
    ssoTokenWebAuthenticationDetails = (SsoTokenWebAuthenticationDetails)webWebAuthenticationDetails;       
    Cookie ssoTokenCookie = ssoTokenWebAuthenticationDetails.getSsoTokenCookie();

    // check if SSO cookie is available
    if (ssoTokenCookie == null){ 
        return new UsernamePasswordAuthenticationToken(null, null); //do basic auth.
    }
    String username = ssoTokenCookie.getValue();

    // Do your SSO token authentication here
    if (! username.equals("michael"))
        return new UsernamePasswordAuthenticationToken(null, null); //do basic auth.

    // Create new Authentication object. Name and password can be null (but you can set the values of course).
    // Be careful with your role names!
    // In WebSecurityConfig the role "USER" is automatically prefixed with String "ROLE_", so it is "ROLE_USER" in the end.
    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(null, null, AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER,ROLE_RESTUSER"));
    authRequest.setDetails(ssoTokenWebAuthenticationDetails);

    // Don't let spring decide.. you already have made the right decisions. Tell spring you have an authenticated user.
    // vielleicht ist dieses obere Kommentar auch bullshit... ich lese das morgen noch mal nach...
    SecurityContextHolder.getContext().setAuthentication(authentication);
    return authentication;
}

@Override
public boolean supports(Class<?> authentication) {
    return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}

SsoTokenWebAuthenticationDetailsSource.java

public class SsoTokenWebAuthenticationDetailsSource extends
    WebAuthenticationDetailsSource {

@Override
public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
    return new SsoTokenWebAuthenticationDetails(context);
}

}

SsoTokenWebAuthenticationDetails.java

public class SsoTokenWebAuthenticationDetails extends WebAuthenticationDetails {
private static final long serialVersionUID = 1234567890L;

private Cookie ssoTokenCookie;

public SsoTokenWebAuthenticationDetails(HttpServletRequest request) {
    super(request);
    // Fetch cookie from request
    Cookie[] cookies = request.getCookies();

    Cookie ssoTokenCookie = null;
    for (int i = 0; i < cookies.length; i++) {
        if (cookies[i].getName().equals("SSOToken"))
            ssoTokenCookie= cookies[i];
        }
    this.setSsoTokenCookie(ssoTokenCookie);
}

public Cookie getSsoTokenCookie() {
    return ssoTokenCookie;
}

public void setSsoTokenCookie(Cookie ssoTokenCookie) {
    this.ssoTokenCookie = ssoTokenCookie;
}
}

Я описываю решение в виде слов:

  1. Класс Config защищает любой контроллер /restapi с ролью ROLE_USER. Аутентификацию можно выполнить с использованием httpBasic-аутентификации, но прежде чем вы сможете попробовать обычную аутентификацию. вы должны попытаться аутентифицировать пользователя с помощью ssoTokenCookie (если доступно). Поэтому вы устанавливаете фильтр SsoTokenAuthenticationFilter перед базовой аутентификацией. применены.
  2. Inside the filter, you check if a ssoTokenCookie is available in request.
    • If yes, you delegate the authentication to the standard spring AuthenticationManager. The AuthenticationManager knows your own SsoTokenAuthenticationProvider implementation and delegates the authentication to it. Here, it is important to have the cookie information available. This can be done by use of a customized WebAuthenticationDetails.
    • если нет, вы передаете работу следующему фильтру в цепочке. Неудивительно, что стандарт будет называться BasicAuthenticationFilter. Поскольку вы сказали Spring использовать стандартный daoAuthenticationProvider в WebSecurityConfig.java, Spring может аутентифицировать пользователя, когда правильные учетные данные будут введены в базовую аутентификацию. диалог
09.02.2015
  • Большое спасибо. Работал отлично. Кроме того, я исправил свой код следующей частью: static final String FILTER_APPLIED = __spring_security_scpf_applied; если (req.getAttribute(FILTER_APPLIED) != null) { chain.doFilter(req, res); возвращаться; } req.setAttribute(FILTER_APPLIED, Boolean.TRUE); С этим моя рекурсия исчезла. 11.02.2015
  • Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..