Google使用Java Spring安全应用程序登录

时间:2017-08-15 12:39:36

标签: java authentication spring-security google-signin

我正在尝试将Google登录集成到现有的Spring安全应用程序中。目标是拥有一个Google登录按钮,允许用户使用用户名/密码组合登录标准登录。

根据Google提供的指南(https://developers.google.com/identity/sign-in/web/backend-auth),它看起来像我需要做的就是扩展登录表单(目前只有登录名和密码输入字段) 使用额外字段“id_token”并将其提交给服务器。

这是一个很好的安全措施吗?我搜索了网页,我很惊讶我在网上找不到任何类似的实现。

2 个答案:

答案 0 :(得分:2)

以下是我对所需弹簧安全组件的看法:

过滤器:

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.util.Assert;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class GoogleIdAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
    private static final long serialVersionUID = 1L;
    private String tokenParamName = "googleIdToken";

    /**
     * Creates an instance which will authenticate against the supplied
     * {@code AuthenticationManager} and which will ignore failed authentication attempts,
     * allowing the request to proceed down the filter chain.
     *
     * @param authenticationManager     the bean to submit authentication requests to
     * @param defaultFilterProcessesUrl the url to check for auth requests on (e.g. /login/google)
     */
    public GoogleIdAuthenticationFilter(AuthenticationManager authenticationManager, String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
        Assert.notNull(authenticationManager, "authenticationManager cannot be null");
        setAuthenticationManager(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        String token = request.getParameter(tokenParamName);

        if (token == null) {
            return null;
        }

        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Google ID Token Authorization parameter found with value '" + token + "'");
        }

        Object details = this.authenticationDetailsSource.buildDetails(request);

        GoogleIdAuthenticationToken authRequest = new GoogleIdAuthenticationToken(token, details);

        Authentication authResult = getAuthenticationManager().authenticate(authRequest);

        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Authentication success: " + authResult);
        }

        return authResult;
    }

    public String getTokenParamName() {
        return tokenParamName;
    }

    public void setTokenParamName(String tokenParamName) {
        this.tokenParamName = tokenParamName;
    }
}

身份验证提供程序:

import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.apache.ApacheHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import javax.annotation.Resource;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collections;

public class GoogleIdAuthenticationProvider implements AuthenticationProvider {
    private static final Logger logger = LoggerFactory.getLogger(GoogleIdAuthenticationProvider.class);

    private String clientId;

    @Resource
    private UserDetailsService userDetailsService;

    private HttpTransport httpTransport = new ApacheHttpTransport();
    private JsonFactory jsonFactory = new JacksonFactory();

    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        if (!supports(authentication.getClass())) {
            if (logger.isDebugEnabled()) {
                logger.debug(String.format("This authentication provider does not support instances of type %s", authentication.getClass().getName()));
            }
            return null;
        }

        GoogleIdAuthenticationToken googleIdAuthenticationToken = (GoogleIdAuthenticationToken) authentication;

        if (logger.isDebugEnabled())
            logger.debug(String.format("Validating google login with token '%s'", googleIdAuthenticationToken.getCredentials()));


        GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(httpTransport, jsonFactory)
                .setAudience(Collections.singletonList(getClientId()))
                .build();

        GoogleIdToken googleIdToken = null;
        try {
            googleIdToken = verifier.verify((String) googleIdAuthenticationToken.getCredentials());

            if (googleIdToken == null) {
                throw new BadCredentialsException("Unable to verify token");
            }
        } catch (IOException|GeneralSecurityException e) {
            throw new BadCredentialsException("Unable to verify token", e);
        }

        Payload payload = googleIdToken.getPayload();

        // Get profile information from payload
        String email = payload.getEmail();

        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Loading user details for email '%s'", email));
        }
        UserDetails userDetails = null;
        try {
            userDetails = userDetailsService.loadUserByUsername(email);

            if (!userDetails.isAccountNonLocked()) {
                throw new LockedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
            }

            if (!userDetails.isEnabled()) {
                throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
            }

            if (!userDetails.isAccountNonExpired()) {
                throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
            }
        } catch (UsernameNotFoundException e) {
            // provision a new user?
            throw e;
        }

        return new GoogleIdAuthenticationToken((String) googleIdAuthenticationToken.getCredentials(), userDetails.getUsername(), userDetails.getAuthorities(), authentication.getDetails());
    }

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

    public String getClientId() {
        return clientId;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }
}

令牌:

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import java.util.ArrayList;
import java.util.Collection;

public class GoogleIdAuthenticationToken extends AbstractAuthenticationToken {
    private String credentials;
    private Object principal;

    public GoogleIdAuthenticationToken(String token, Object details) {
        super(new ArrayList<>());
        this.credentials = token;
        setDetails(details);
        setAuthenticated(false);
    }

    GoogleIdAuthenticationToken(String token, String principal, Collection<? extends GrantedAuthority> authorities, Object details) {
        super(authorities);
        this.credentials = token;
        this.principal = principal;
        setDetails(details);
        setAuthenticated(true);
    }

    @Override
    public Object getCredentials() {
        return credentials;
    }

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

插入上述内容后,您只需要使用Google在'googleIdToken'(或您配置的任何内容)中返回的令牌发布到“/ login / google”(或您配置的任何内容)。

答案 1 :(得分:0)

所以,正确的答案结果是没有扩展现有的auth过滤器/提供者,而是定义/添加另一个{Token Authentication class + token auth filter + token auth provider(provider is optional of optional)}