Spring Securtiy 认证流程(源码分析)_重写daoauthenticationprovider 不走daoauthenticationpro-程序员宅基地

当用 Spring Security 框架进行认证时,你可能会遇到这样的问题:

你输入的用户名或密码不管是空还是错误,它的错误信息都是 Bad credentials。

那么如果你想根据不同的情况给出相应的错误提示该怎么办呢?

这个时候我们只有了解 Spring Securiy 认证的流程才能知道如何修改代码。

好啦,来看下面的例子,大部分人的 WebSecurityConfig 的 configure 代码都类似于下:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // TODO Auto-generated method stub
        http
        .authorizeRequests()
        .anyRequest().permitAll()
        .and()
        .formLogin().loginPage("/signin")
        .usernameParameter("username")
        .passwordParameter("password")
        .loginProcessingUrl("/signin")
        .and()
        .csrf().disable();
    }

相信以上代码大家都知道什么意思:任何请求信息都允许,也就是不需要身份认证。

登录页面请求为 /signin,用户名和密码参数的name属性分别是 username,password。登录页面 form 的 action 请求为 /signin。

当然这个 action 不必和登录页面请求一样。最后的那个是禁止跨站请求伪造。

这段代码和登录认证联系较大的应该是从 loginPage() 到 loginProcessingUrl() 里的方法。

咱先从 loginPage 看起,鼠标左键拖动覆盖 loginPage,然后右键 Open Declaration 就进入到了 FormLoginConfigurer 类。

这个类里值得注意的方法有两个:构造方法和 loginPage 方法。

    public FormLoginConfigurer() {
        super(new UsernamePasswordAuthenticationFilter(), null);
        usernameParameter("username");
        passwordParameter("password");
    }

    public FormLoginConfigurer<H> loginPage(String loginPage) {
        return super.loginPage(loginPage);
    }

构造方法中使用了一个用户名密码认证过滤器类,这一看就和认证有关系。

loginPage 方法大家可以自行按照这个步骤查看,现在直接看 UsernamePasswordAuthenticationFilter 类。

    public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
    private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
    private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
    private boolean postOnly = true;

   public UsernamePasswordAuthenticationFilter() {
        super(new AntPathRequestMatcher("/login", "POST"));
    }

   public Authentication attemptAuthentication(HttpServletRequest request,
            HttpServletResponse response) throws AuthenticationException {
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + request.getMethod());
        }
        String username = obtainUsername(request);
        String password = obtainPassword(request);
        if (username == null) {
            username = "";
        }
        if (password == null) {
            password = "";
        }
        username = username.trim();
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
        username, password);
    
setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest); }

 这只是其中一部分代码,其他的可以自己看。该类中定义的两个字符串和构造方法定义了默认的登录方式。

登录 action 请求为以 POST 方式的 /login,用户名及密码分别以 username,password 属性值获取。

该类的父类的父类 GenericFilterBean 实现了 InitializingBean 接口,也就是会初始化为一个 Bean。

当看到 attemptAuthentication 时,就知道他是认证的方法啦。

这里咱直接看到 new UsernamePasswordAuthenticationToken(username, password);

    public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        setAuthenticated(false);
    }

从这里可以知道,它把用户名和密码分别存在了 principal,credentials 里。

