草庐IT

Spring Boot + Spring Security + Spring OAuth2 + Google 登录

coder 2023-05-12 原文

我已经设置了一个小项目,使用 Spring Boot (1.5.2)、Spring Security 和 Spring Security OAuth2 实现 OAuth2 Login 和 Google+ API。

您可以在以下位置找到来源:https://github.com/ccoloradoc/OAuth2Sample

我能够通过 google 进行身份验证并提取用户信息。但是,在我尝试将“https://accounts.google.com/o/oauth2/auth”与我的 RestTemplate 连接以调用 google api 之后,我无法再次登录,因为我收到了“400 Bad Request”。

请参阅过滤器尝试身份验证方法以获取更多引用。

这是我的安全配置类

@Configuration
@EnableGlobalAuthentication
@EnableOAuth2Client
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@PropertySource(value = {"classpath:oauth.properties"})
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {


    @Autowired
    private UserDetailsService userDetailsService;

    @Resource
    @Qualifier("accessTokenRequest")
    private AccessTokenRequest accessTokenRequest;

    @Autowired
    private OAuth2ClientContextFilter oAuth2ClientContextFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http.
                authorizeRequests()
                .antMatchers(HttpMethod.GET, "/login","/public/**", "/resources/**","/resources/public/**").permitAll()
                .antMatchers("/google_oauth2_login").anonymous()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/login")
                .defaultSuccessUrl("/")
                .and()
                .csrf().disable()
                .logout()
                .logoutSuccessUrl("/")
                .logoutUrl("/logout")
                .deleteCookies("remember-me")
                .and()
                .rememberMe()
                .and()
                .addFilterAfter(oAuth2ClientContextFilter,ExceptionTranslationFilter.class)
                .addFilterAfter(googleOAuth2Filter(),OAuth2ClientContextFilter.class)
                .userDetailsService(userDetailsService);
        // @formatter:on
    }

    @Bean
    @ConfigurationProperties("google.client")
    public OAuth2ProtectedResourceDetails auth2ProtectedResourceDetails() {
        return new AuthorizationCodeResourceDetails();
    }

    @Bean
    public OAuth2RestTemplate oauth2RestTemplate() {
        return new OAuth2RestTemplate(auth2ProtectedResourceDetails(),
                new DefaultOAuth2ClientContext(accessTokenRequest));
    }


    @Bean
    public GoogleOAuth2Filter googleOAuth2Filter() {
        return new GoogleOAuth2Filter("/google_oauth2_login");
    }

    /*
    *  Building our custom Google Provider
    * */
    @Bean
    public GoogleOauth2AuthProvider googleOauth2AuthProvider() {
        return new GoogleOauth2AuthProvider();
    }

    /*
    *  Using autowired to assign it to the auth manager
    * */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(googleOauth2AuthProvider());
    }

    @Bean
    public SpringSecurityDialect springSecurityDialect() {
        return new SpringSecurityDialect();
    }

    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }

}

这是我的身份验证提供程序:

public class GoogleOauth2AuthProvider implements AuthenticationProvider {

    private static final Logger logger = LoggerFactory.getLogger(GoogleOauth2AuthProvider.class);

    @Autowired(required = true)
    private UserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        logger.info("Provider Manager Executed");
        CustomOAuth2AuthenticationToken token = (CustomOAuth2AuthenticationToken) authentication;
        UserDetailsImpl registeredUser = (UserDetailsImpl) token.getPrincipal();
        try {
            registeredUser = (UserDetailsImpl) userDetailsService
                    .loadUserByUsername(registeredUser.getEmail());
        } catch (UsernameNotFoundException usernameNotFoundException) {
            logger.info("User trying google/login not already a registered user. Register Him !!");
        }
        return token;
    }

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

UserDetailService 是 Spring Security Core 的一个实现,它从数据库中读取用户并将其转换为实现 Spring Security Core UserDetails 的 UserDetails POJO。

这是我的过滤器实现:

public class GoogleOAuth2Filter extends AbstractAuthenticationProcessingFilter {

    /**
     * Logger
     */
    private static final Logger log = LoggerFactory.getLogger(GoogleOAuth2Filter.class);

    private static final Authentication dummyAuthentication;

    static {
        dummyAuthentication = new UsernamePasswordAuthenticationToken(
                "dummyUserName23452346789", "dummyPassword54245",
                CustomUserDetails.DEFAULT_ROLES);
    }

