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



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

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

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

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

滤波器:

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"(或您配置的任何内容(中返回的令牌POST到"/login/google"(或您配置的任何内容(。

在这里,我分享了一些代码,这些代码已用于使用spring进行服务器端Google签名令牌验证。这是一个工作示例:

@Autowired
HttpTransport transport;

private static final JsonFactory jsonFactory = new JacksonFactory();
public void verify(String idTokenString)
{
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(transport, jsonFactory).setAudience(Collections.singletonList(GOOGLE_CLIENT_ID))
.build();
GoogleIdToken idToken = verifier.verify(idTokenString);
IdToken.Payload payload = idToken.getPayload();
Boolean emailVerified = (Boolean) payload.get("email_verified");
if (if (idToken != null) {
String email = (String) payload.get("email");
String fname = (String) payload.get("given_name");
String pictureUrl = (String) payload.get("picture");
String lname = (String) payload.get("family_name");
}

依赖:

<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>1.31.1</version>
</dependency>
<dependency>
<groupId>com.google.http-client</groupId>
<artifactId>google-http-client</artifactId>
<version>1.40.0</version>
</dependency>

因此,正确的答案不是扩展现有的身份验证过滤器/提供程序,而是定义/添加另一个{令牌身份验证类 + 令牌身份验证过滤器 + 令牌身份验证提供程序(提供程序是可选的(}