现在我们只需要记住登录信息存在了 authRequest 里。现在来看下setDetails,虽然我不感兴趣。

    protected void setDetails(HttpServletRequest request,
            UsernamePasswordAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

它调用了一个 buildDetails 方法,实际上是调用的:(追根溯源可以看到)

    
/**
  * Records the remote address and will also set the session Id if a session already
  * exists (it won't create one).
  *
  * @param request that the authentication request was received from
  */
public WebAuthenticationDetails(HttpServletRequest request) { this.remoteAddress = request.getRemoteAddr(); HttpSession session = request.getSession(false); this.sessionId = (session != null) ? session.getId() : null; }

从源码注释可以看到,它是记录远程地址并且会设置一个会话 ID,这里我们不管它了。

直接看这一句:return this.getAuthenticationManager().authenticate(authRequest);

它调用的是一个实现了 AuthenticationManager 接口的类的 authenticate 方法。

从源码中我们找不到它用的是哪个实现类,网上说是 ProviderManager 类,我们来看一下该类。

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
        InitializingBean {
  public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        AuthenticationException parentException = null;
        Authentication result = null;
        Authentication parentResult = null;
        boolean debug = logger.isDebugEnabled();

        for (AuthenticationProvider provider : getProviders()) {
            if (!provider.supports(toTest)) {
                continue;
            }

            if (debug) {
                logger.debug("Authentication attempt using "
                        + provider.getClass().getName());
            }

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

        if (result == null && parent != null) {
            // Allow the parent to try.
            try {
                result = parentResult = parent.authenticate(authentication);
            }
            catch (ProviderNotFoundException e) {
                // ignore as we will throw below if no other exception occurred prior to
                // calling parent and the parent
                // may throw ProviderNotFound even though a provider in the child already
                // handled the request
            }
            catch (AuthenticationException e) {
                lastException = parentException = e;
            }
        }

        if (result != null) {
            if (eraseCredentialsAfterAuthentication
                    && (result instanceof CredentialsContainer)) {
                // Authentication is complete. Remove credentials and other secret data
                // from authentication
                ((CredentialsContainer) result).eraseCredentials();
            }

            // If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
            // This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
            if (parentResult == null) {
                eventPublisher.publishAuthenticationSuccess(result);
            }
            return result;
        }

        // Parent was null, or didn't authenticate (or throw an exception).

        if (lastException == null) {
            lastException = new ProviderNotFoundException(messages.getMessage(
                    "ProviderManager.providerNotFound",
                    new Object[] { toTest.getName() },
                    "No AuthenticationProvider found for {0}"));
        }

        // If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
        // This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
        if (parentException == null) {
            prepareException(lastException, authentication);
        }

        throw lastException;
    }
}

这里我只给出该类的声明和 authenticate 方法,从类的声明可以看出来它也会初始化为一个 Bean,咱找不到很正常对吧。

authenticate 方法会遍历所有的 AuthenticationProvider ,然后调用 provider 的 authenticate 方法。

如果认证结果不为空的话将会保存到 result 中,并且擦除认证信息再返回 result。

为空的话一般是没有提供 AuthenticationProvider,会报 ProviderNotFoundException 错误。

现在我们来看下 provider 的 authenticate 方法。

    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider provider = new CustomAuthenticationProvider();
        provider.setMessageSource(messageSource);
        provider.setUserDetailsService(userService);
        provider.setPasswordEncoder(new BCryptPasswordEncoder());
        return provider;
    }

这个是我写的一个 AuthenticationProvider,只不过我重写了一个类继承了 DaoAuthenticationProvider。

这里我们来看 DaoAuthenticationProvider 类:(这个类里面并没有发现  authenticate 方法,那先从它的父类找)

父类是 AbstractUserDetailsAuthenticationProvider,它也实现了 InitializingBean 接口,也是初始化为一个 Bean。

public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
                () -> messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.onlySupports",
                        "Only UsernamePasswordAuthenticationToken is supported"));

        // Determine username
        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
                : authentication.getName();

        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);

        if (user == null) {
            cacheWasUsed = false;

            try {
                user = retrieveUser(username,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
            catch (UsernameNotFoundException notFound) {
                logger.debug("User '" + username + "' not found");

                if (hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.badCredentials",
                            "Bad credentials"));
                }
                else {
                    throw notFound;
                }
            }

            Assert.notNull(user,
                    "retrieveUser returned null - a violation of the interface contract");
        }

        try {
            preAuthenticationChecks.check(user);
            additionalAuthenticationChecks(user,
                    (UsernamePasswordAuthenticationToken) authentication);
        }
        catch (AuthenticationException exception) {
            if (cacheWasUsed) {
                // There was a problem, so try again after checking
                // we're using latest data (i.e. not from the cache)
                cacheWasUsed = false;
                user = retrieveUser(username,
                        (UsernamePasswordAuthenticationToken) authentication);
                preAuthenticationChecks.check(user);
                additionalAuthenticationChecks(user,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
            else {
                throw exception;
            }
        }

        postAuthenticationChecks.check(user);

        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;

        if (forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }

        return createSuccessAuthentication(principalToReturn, authentication, user);
    }

 在这段代码中可以知道:如果 authentication.getPrincipal() 为空的话,username 将会为 NONE_PROVIDED。

不为空的话将会得到 authentication.getPrincipal(),也就是用户名,只是这种类型不是 String 类型,但可以强制转换。

代码中是 authentication.getName(),这种和上面基本一样,只不过该类型是 String 类型的。

然后定义一个 user,先尝试从缓存中获取 user,没获取到的话就通过 retrieveUser 获取。

该类中 retrieveUser 是一个抽象方法,我们现在来看 DaoAuthenticationProvider 类里的方法。

protected final UserDetails retrieveUser(String username,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        prepareTimingAttackProtection();
        try {
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        }
        catch (UsernameNotFoundException ex) {
            mitigateAgainstTimingAttack(authentication);
            throw ex;
        }
        catch (InternalAuthenticationServiceException ex) {
            throw ex;
        }
        catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }

从代码中可以看到是通过我们之前写的 UserDetailsService 方法获取用户。

接下来我们看后面的代码,这部分异常代码我们等会再看。

try {
            preAuthenticationChecks.check(user);
            additionalAuthenticationChecks(user,
                    (UsernamePasswordAuthenticationToken) authentication);
        }

这两句代码是对用户进行检查的,第一行代码调用的其实是这部分的:

private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
        public void check(UserDetails user) {
            if (!user.isAccountNonLocked()) {
                logger.debug("User account is locked");

                throw new LockedException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.locked",
                        "User account is locked"));
            }

            if (!user.isEnabled()) {
                logger.debug("User account is disabled");

                throw new DisabledException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.disabled",
                        "User is disabled"));
            }

            if (!user.isAccountNonExpired()) {
                logger.debug("User account is expired");

                throw new AccountExpiredException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.expired",
                        "User account has expired"));
            }
        }
    }

