Commit d89a470e authored by Maxym Borodenko's avatar Maxym Borodenko Committed by Matija Obreza

Google Sign-in authentication system

parent 5ccc1438
...@@ -76,7 +76,6 @@ ...@@ -76,7 +76,6 @@
<spring.security.oauth2.version>2.3.3.RELEASE</spring.security.oauth2.version> <spring.security.oauth2.version>2.3.3.RELEASE</spring.security.oauth2.version>
<spring-security-jwt>1.0.8.RELEASE</spring-security-jwt> <spring-security-jwt>1.0.8.RELEASE</spring-security-jwt>
<org.springframework.social-version>1.1.4.RELEASE</org.springframework.social-version> <org.springframework.social-version>1.1.4.RELEASE</org.springframework.social-version>
<org.springframework.social-google-version>1.0.0.RELEASE</org.springframework.social-google-version>
<querydsl.version>4.1.4</querydsl.version> <querydsl.version>4.1.4</querydsl.version>
<hibernate.version>4.3.11.Final</hibernate.version> <hibernate.version>4.3.11.Final</hibernate.version>
...@@ -560,23 +559,16 @@ ...@@ -560,23 +559,16 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.social</groupId> <groupId>com.google.api-client</groupId>
<artifactId>spring-social-google</artifactId> <artifactId>google-api-client</artifactId>
<version>${org.springframework.social-google-version}</version> <version>1.27.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.social</groupId> <groupId>org.springframework.social</groupId>
<artifactId>spring-social-web</artifactId> <artifactId>spring-social-web</artifactId>
<version>${org.springframework.social-version}</version> <version>${org.springframework.social-version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-core</artifactId>
<version>${org.springframework.social-version}</version>
</dependency>
<!--Test dependencies --> <!--Test dependencies -->
<dependency> <dependency>
<groupId>org.hamcrest</groupId> <groupId>org.hamcrest</groupId>
......
/** /*
* Copyright 2014 Global Crop Diversity Trust * Copyright 2018 Global Crop Diversity Trust
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -12,28 +12,23 @@ ...@@ -12,28 +12,23 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
**/ */
package org.genesys2.server.component.security; package org.genesys2.server.component.security;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.util.Arrays;
import java.util.ArrayList; import java.util.Collections;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.apache.http.HttpResponse; import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeRequestUrl;
import org.apache.http.NameValuePair; import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
import org.apache.http.client.entity.UrlEncodedFormEntity; import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import org.apache.http.client.methods.HttpPost; import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import org.apache.http.client.utils.URLEncodedUtils; import com.google.api.client.http.javanet.NetHttpTransport;
import org.apache.http.impl.client.CloseableHttpClient; import com.google.api.client.json.jackson2.JacksonFactory;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.genesys.blocks.security.UserException; import org.genesys.blocks.security.UserException;
import org.genesys.blocks.security.model.BasicUser; import org.genesys.blocks.security.model.BasicUser;
import org.genesys2.server.model.impl.User; import org.genesys2.server.model.impl.User;
...@@ -41,6 +36,7 @@ import org.genesys2.server.service.UserService; ...@@ -41,6 +36,7 @@ import org.genesys2.server.service.UserService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
...@@ -48,19 +44,13 @@ import org.springframework.security.core.context.SecurityContextHolder; ...@@ -48,19 +44,13 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.social.google.api.plus.Person;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
@Component @Component
public class GoogleOAuthUtil { public class GoogleOAuthUtil {
private static final Logger LOG = LoggerFactory.getLogger(GoogleOAuthUtil.class); private static final Logger LOG = LoggerFactory.getLogger(GoogleOAuthUtil.class);
public static final String LOCAL_GOOGLEAUTH_PATH = "/google/auth"; public static final String LOCAL_GOOGLEAUTH_PATH = "/google/auth";
private ObjectMapper mapper = new ObjectMapper();
@Value("${base.url}") @Value("${base.url}")
private String baseUrl; private String baseUrl;
...@@ -74,64 +64,34 @@ public class GoogleOAuthUtil { ...@@ -74,64 +64,34 @@ public class GoogleOAuthUtil {
private UserService userService; private UserService userService;
@Autowired @Autowired
@Named("authUserDetailsService") @Qualifier("authUserDetailsService")
private UserDetailsService userDetailsService; private UserDetailsService userDetailsService;
public String exchangeForAccessToken(HttpServletRequest request) throws IOException { public GoogleTokenResponse exchangeForAccessToken(HttpServletRequest request) throws IOException {
final CloseableHttpClient httpclient = HttpClientBuilder.create().build(); // Exchange auth code for access token
return new GoogleAuthorizationCodeTokenRequest(
try { new NetHttpTransport(),
final HttpPost httppost = new HttpPost("https://accounts.google.com/o/oauth2/token"); JacksonFactory.getDefaultInstance(),
"https://www.googleapis.com/oauth2/v4/token",
final List<NameValuePair> params = new ArrayList<>(); googleApiClientId,
params.add(new BasicNameValuePair("code", request.getParameter("code"))); secret,
params.add(new BasicNameValuePair("client_id", googleApiClientId)); request.getParameter("code"),
params.add(new BasicNameValuePair("client_secret", secret)); baseUrl + LOCAL_GOOGLEAUTH_PATH).execute();
params.add(new BasicNameValuePair("redirect_uri", baseUrl + LOCAL_GOOGLEAUTH_PATH));
params.add(new BasicNameValuePair("grant_type", "authorization_code"));
params.add(new BasicNameValuePair("scope", ""));
httppost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
final HttpResponse response = httpclient.execute(httppost);
final BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
final StringBuilder builder = new StringBuilder();
for (String line = null; (line = reader.readLine()) != null;) {
builder.append(line).append("\n");
}
JsonNode json = mapper.readTree(builder.toString());
return json.has("access_token") ? json.get("access_token").textValue() : null;
} finally {
httpclient.close();
}
} }
public String getAuthenticationUrl() { public String getAuthenticationUrl() {
// google.auth.url=https://accounts.google.com/o/oauth2/auth?redirect_uri=http://localhost:8080/g/auth&response_type=code&client_id=12345-hfp8qjfeqaefpitbc707uluuh8vq65k7.apps.googleusercontent.com&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fplus.login+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fplus.me+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile&approval_prompt=auto&access_type=online&include_granted_scopes=true return new GoogleAuthorizationCodeRequestUrl(googleApiClientId, baseUrl + LOCAL_GOOGLEAUTH_PATH,
final List<NameValuePair> parameters = new ArrayList<NameValuePair>(); Arrays.asList("https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"))
parameters.add(new BasicNameValuePair("redirect_uri", baseUrl + LOCAL_GOOGLEAUTH_PATH)); .setResponseTypes(Collections.singletonList("code"))
parameters.add(new BasicNameValuePair("response_type", "code")); .setApprovalPrompt("auto").build();
parameters.add(new BasicNameValuePair("client_id", googleApiClientId));
parameters.add(new BasicNameValuePair("approval_prompt", "auto"));
parameters.add(new BasicNameValuePair("access_type", "online"));
parameters.add(new BasicNameValuePair("include_granted_scopes", "true"));
// Google+
// "https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/plus.me https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile"));
// Only basic:
parameters.add(new BasicNameValuePair("scope", "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email"));
final String query = URLEncodedUtils.format(parameters, "UTF-8");
return "https://accounts.google.com/o/oauth2/auth?" + query;
} }
public Authentication googleAuthentication(Person userInfo) { public Authentication googleAuthentication(GoogleIdToken.Payload tokenPayload) {
try { try {
final UserDetails userDetails = userDetailsService.loadUserByUsername(userInfo.getAccountEmail()); final UserDetails userDetails = userDetailsService.loadUserByUsername(tokenPayload.getEmail());
if (!(userDetails.isEnabled() && userDetails.isAccountNonExpired() && userDetails.isAccountNonLocked() && userDetails.isCredentialsNonExpired())) { if (!(userDetails.isEnabled() && userDetails.isAccountNonExpired() && userDetails.isAccountNonLocked() && userDetails.isCredentialsNonExpired())) {
LOG.warn("Google login canceled: Account currently not available: {}", userInfo.getAccountEmail()); LOG.warn("Google login canceled: Account currently not available: {}", tokenPayload.getEmail());
return null; return null;
} }
...@@ -140,18 +100,15 @@ public class GoogleOAuthUtil { ...@@ -140,18 +100,15 @@ public class GoogleOAuthUtil {
return authentication; return authentication;
} catch (final UsernameNotFoundException e) { } catch (final UsernameNotFoundException e) {
LOG.warn("Authentication with Google+ failed: No such user {}", userInfo.getAccountEmail()); LOG.warn("Authentication with Google failed: No such user {}", tokenPayload.getEmail());
return null; return null;
} }
} }
public User extractUserFromGoogleProfile(Person userInfo) throws UserException { public User extractUserFromGoogleTokenPayload(GoogleIdToken.Payload tokenPayload) throws UserException {
User user = null; User user = null;
try { try {
user = userService.getUserByEmail(userInfo.getAccountEmail()); user = (User) userDetailsService.loadUserByUsername(tokenPayload.getEmail());
if(user == null){
throw new UsernameNotFoundException("User not found");
}
if (user.getAccountType() == BasicUser.AccountType.LOCAL) { if (user.getAccountType() == BasicUser.AccountType.LOCAL) {
// account exists, change to {@link LoginType#GOOGLE} // account exists, change to {@link LoginType#GOOGLE}
LOG.info("Changing account type to LoginType#GOOGLE"); LOG.info("Changing account type to LoginType#GOOGLE");
...@@ -159,7 +116,7 @@ public class GoogleOAuthUtil { ...@@ -159,7 +116,7 @@ public class GoogleOAuthUtil {
} }
} catch (UsernameNotFoundException e) { } catch (UsernameNotFoundException e) {
LOG.info("Username not found, creating new Google account"); LOG.info("Username not found, creating new Google account");
user = userService.createUser(userInfo.getAccountEmail(), userInfo.getDisplayName(),null, BasicUser.AccountType.GOOGLE); user = userService.createUser(tokenPayload.getEmail(), (String) tokenPayload.get("name"),null, BasicUser.AccountType.GOOGLE);
userService.userEmailValidated(UUID.fromString(user.getUuid())); userService.userEmailValidated(UUID.fromString(user.getUuid()));
} }
return user; return user;
......
/** /*
* Copyright 2014 Global Crop Diversity Trust * Copyright 2018 Global Crop Diversity Trust
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -12,12 +12,14 @@ ...@@ -12,12 +12,14 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
**/ */
package org.genesys2.server.mvc; package org.genesys2.server.mvc;
import java.io.IOException; import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
...@@ -25,15 +27,21 @@ import javax.servlet.ServletException; ...@@ -25,15 +27,21 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.genesys.blocks.security.NotUniqueUserException; 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.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import org.genesys.blocks.security.UserException; import org.genesys.blocks.security.UserException;
import org.genesys.blocks.security.service.PasswordPolicy.PasswordPolicyException;
import org.genesys2.server.component.security.GoogleOAuthUtil; import org.genesys2.server.component.security.GoogleOAuthUtil;
import org.genesys2.server.model.impl.User; import org.genesys2.server.model.impl.User;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request; import org.springframework.security.oauth2.provider.OAuth2Request;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
...@@ -41,9 +49,6 @@ import org.springframework.security.web.authentication.AuthenticationFailureHand ...@@ -41,9 +49,6 @@ import org.springframework.security.web.authentication.AuthenticationFailureHand
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.social.google.api.Google;
import org.springframework.social.google.api.impl.GoogleTemplate;
import org.springframework.social.google.api.plus.Person;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
...@@ -63,37 +68,31 @@ public class GoogleSocialController extends BaseController { ...@@ -63,37 +68,31 @@ public class GoogleSocialController extends BaseController {
@Autowired @Autowired
private AuthorizationServerTokenServices tokenServices; private AuthorizationServerTokenServices tokenServices;
@Value("${google.consumerKey}")
private String googleApiClientId;
@RequestMapping("/google/login") @RequestMapping("/google/login")
public void redirectToGoogle(HttpServletResponse response) throws IOException { public void redirectToGoogle(HttpServletResponse response) throws IOException {
response.sendRedirect(googleOAuthUtil.getAuthenticationUrl()); response.sendRedirect(googleOAuthUtil.getAuthenticationUrl());
} }
/** /**
* @throws PasswordPolicyException Shouldn't happen * @throws UsernameNotFoundException
* @throws NotUniqueUserException
*/ */
@RequestMapping(GoogleOAuthUtil.LOCAL_GOOGLEAUTH_PATH) @RequestMapping(GoogleOAuthUtil.LOCAL_GOOGLEAUTH_PATH)
public void googleAuth(Model model, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException, UserException { public void googleAuth(Model model, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
String accessToken = null; GoogleTokenResponse googleTokenResponse = googleOAuthUtil.exchangeForAccessToken(request);
try {
accessToken = googleOAuthUtil.exchangeForAccessToken(request);
} catch (IOException e) {
LOG.warn(e.getMessage(), e);
}
if (accessToken == null) { if (googleTokenResponse == null) {
model.addAttribute("error", true); model.addAttribute("error", true);
authFailureHandler.onAuthenticationFailure(request, response, new BadCredentialsException("Could not authenticate you with Google+")); authFailureHandler.onAuthenticationFailure(request, response, new BadCredentialsException("Could not authenticate you with Google"));
return; return;
} }
final Google google = new GoogleTemplate(accessToken); // Get profile info from ID token
final Person userInfo = google.plusOperations().getGoogleProfile(); GoogleIdToken idToken = googleTokenResponse.parseIdToken();
final Authentication authentication = googleOAuthUtil.googleAuthentication(idToken.getPayload());
googleOAuthUtil.extractUserFromGoogleProfile(userInfo);
final Authentication authentication = googleOAuthUtil.googleAuthentication(userInfo);
// Redirect to URL in session // Redirect to URL in session
authSuccessHandler.onAuthenticationSuccess(request, response, authentication); authSuccessHandler.onAuthenticationSuccess(request, response, authentication);
...@@ -102,27 +101,32 @@ public class GoogleSocialController extends BaseController { ...@@ -102,27 +101,32 @@ public class GoogleSocialController extends BaseController {
/** /**
* Google XHR auth. * Google XHR auth.
* *
* @param accessToken the access token * @param tokenId the user's ID token
* @param clientId the client id * @param clientId the client id
* @return the object * @return the object
*/ */
@RequestMapping(value = "/google/verify-token", method = RequestMethod.GET) @RequestMapping(value = "/google/verify-token", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Object googleAuth(@RequestParam("accessToken") final String accessToken, public Object googleAuth(@RequestParam("tokenId") final String tokenId,
@RequestParam("clientId") final String clientId) throws UserException { @RequestParam("clientId") final String clientId) throws UserException, IOException, GeneralSecurityException {
final Google google = new GoogleTemplate(accessToken);
final Person userInfo = google.plusOperations().getGoogleProfile();
User user = googleOAuthUtil.extractUserFromGoogleProfile(userInfo); GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance())
.setAudience(Collections.singletonList(googleApiClientId)).build();
GoogleIdToken googleIdToken = verifier.verify(tokenId);
final Set<String> scope = new HashSet<>(Arrays.asList("trust", "read", "write")); if (googleIdToken != null) {
final OAuth2Request oAuth2Request = new OAuth2Request(null, clientId, user.getAuthorities(), true, scope, null, null, null, null); Payload payload = googleIdToken.getPayload();
User user = googleOAuthUtil.extractUserFromGoogleTokenPayload(payload);
final UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()); final Set<String> scope = new HashSet<>(Arrays.asList("trust", "read", "write"));
final OAuth2Authentication auth = new OAuth2Authentication(oAuth2Request, authenticationToken); final OAuth2Request oAuth2Request = new OAuth2Request(null, clientId, user.getAuthorities(), true, scope, null, null, null, null);
return tokenServices.createAccessToken(auth); final UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
final OAuth2Authentication auth = new OAuth2Authentication(oAuth2Request, authenticationToken);
return tokenServices.createAccessToken(auth);
} else {
throw new BadCredentialsException("Could not authenticate you with Google");
}
} }
} }
...@@ -41,6 +41,7 @@ login.register-now=Create an account ...@@ -41,6 +41,7 @@ login.register-now=Create an account
logout=Logout logout=Logout
login.forgot-password=Forgot password login.forgot-password=Forgot password
login.with-google-plus=Login with Google+ login.with-google-plus=Login with Google+
login.with-google-sign-in=Login with Google
# Registration # Registration
registration.page.title=Create a user account registration.page.title=Create a user account
......
...@@ -167,7 +167,7 @@ ...@@ -167,7 +167,7 @@
<spring:message code="login.login-button" /> <spring:message code="login.login-button" />
</button> </button>
<span class="or">-</span> <span class="or">-</span>
<a href="<c:url value="/google/login" />" class="btn btn-default google-signin"> <spring:message code="login.with-google-plus" /> <a href="<c:url value="/google/login" />" class="btn btn-default google-signin"> <spring:message code="login.with-google-sign-in" />
</a> <a href="<c:url value="/registration" />" class="btn btn-default"> <spring:message code="login.register-now" /> </a> <a href="<c:url value="/registration" />" class="btn btn-default"> <spring:message code="login.register-now" />
</a> </a>
<!-- CSRF protection --> <!-- CSRF protection -->
......
...@@ -43,7 +43,7 @@ ...@@ -43,7 +43,7 @@
<div class="form-group transparent"> <div class="form-group transparent">
<div class="col-lg-offset-2 col-lg-10 col-md-offset-3 col-md-9 col-sm-offset-3 col-sm-9 col-xs-12"> <div class="col-lg-offset-2 col-lg-10 col-md-offset-3 col-md-9 col-sm-offset-3 col-sm-9 col-xs-12">
<input type="submit" value="<spring:message code="login.login-button" />" class="btn btn-primary" /> <input type="submit" value="<spring:message code="login.login-button" />" class="btn btn-primary" />
<a href="<c:url value="/google/login" />" class="btn btn-default google-signin"><spring:message code="login.with-google-plus"/></a> <a href="<c:url value="/google/login" />" class="btn btn-default google-signin"><spring:message code="login.with-google-sign-in"/></a>
<a href="<c:url value="/registration" />" id="registration" class="btn btn-default"><spring:message code="login.register-now"/></a> <a href="<c:url value="/registration" />" id="registration" class="btn btn-default"><spring:message code="login.register-now"/></a>
<a href="<c:url value="/profile/forgot-password" />" id="forgot-password" class="btn btn-default"><spring:message code="login.forgot-password"/></a> <a href="<c:url value="/profile/forgot-password" />" id="forgot-password" class="btn btn-default"><spring:message code="login.forgot-password"/></a>
</div> </div>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment