我正在尝试将Google登录集成到现有的Spring安全应用程序中。目标是拥有一个Google登录按钮,允许用户使用用户名/密码组合登录标准登录。
根据Google提供的指南(https://developers.google.com/identity/sign-in/web/backend-auth),它看起来像我需要做的就是扩展登录表单(目前只有登录名和密码输入字段) 使用额外字段“id_token”并将其提交给服务器。
这是一个很好的安全措施吗?我搜索了网页,我很惊讶我在网上找不到任何类似的实现。
答案 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)}