    private static final String NAME = "name";
    private static final String EMAIL = "email";
    private static final String PICTURE = "picture";

    private static final Logger logger = LoggerFactory
            .getLogger(GoogleOAuth2Filter.class);


    @Value(value = "${google.authorization.url}")
    private String googleAuhorizationUrl;

    public GoogleOAuth2Filter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
    }

    @Autowired
    private UserService userService;

    @Autowired
    private OAuth2RestTemplate oauth2RestTemplate;

    @Autowired
    @Override
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        super.setAuthenticationManager(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException,
            IOException, ServletException {
        logger.info("Google Oauth Filter Triggered!!");
        URI authURI;
        try {
            authURI = new URI(googleAuhorizationUrl);
        } catch (URISyntaxException e) {
            log.error("\n\n\n\nERROR WHILE CREATING GOOGLE AUTH URL", e);
            return null;
        }
        SecurityContext context = SecurityContextHolder.getContext();
        // auth null or not authenticated.
        String code = request.getParameter("code");
        Map<String, String[]> parameterMap = request.getParameterMap();
        logger.debug(parameterMap.toString());
        if (StringUtils.isEmpty(code)) {
            // Google authentication in progress. will return null.
            logger.debug("Will set dummy user in context ");
            context.setAuthentication(dummyAuthentication);
            // trigger google oauth2.
            // ERROR ON SECOND LOGIN ATTEMPT
            oauth2RestTemplate.postForEntity(authURI, null, Object.class);
            return null;
        } else {
            logger.debug("Response from Google Recieved !!");

            ResponseEntity<Object> forEntity = oauth2RestTemplate.getForEntity(
                    "https://www.googleapis.com/plus/v1/people/me/openIdConnect",
                    Object.class);

            @SuppressWarnings("unchecked")
            Map<String, String> profile = (Map<String, String>) forEntity.getBody();

            CustomOAuth2AuthenticationToken authenticationToken = getOAuth2Token(
                    profile.get(EMAIL), profile.get(NAME), profile.get(PICTURE));
            authenticationToken.setAuthenticated(false);

            return getAuthenticationManager().authenticate(authenticationToken);
        }
    }

    private CustomOAuth2AuthenticationToken getOAuth2Token(
            String email, String name, String picture) {

        User user = userService.findByEmail(email);
        //Register user
        if(user == null) {
            user = new User(name, email, picture);
            userService.saveOrUpdate(user);
        }

        UserDetailsImpl registeredUser = new UserDetailsImpl(name, email, picture);

        CustomOAuth2AuthenticationToken authenticationToken =
                new CustomOAuth2AuthenticationToken(registeredUser);

        return authenticationToken;
    }

}

最佳答案

感谢 Cristian,您不知道您的代码对我自己的代码奠定了基础。我修改了你原来的OAuth2 Github项目,改成如下代码。

GoogleOAuth2Filter.java

package tech.aabo.celulascontentas.oauth.filter;

import static java.lang.Math.toIntExact;
import com.google.api.client.auth.oauth2.AuthorizationCodeResponseUrl;
import com.google.api.client.auth.oauth2.TokenResponse;
import com.google.api.client.auth.oauth2.TokenResponseException;
import com.google.api.client.googleapis.auth.oauth2.*;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.plus.Plus;
import com.google.api.services.plus.model.Person;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.configurationprocessor.json.JSONException;
import org.springframework.boot.configurationprocessor.json.JSONObject;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import tech.aabo.celulascontentas.oauth.domain.User;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.Arrays;
import java.util.Calendar;
import java.util.UUID;

/**
 * Created by colorado on 9/03/17.
 * Modified by frhec on 7/06/18
 */
public class GoogleOAuth2Filter extends AbstractAuthenticationProcessingFilter {
/**
 * Logger
 */
private static final Logger logger = LoggerFactory.getLogger(GoogleOAuth2Filter.class);


public GoogleOAuth2Filter(String defaultFilterProcessesUrl) {
    super(defaultFilterProcessesUrl);
}

@Autowired
@Override
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
    super.setAuthenticationManager(authenticationManager);
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    String CLIENT_SECRET_FILE = "client_secret.json";

    SecurityContext context = SecurityContextHolder.getContext();