可以看到并不是检查密码的,只是对用户状态进行检查。那么我们不管它了,看下一行代码:

@SuppressWarnings("deprecation")
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            logger.debug("Authentication failed: no credentials provided");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }

        String presentedPassword = authentication.getCredentials().toString();

        if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            logger.debug("Authentication failed: password does not match stored value");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }
    }

这里有个获取密码的操作:authentication.getCredentials()。

然后如果密码不为空的话就通过 passwordEncoder.matches(presentedPassword, userDetails.getPassword() 检查是否匹配。

如果匹配成功的话,嗯,这部分结束了,我们回到 AbstractUserDetailsAuthenticationProvider 类里的 authenticate 方法。

return createSuccessAuthentication(principalToReturn, authentication, user);

它会返回一个创建成功认证方法的返回值。这里我们就不管了。

现在我们先回到AbstractUserDetailsAuthenticationProvider 类的错误处理上:

try {
                user = retrieveUser(username,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
            catch (UsernameNotFoundException notFound) {
                logger.debug("User '" + username + "' not found");

                if (hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.badCredentials",
                            "Bad credentials"));
                }
                else {
                    throw notFound;
                }
            }

这个是用户找不到引起的错误,我们看下 messages.getMessage():

    public String getMessage(String code, String defaultMessage) {
        String msg = this.messageSource.getMessage(code, null, defaultMessage, getDefaultLocale());
        return (msg != null ? msg : "");
    }

再来看下这个里面的 getMessage():

它是一个接口类里的方法:根据 code 返回 messageSource 里的字符串,如果不存在这个 code,就返回 defaultMessage。

既然是个接口类,那我们看下它的实现类,回到 messageSource,查看一下它:

public class SpringSecurityMessageSource extends ResourceBundleMessageSource {
    // ~ Constructors
    // ===================================================================================================

    public SpringSecurityMessageSource() {
        setBasename("org.springframework.security.messages");
    }

    // ~ Methods
    // ========================================================================================================

    public static MessageSourceAccessor getAccessor() {
        return new MessageSourceAccessor(new SpringSecurityMessageSource());
    }
}

原来是从这个路径里找数据源。

其他的错误处理也是一样,这里就省略了。那我们如何获取错误信息呢?

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // TODO Auto-generated method stub
        http
        .authorizeRequests()
        .anyRequest().permitAll()
        .and()
        .formLogin().loginPage("/signin")
        .usernameParameter("username")
        .passwordParameter("password")
        .loginProcessingUrl("/signin")
        .failureHandler(authenticationFailureHandler)
        .and()
        .csrf().disable();

看到那个 failureHandler 没,这个是登录失败处理器,这里加上只是看一下里面源码:AbstractAuthenticationFilterConfigurer

    /**
     * Specifies the {
    @link AuthenticationFailureHandler} to use when authentication
     * fails. The default is redirecting to "/login?error" using
     * {
    @link SimpleUrlAuthenticationFailureHandler}
     *
     * @param authenticationFailureHandler the {
    @link AuthenticationFailureHandler} to use
     * when authentication fails.
     * @return the {
    @link FormLoginConfigurer} for additional customization
     */
    public final T failureHandler(
            AuthenticationFailureHandler authenticationFailureHandler) {
        this.failureUrl = null;
        this.failureHandler = authenticationFailureHandler;
        return getSelf();
    }

从注释中可以看出默认的失败处理器是 SimpleUrlAuthenticationFailureHandler:

    public void onAuthenticationFailure(HttpServletRequest request,
            HttpServletResponse response, AuthenticationException exception)
            throws IOException, ServletException {

        if (defaultFailureUrl == null) {
            logger.debug("No failure URL set, sending 401 Unauthorized error");

            response.sendError(HttpStatus.UNAUTHORIZED.value(),
                HttpStatus.UNAUTHORIZED.getReasonPhrase());
        }
        else {
            saveException(request, exception);

            if (forwardToDestination) {
                logger.debug("Forwarding to " + defaultFailureUrl);

                request.getRequestDispatcher(defaultFailureUrl)
                        .forward(request, response);
            }
            else {
                logger.debug("Redirecting to " + defaultFailureUrl);
                redirectStrategy.sendRedirect(request, response, defaultFailureUrl);
            }
        }
    }

因为默认的 defaultFailureUrl 为 /login?error,从 AbstractAuthenticationFilterConfigurer 类里可以看出来。

登录失败后,会调用 saveException(request, exception); 保存错误信息。

protected final void saveException(HttpServletRequest request,
            AuthenticationException exception) {
        if (forwardToDestination) {
            request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
        }
        else {
            HttpSession session = request.getSession(false);

            if (session != null || allowSessionCreation) {
                request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION,
                        exception);
            }
        }
    }