    if(context.getAuthentication() == null) {

        GoogleClientSecrets clientSecrets = loadSecret(CLIENT_SECRET_FILE);

        if (StringUtils.isEmpty(request.getQueryString())) {
            try {
                GoogleAuthorizationCodeRequestUrl auth = new GoogleAuthorizationCodeRequestUrl(clientSecrets.getDetails().getClientId(),
                        request.getRequestURL().toString(), Arrays.asList(
                        "https://www.googleapis.com/auth/plus.login",
                        "https://www.googleapis.com/auth/plus.me",
                        "https://www.googleapis.com/auth/plus.profile.emails.read")).setState("/user");
                auth.setAccessType("offline");
                response.addHeader("Place","Before");
                response.sendRedirect(auth.build());
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {

            response.addHeader("Place","After");
            AuthorizationCodeResponseUrl authResponse = new AuthorizationCodeResponseUrl(transformName(request, 0));
            // check for user-denied error
            if (authResponse.getError() != null) {
                logger.info("Denied");
            } else {
                try {
                    assert clientSecrets != null;
                    Calendar calendar = Calendar.getInstance();

                    NetHttpTransport net = new NetHttpTransport();
                    JacksonFactory jackson = new JacksonFactory();

                    GoogleTokenResponse tokenResponse =
                            new GoogleAuthorizationCodeTokenRequest(net, jackson,
                                    clientSecrets.getDetails().getClientId(), clientSecrets.getDetails().getClientSecret(),
                                    authResponse.getCode(), transformName(request, 1))
                                    .execute();

                    // Use access token to call API
                    GoogleCredential credential;

                    if (tokenResponse.getRefreshToken() == null) {
                        credential = new GoogleCredential();
                        credential.setFromTokenResponse(tokenResponse);
                    } else {
                        credential = createCredentialWithRefreshToken(net, jackson, clientSecrets, tokenResponse);
                    }

                    Plus plus =
                            new Plus.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), credential)
                                    .setApplicationName("Google Plus Profile Info")
                                    .build();

                    Person profile = plus.people().get("me").execute();

                    // Get profile info from ID token
                    GoogleIdToken idToken = tokenResponse.parseIdToken();
                    GoogleIdToken.Payload payload = idToken.getPayload();

                    User auth = new User();

                    auth.setAccessToken(tokenResponse.getAccessToken());
                    auth.setId(new BigInteger(payload.getSubject().trim())); // Use this value as a key to identify a user.
                    auth.setUuid(UUID.randomUUID().toString());
                    auth.setEmail(payload.getEmail());
                    auth.setVerifiedEmail(payload.getEmailVerified());
                    auth.setName(profile.getDisplayName());
                    auth.setPictureURL(profile.getImage().getUrl());
                    auth.setLocale(profile.getLanguage());
                    auth.setFamilyName(profile.getName().getFamilyName());
                    auth.setGivenName(profile.getName().getGivenName());
                    auth.setStatus(true);
                    auth.setExpired(false);
                    auth.setLocked(false);
                    auth.setExpiredCredentials(false);
                    auth.setRoles("USER");
                    auth.setRefreshToken(tokenResponse.getRefreshToken());
                    auth.setDateCreated(calendar.getTime());
                    calendar.add(Calendar.SECOND, toIntExact(tokenResponse.getExpiresInSeconds()));
                    auth.setExpirationDate(calendar.getTime());
                    auth.setDateModified(Calendar.getInstance().getTime());

                    Authentication authenticationToken = getOAuth2Token(auth);

                    request.authenticate(response);

                    if (//Validation happening) {
                        authenticationToken.setAuthenticated(true);
                    } else {
                        authenticationToken.setAuthenticated(false);
                    }

                    return authenticationToken;

                } catch (TokenResponseException e) {
                    if (e.getDetails() != null) {
                        System.err.println("Error: " + e.getDetails().getError());
                        if (e.getDetails().getErrorDescription() != null) {
                            System.err.println(e.getDetails().getErrorDescription());
                        }
                        if (e.getDetails().getErrorUri() != null) {
                            System.err.println(e.getDetails().getErrorUri());
                        }
                    } else {
                        System.err.println(e.getMessage());
                    }
                } catch (IOException | ServletException e) {
                    e.printStackTrace();
                }
            }

        }
    }else if(!context.getAuthentication().isAuthenticated()) {
        setResponseUnauthenticated(response);
    }else{
        try {
            response.sendRedirect(transformName(request,2)+"/user");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return null;
}

private void setResponseUnauthenticated(HttpServletResponse response){
    try {

        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");
        PrintWriter out = response.getWriter();

        //create Json Object
        JSONObject values = new JSONObject();

        values.put("principal", null);

        values.put("authentication", null);
        values.put("timestamp", String.valueOf(Timestamp.from(Instant.now())));
        values.put("code",401);
        values.put("message", "Not Authorized");

        out.print(values.toString());
    } catch (JSONException | IOException e) {
        e.printStackTrace();
    }
}

public static GoogleCredential createCredentialWithRefreshToken(HttpTransport transport,
                                                                JsonFactory jsonFactory,
                                                                GoogleClientSecrets clientSecrets,
                                                                TokenResponse tokenResponse) {
    return new GoogleCredential.Builder().setTransport(transport)
            .setJsonFactory(jsonFactory)
            .setClientSecrets(clientSecrets)
            .build()
            .setFromTokenResponse(tokenResponse);
}


public static String transformName(HttpServletRequest request, Integer type){

    switch(type) {
        case 0:
            return request.getScheme() + "://" +   // "http" + "://
                    request.getServerName() +       // "myhost"
                    ":" +                           // ":"
                    request.getServerPort() +       // "8080"
                    request.getRequestURI() +       // "/people"
                    "?" +                           // "?"
                    request.getQueryString();       // "lastname=Fox&age=30"
        case 1:
            return request.getScheme() + "://" +   // "http" + "://
                    request.getServerName() +       // "myhost"
                    ":" +                           // ":"
                    request.getServerPort() +       // "8080"
                    request.getRequestURI();      // "/people"
        case 2:
            return request.getScheme() + "://" +   // "http" + "://
                    request.getServerName() +       // "myhost"
                    ":" +                           // ":"
                    request.getServerPort();        // "8080"
        default:
            return request.getScheme() + "://" +   // "http" + "://
                    request.getServerName() +       // "myhost"
                    ":" +                           // ":"
                    request.getServerPort() +       // "8080"
                    request.getRequestURI() +       // "/people"
                    "?" +                           // "?"
                    request.getQueryString();       // "lastname=Fox&age=30"
    }
}

@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {

    SecurityContextHolder.getContext().setAuthentication(authResult);

    // Fire event
    if (this.eventPublisher != null) {
        eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                authResult, this.getClass()));
    }

    response.sendRedirect(transformName(request,2)+"/user");


}

private CustomOAuth2AuthenticationToken getOAuth2Token(User auth) {

    return new CustomOAuth2AuthenticationToken(auth);
}

private GoogleClientSecrets loadSecret(String name){
    ClassPathResource resource = new ClassPathResource(name);
    try {
        // Exchange auth code for access token
        return GoogleClientSecrets.load(JacksonFactory.getDefaultInstance(), new FileReader(resource.getFile()));
    } catch (IOException e) {
        return null;
    }
}

}

我还将主要的安全类更改为:

private GoogleOAuth2Filter googleOAuth2Filter = new GoogleOAuth2Filter("/login/google");

@Override
protected void configure(HttpSecurity http) throws Exception {
     // @formatter:off
     http.antMatcher("/**")
            .authorizeRequests()
               .antMatchers("/", "/login/google", "/error**").permitAll().anyRequest().authenticated()
             .and().exceptionHandling().authenticationEntryPoint((request, response, e) -> {
                 //create Json Object
                 try {
                      JSONObject values = new JSONObject();
                      values.put("principal", JSONObject.NULL);
                      values.put("authentication", JSONObject.NULL);
                      values.put("timestamp", String.valueOf(Timestamp.from(Instant.now())));
                      values.put("code",401);
                      values.put("message", "Not Authorized");

                      response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                      response.setContentType("application/json");
                      response.setCharacterEncoding("UTF-8");
                      response.getWriter().write(values.toString());
                  } catch (JSONException | IOException f) {
                     f.printStackTrace();
                  }
                })
            .and().addFilterBefore(googleOAuth2Filter, BasicAuthenticationFilter.class);
        // @formatter:on
}

我还为/user 和/logout 创建了自定义映射。

希望以后能帮到别人

关于Spring Boot + Spring Security + Spring OAuth2 + Google 登录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42727014/