由于该类中 forwardToDestination 为 false,它将执行 else 里的语句。

将错误信息保存到会话的 WebAttributes.AUTHENTICATION_EXCEPTION 属性中:

public static final String AUTHENTICATION_EXCEPTION = "SPRING_SECURITY_LAST_EXCEPTION";

所有我们可以通过会话的这个属性来获取错误信息。(thymeleaf)

(注意:signin.html 不能放在 static 目录下,不然获取不到错误信息。)

<p th:if="${param.error}" th:text="${session?.SPRING_SECURITY_LAST_EXCEPTION?.message}" ></p>

好啦,都介绍完了,可以看下我的 CustomAuthenticationProvider:

package security.config;

import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.Assert;

public class CustomAuthenticationProvider extends DaoAuthenticationProvider {
    
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        // TODO Auto-generated method stub

        String presentedPassword = authentication.getCredentials().toString();
        if (!getPasswordEncoder().matches(presentedPassword, userDetails.getPassword())) {
            logger.debug("Authentication failed: password does not match stored value");

            throw new BadCredentialsException(messages.getMessage(
                    "UNameOrPwdIsError","Username or Password is not correct"));
        }
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // TODO Auto-generated method stub
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
                () -> messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.onlySupports",
                        "Only UsernamePasswordAuthenticationToken is supported"));

        if("".equals(authentication.getPrincipal())) {
            throw new BadCredentialsException(messages.getMessage(
                    "UsernameIsNull","Username cannot be empty"));
        }
        if("".equals(authentication.getCredentials())) {
            throw new BadCredentialsException(messages.getMessage(
                    "PasswordIsNull","Password cannot be empty"));
        }
        
        String username = (String) authentication.getPrincipal();
        boolean cacheWasUsed = true;
        UserDetails user = this.getUserCache().getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;
            try {
                user = retrieveUser(username,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
            catch (UsernameNotFoundException notFound) {
                logger.debug("User '" + username + "' not found");

                if (hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(messages.getMessage(
                            "UNameOrPwdIsError","Username or Password is not correct"));
                }
                else {
                    throw notFound;
                }
            }
            Assert.notNull(user,
                    "retrieveUser returned null - a violation of the interface contract");
        }
        try {
            getPreAuthenticationChecks().check(user);
            additionalAuthenticationChecks(user,
                    (UsernamePasswordAuthenticationToken) authentication);
        }
        catch (AuthenticationException exception) {
            if (cacheWasUsed) {
                cacheWasUsed = false;
                user = retrieveUser(username,
                        (UsernamePasswordAuthenticationToken) authentication);
                getPreAuthenticationChecks().check(user);
                additionalAuthenticationChecks(user,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
            else {
                throw exception;
            }
        }

        getPostAuthenticationChecks().check(user);

        if (!cacheWasUsed) {
            this.getUserCache().putUserInCache(user);
        }

        Object principalToReturn = user;

        if (isForcePrincipalAsString()) {
            principalToReturn = user.getUsername();
        }

        return createSuccessAuthentication(principalToReturn, authentication, user);
    }
    

}

这里值得注意的是 "".equals(authentication.getPrincipal()),"".equals(authentication.getCredentials())

因为如果按照那个 AbstractUserDetailsAuthenticationProvider 类来写的话,发现这一步永不为 null。

我通过加入代码 System.out.println(username);  才知道的,应该是个坑吧。

项目代码可供大家参考:

链接:https://pan.baidu.com/s/1pNWQMyIgZOzX5_rF3Tvd2A
提取码:m585

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/songfei_dream/article/details/103445434

智能推荐

pic秒表c语言程序,PIC单片机C语言编程实例三-第7章秒表.doc-程序员宅基地

文章浏览阅读91次。PIC单片机C语言编程实例三-第7章秒表.docPAGEPAGE 133第7章 秒 表7.2.2 程序清单该源程序已在实验板上调试通过,读者可直接引用,并可利用软件编程的灵活性,加以拓展,实现更为复杂的功能。#include#include //此程序实现计时秒表功能,时钟显示范围00.00~99.99秒,分辨度:0.01秒unsigned chars0,s1,s2,s3;//定义0.0..._pic跑表启动条件

Java调用Groovy脚本_groovy evaluate-程序员宅基地

文章浏览阅读2.1k次。概述 在Java和Groovy的结合中,经常会碰到需要从Java代码中调起一个写好的Groovy脚本,我们可以通过GroovyShell来实现。其中最重要的就是GroovyShell和Binding两个类,其中GroovyShell可以调起一个Groovy脚本,而Binding可以向脚本里面传递参数。简单示例//通过Binding向要执行的groovy脚本传递变量 Bindi..._groovy evaluate

服务器发送信息给arduino,arduino通过esp8266模块发送数据到云服务器-程序员宅基地

文章浏览阅读1.4k次,点赞2次,收藏25次。arduino通过esp8266模块发送数据到云服务器我是代码小白,一个正在做毕设的秃头少年。鄙人拙作,有不当之处,还请指教。最近买了一套arduino设备,打算做一个物联网设备小玩意,可是怎么把数据上传到云服务器可愁坏我了。通过对比实验,我决定用esp8266wifi模块进行通信。云服务器的话,我现在还没写相应的代码,所以先用Onenet平台进行配置。Onenet平台进行配置1.进入Onenet..._esp8266 arduino wifi发送数据

latex h t b p是什么意思_latex htpb-程序员宅基地

文章浏览阅读1.4w次,点赞9次,收藏17次。常用选项[htbp]是浮动格式:『h』当前位置。将图形放置在正文文本中给出该图形环境的地方。如果本页所剩的页面不够,这一参数将不起作用。『t』顶部。将图形放置在页面的顶部。『b』底部。将图形放置在页面的底部。『p』浮动页。将图形放置在一只允许有浮动对象的页面上。在table或者figure 后加 [!htb] 是系统忽略“美学”标准,把表格和图片插入到你的代码中,是动的,但是不加感叹号,它就是按顺序选择h(此处),t(上方),b(下方),所以为了让图片随着你的代码移动,最好加一个[!htb]_latex htpb

【转载】linux下的usb抓包方法-程序员宅基地

文章浏览阅读67次。1 linux下的usb抓包方法1、配置内核使能usb monitor:make menuconfigDevice Drivers --> USB Support --> USB Monitor --> Sel..._linux安装tcpdump 查看usb

计算机组成pc em ir,计算机组成 课程设计报告.doc-程序员宅基地

文章浏览阅读164次。计算机组成 课程设计报告计算机组成原理课程设计报告姓 名:班 级:学 号:指导老师:2016年 6月31日目 录第一章 背景知识与课设任务概述11.1课设目的11.2课设任务11.2111.2211.2321.2421.252第二章 课设内容32.1指令的执行流程32.1.132.1.242.1.352.2存储器62.2.162.3运算器72.3.172.4硬件系统组成122.4..._计算机组成课程设计报告

随便推点

Texlive2020+Texstudio2.12.22资源,附安装教程和书-程序员宅基地

文章浏览阅读1.6k次,点赞21次,收藏6次。Texlive2020+Texstudio2.12.22资源,附安装教程和刘海洋latex入门使用说明书百度云地址文件截图![\[\]](https://img-blog.csdnimg.cn/20201031105628261.png#pic_center)总结百度云地址链接:https://pan.baidu.com/s/1w4ZdEHvgMBF2uURQmnAxXw提取码:6jga复制这段内容后打开百度网盘手机App,操作更方便哦–来自百度网盘超级会员V1的分享文件截图总结如果链接..

Hamburgers 二分答案_hamburger题解-程序员宅基地

文章浏览阅读5.2k次。题目大意:有一种汉堡,用B、S、C三种原料做成,现在告诉你当前有的B、S、C的个数,到商店买的B、S、C的单价(商店无限供应这三种原料),还有你拥有的钱。问最多能做多少个汉堡。刚开始我还以为是模拟,先把能用的用完,再去买。但是写了半天写不下去了,找了一下题解才发现是二分答案板子题。发现自己对二分还是不是很敏感。AC代码://https://blog.csdn.net/hesorche..._hamburger题解

ubuntu下安装uhd+gnuradio_无法定位软件包 libuhd003-程序员宅基地

文章浏览阅读1.9k次。提示:安装uhd+gnuradio实际上并不难,只是实际安装的时候,作为新手经常会因为缺乏相关知识而踩不少坑,以下是我踩坑安装的一些记录。gnuradio+uhd安装过程ubuntu下安装uhd+gnuradioExample: For UHD 3.9.5:Example: For UHD 3.14.0.0win10下安装ubuntu双系统使用usrpb210ubuntu18.04安装方法有两种,一种是使用已经编译好的二进制码,缺点是版本通常比较旧,但学习usrp也不需要太新的版本,另外,这种_无法定位软件包 libuhd003

Awk命令详解_在linux系统中,awk允许进行多种测试。作为样式匹配,还提供了模式匹配表达式,以下-程序员宅基地

文章浏览阅读3.1k次,点赞4次,收藏45次。awk文本和数据进行处理的编程语言awk 是一种编程语言,用于在linux/unix下对文本和数据进行处理。数据可以来自标准输入(stdin)、一个或多个文件,或其它命令的输出。它支持用户自定义函数和动态正则表达式等先进功能,是linux/unix下的一个强大编程工具。它在命令行中使用,但更多是作为脚本来使用。awk有很多内建的功能,比如数组、函数等,这是它和C语言的相同之处,灵活性是awk最大的优势。awk命令格式和选项语法形式awk [options] ‘script’ va_在linux系统中,awk允许进行多种测试。作为样式匹配,还提供了模式匹配表达式,以下

QT5实现简单的TCP通信_qt tcpsocket 同步接收-程序员宅基地

文章浏览阅读8.3w次,点赞115次,收藏776次。这段时间用到了QT的TCP通信,做了初步的学习与尝试,编写了一个客户端和服务器基于窗口通信的小例程。使用QT的网络套接字需要.pro文件中加入一句:QT += network一、客户端1、客户端的代码比服务器稍简单,总的来说,使用QT中的QTcpSocket类与服务器进行通信只需要以下5步:(1)创建QTcpSocket套接字对象socket = new Q..._qt tcpsocket 同步接收

基于相空间重构的混沌背景下微弱信号检测算法matlab仿真,对比SVM,PSO-SVM以及GA-PSO-SVM_微弱信号检测 仿真-程序员宅基地

文章浏览阅读500次。支持向量机(Support Vector Machine,SVM)是一种用于分类和回归的机器学习方法,其原理基于寻找一个最优超平面(或者曲线在非线性情况下)来划分不同类别的数据点。SVM 的目标是找到一个能够最大化不同类别之间的间隔(margin)的超平面,从而在未知数据上取得良好的泛化能力。SVM 的目标是找到一个超平面,使得距离超平面最近的数据点(支持向量)到超平面的距离(间隔)最大。_微弱信号检测 仿真