有关Spring Boot + Spring Security + Spring OAuth2 + Google 登录的更多相关文章

  1. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  2. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  3. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

  4. arrays - Ruby 数组 += vs 推送 - 2

    我有一个数组数组,想将元素附加到子数组。+=做我想做的,但我想了解为什么push不做。我期望的行为(并与+=一起工作):b=Array.new(3,[])b[0]+=["apple"]b[1]+=["orange"]b[2]+=["frog"]b=>[["苹果"],["橙子"],["Frog"]]通过推送,我将推送的元素附加到每个子数组(为什么?):a=Array.new(3,[])a[0].push("apple")a[1].push("orange")a[2].push("frog")a=>[[“苹果”、“橙子”、“Frog”]、[“苹果”、“橙子”、“Frog”]、[“苹果”、“

  5. += 的 Ruby 方法 - 2

    有没有办法让Ruby能够做这样的事情?classPlane@moved=0@x=0defx+=(v)#thisiserror@x+=v@moved+=1enddefto_s"moved#{@moved}times,currentxis#{@x}"endendplane=Plane.newplane.x+=5plane.x+=10putsplane.to_s#moved2times,currentxis15 最佳答案 您不能在Ruby中覆盖复合赋值运算符。任务在内部处理。您应该覆盖+,而不是+=。plane.a+=b与plane.a=

  6. ruby - Google-api-ruby-client 翻译 API 示例 - 2

    很高兴看到google代码:google-api-ruby-client项目,因为这对我来说意味着Ruby人员可以使用GoogleAPI-s来完善代码。虽然我现在很困惑,因为给出的唯一示例使用Buzz,并且根据我的实验,Google翻译(v2)api的行为必须与google-api-ruby-client中的Buzz完全不同。.我对“Explorer”演示示例很感兴趣——但据我所知,它并不是一个探索器。它所做的只是调用一个Buzz服务,然后浏览它已经知道的关于Buzz服务的事情。对我来说,Explorer应该让您“发现”所公开的服务和方法/功能,而不一定已经知道它们。我很想听听使用这个

  7. ruby - Sinatra + Heroku + Datamapper 使用 dm-sqlite-adapter 部署问题 - 2

    出于某种原因,heroku尝试要求dm-sqlite-adapter,即使它应该在这里使用Postgres。请注意,这发生在我打开任何URL时-而不是在gitpush本身期间。我构建了一个默认的Facebook应用程序。gem文件:source:gemcuttergem"foreman"gem"sinatra"gem"mogli"gem"json"gem"httparty"gem"thin"gem"data_mapper"gem"heroku"group:productiondogem"pg"gem"dm-postgres-adapter"endgroup:development,:t

  8. ruby - Ruby 中字符串运算符 + 和 << 的区别 - 2

    我是Ruby和这个网站的新手。下面两个函数是不同的,一个在函数外修改变量,一个不修改。defm1(x)x我想确保我理解正确-当调用m1时,对str的引用被复制并传递给将其视为x的函数。运算符当调用m2时,对str的引用被复制并传递给将其视为x的函数。运算符+创建一个新字符串,赋值x=x+"4"只是将x重定向到新字符串,而原始str变量保持不变。对吧?谢谢 最佳答案 String#+::str+other_str→new_strConcatenation—ReturnsanewStringcontainingother_strconc

  9. ruby - 使用 Ruby 和 Mechanize 登录网站 - 2

    我需要从站点抓取数据,但它需要我先登录。我一直在使用hpricot成功地抓取其他网站,但我是使用mechanize的新手,我真的对如何使用它感到困惑。我看到这个例子经常被引用:require'rubygems'require'mechanize'a=Mechanize.newa.get('http://rubyforge.org/')do|page|#Clicktheloginlinklogin_page=a.click(page.link_with(:text=>/LogIn/))#Submittheloginformmy_page=login_page.form_with(:act

  10. ruby - rails 3.2.2(或 3.2.1)+ Postgresql 9.1.3 + Ubuntu 11.10 连接错误 - 2

    我正在使用PostgreSQL9.1.3(x86_64-pc-linux-gnu上的PostgreSQL9.1.3,由gcc-4.6.real(Ubuntu/Linaro4.6.1-9ubuntu3)4.6.1,64位编译)和在ubuntu11.10上运行3.2.2或3.2.1。现在,我可以使用以下命令连接PostgreSQLsupostgres输入密码我可以看到postgres=#我将以下详细信息放在我的config/database.yml中并执行“railsdb”,它工作正常。开发:adapter:postgresqlencoding:utf8reconnect:falsedat

随机推荐