How to Build Spring Boot User Registration and OAuth2 Social login with Facebook, Google, LinkedIn, and Github – Part 2

Welcome to the 2nd part of Spring Boot User Registration and OAuth2 social login tutorial series. In this article, You’ll learn how to implement the view layer.

Creating the model classes

LocalUser.java

LocalUser extends User which models core user information retrieved by a UserDetailsService. It also implements OAuth2User and OidcUser.

OAuth2User is a representation of a user Principal that is registered with an OAuth 2.0 Provider

OidcUser is a representation of a user Principal that is registered with an OpenID Connect 1.0 Provider.

OpenID is a protocol for authentication while OAuth is for authorization.

public class LocalUser extends User implements OAuth2User, OidcUser {

	private static final long serialVersionUID = -2845160792248762779L;
	private final OidcIdToken idToken;
	private final OidcUserInfo userInfo;
	private Map<String, Object> attributes;
	private com.javachinna.model.User user;

	public LocalUser(final String userID, final String password, final boolean enabled, final boolean accountNonExpired, final boolean credentialsNonExpired,
			final boolean accountNonLocked, final Collection<? extends GrantedAuthority> authorities, final com.javachinna.model.User user) {
		this(userID, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities, user, null, null);
	}

	public LocalUser(final String userID, final String password, final boolean enabled, final boolean accountNonExpired, final boolean credentialsNonExpired,
			final boolean accountNonLocked, final Collection<? extends GrantedAuthority> authorities, final com.javachinna.model.User user, OidcIdToken idToken,
			OidcUserInfo userInfo) {
		super(userID, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
		this.user = user;
		this.idToken = idToken;
		this.userInfo = userInfo;
	}

	public static LocalUser create(com.javachinna.model.User user, Map<String, Object> attributes, OidcIdToken idToken, OidcUserInfo userInfo) {
		LocalUser localUser = new LocalUser(user.getEmail(), user.getPassword(), user.isEnabled(), true, true, true, GeneralUtils.buildSimpleGrantedAuthorities(user.getRoles()),
				user, idToken, userInfo);
		localUser.setAttributes(attributes);
		return localUser;
	}

	public void setAttributes(Map<String, Object> attributes) {
		this.attributes = attributes;
	}

	@Override
	public String getName() {
		return this.user.getDisplayName();
	}

	@Override
	public Map<String, Object> getAttributes() {
		return this.attributes;
	}

	@Override
	public Map<String, Object> getClaims() {
		return this.attributes;
	}

	@Override
	public OidcUserInfo getUserInfo() {
		return this.userInfo;
	}

	@Override
	public OidcIdToken getIdToken() {
		return this.idToken;
	}

	public com.javachinna.model.User getUser() {
		return user;
	}
}

SocialProvider.java

public enum SocialProvider {
	FACEBOOK("facebook"), TWITTER("twitter"), LINKEDIN("linkedin"), GOOGLE("google"), GITHUB("github"), LOCAL("local");
	private String providerType;
	public String getProviderType() {
		return providerType;
	}
	SocialProvider(final String providerType) {
		this.providerType = providerType;
	}
}

UserRegistrationForm.java

@PasswordMatches
public class UserRegistrationForm {

	private Long userID;

	private String providerUserId;

	@NotEmpty
	private String displayName;

	@NotEmpty
	private String email;

	private SocialProvider socialProvider;

	@Size(min = 8, message = "{Size.userDto.password}")
	private String password;

	@NotEmpty
	private String matchingPassword;

	public UserRegistrationForm() {
	}

	public UserRegistrationForm(String providerUserId, String displayName, String email, String password, SocialProvider socialProvider) {
		this.providerUserId = providerUserId;
		this.displayName = displayName;
		this.email = email;
		this.password = password;
		this.socialProvider = socialProvider;
	}

	public static Builder getBuilder() {
		return new Builder();
	}

	// Getters and setters goes here

	public static class Builder {
		private String providerUserID;
		private String displayName;
		private String email;
		private String password;
		private SocialProvider socialProvider;

		public Builder addProviderUserID(final String userID) {
			this.providerUserID = userID;
			return this;
		}

		public Builder addDisplayName(final String displayName) {
			this.displayName = displayName;
			return this;
		}

		public Builder addEmail(final String email) {
			this.email = email;
			return this;
		}

		public Builder addPassword(final String password) {
			this.password = password;
			return this;
		}

		public Builder addSocialProvider(final SocialProvider socialProvider) {
			this.socialProvider = socialProvider;
			return this;
		}

		public UserRegistrationForm build() {
			return new UserRegistrationForm(providerUserID, displayName, email, password, socialProvider);
		}
	}
}

Creating exception classes

OAuth2AuthenticationProcessingException.java

public class OAuth2AuthenticationProcessingException extends AuthenticationException {
	private static final long serialVersionUID = 3392450042101522832L;

	public OAuth2AuthenticationProcessingException(String msg, Throwable t) {
		super(msg, t);
	}

	public OAuth2AuthenticationProcessingException(String msg) {
		super(msg);
	}
}

UserAlreadyExistAuthenticationException.java

public class UserAlreadyExistAuthenticationException extends AuthenticationException {

	private static final long serialVersionUID = 5570981880007077317L;

	public UserAlreadyExistAuthenticationException(final String msg) {
        super(msg);
    }

}

Creating a message service

MessageService.java

@Component
public class MessageService {
	@Resource
	private MessageSource messageSource;
	private Locale locale = LocaleContextHolder.getLocale();
	public MessageService(Environment environment) {
	}
	public String getMessage(String code) {
		return messageSource.getMessage(code, null, locale);
	}
}

Creating custom handler classes

CustomAuthenticationFailureHandler.java

@Component("authenticationFailureHandler")
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

	@Resource
	private MessageService messageService;

	@Override
	public void onAuthenticationFailure(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException exception)
			throws IOException, ServletException {
		String msg = messageService.getMessage("message.badCredentials");
		if (exception instanceof OAuth2AuthenticationProcessingException) {
			msg = exception.getMessage() == null ? "OAuth error occurred" : exception.getMessage();
		}
		setDefaultFailureUrl("/login?error=" + msg);
		super.onAuthenticationFailure(request, response, exception);
	}
}

CustomLogoutSuccessHandler.java

@Component("logoutSuccessHandler")
public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {

	@Override
	public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
		response.sendRedirect("/login?logout=success");
	}
}

Creating validators

PasswordMatches.java

@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = PasswordMatchesValidator.class)
@Documented
public @interface PasswordMatches {

    String message() default "Passwords don't match";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

PasswordMatchesValidator.java

public class PasswordMatchesValidator implements ConstraintValidator<PasswordMatches, UserRegistrationForm> {

	@Override
	public boolean isValid(final UserRegistrationForm user, final ConstraintValidatorContext context) {
		return user.getPassword().equals(user.getMatchingPassword());
	}
}

Creating views

login.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!doctype html>
<html lang="en">
<head>
<link rel="stylesheet" href="<c:url value='/webjars/bootstrap/css/bootstrap.min.css' />" />
<link rel="stylesheet" href="<c:url value='/static/css/style.css' />" />
<title>Demo</title>
</head>
<body>
	<section>
		<div class="container">
			<div class="row">
				<div class="col-md-6 col-sm-12 mx-auto px-0">
					<div class="card border-none">
						<div class="card-body py-0">
							<div>
								<form class="login_form" name='loginForm' action="<c:url value='/login' />" method='POST' accept-charset=utf-8>
									<c:if test="${not empty msg}">
										<div class="alert alert-${css} alert-dismissible" role="alert">
											<button type="button" class="close" data-dismiss="alert" aria-label="Close">
												<span aria-hidden="true">×</span>
											</button>
											<strong>${msg}</strong>
										</div>
									</c:if>
									<div class="form-group">
										<label for="tags">Sign in</label> <input type="email" class="form-control" id="username" name="j_username" placeholder="Enter email address"
											required="required">
									</div>
									<div class="form-group">
										<input type="password" class="form-control" id="password" name="j_password" value="" placeholder="Enter password" required="required">
									</div>
									<div class="custom-control custom-checkbox">
										<button type="submit" class="btn btn-primary float-right">Sign in</button>
									</div>
								</form>
								<div class="clearfix"></div>
								<p class="content-divider center mt-3">
									<span>or</span>
								</p>
							</div>
							<form action="<c:url value='/auth/google'/>" name="signup" id="signup">
								<p class="social-login text-center">
									Sign in with:
									<a href="<c:url value='/oauth2/authorization/google' />" class="ml-2">
										<img alt="Login with Google" src="<c:url value='/static/img/google.png' />" class="btn-img">
									</a>
									<a href="<c:url value='/oauth2/authorization/facebook' />">
										<img alt="Login with Facebook" src="<c:url value='/static/img/facebook.png' />" class="btn-img">
									</a>
									<a href="<c:url value='/oauth2/authorization/github' />">
										<img alt="Login with Github" src="<c:url value='/static/img/github.png' />" class="btn-img">
									</a>
									<a href="<c:url value='/oauth2/authorization/linkedin' />">
										<img alt="Login with Linkedin" src="<c:url value='/static/img/linkedin.png' />" class="btn-img-linkedin">
									</a>
								</p>
							</form>
							<p class="text-center mb-2">
								Don't have an account yet?
								<a href="<c:url value='/register' />">Sign Up Now</a>
							</p>
						</div>
					</div>
				</div>
				<div class="clearfix"></div>
			</div>
		</div>
	</section>
</body>
</html>

register.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<!doctype html>
<html lang="en">
<head>
<link rel="stylesheet" href="<c:url value='/webjars/bootstrap/css/bootstrap.min.css' />" />
<link rel="stylesheet" href="<c:url value='/static/css/style.css' />" />
<title>Demo</title>
</head>
<body>
	<div class="container-fluid">
		<section>
			<div class="container">
				<div class="row">
					<div class="col-md-6 col-sm-12 mx-auto">
						<div class="card border-none">
							<div>
								<div class="mt-2 text-center">
									<h2>Create Your Account</h2>
								</div>
								<div id="result" style="color: #ff0000"></div>
								<div class="mt-2">
									<c:url value="/register" var="actionUrl" />
									<form:form name="register" id="register" modelAttribute="userRegistrationForm" action="${actionUrl}" method="post">
										<div class="form-group">
											<form:input type="text" class="form-control" id="displayName" path="displayName" value="" placeholder="Enter display name" />
											<form:errors path="displayName" class="control-label has-error" />
										</div>
										<div class="form-group">
											<form:input type="email" class="form-control" id="email" path="email" value="" placeholder="Enter email address" />
											<form:errors path="email" class="control-label has-error" />
										</div>
										<div class="form-group">
											<form:input type="password" class="form-control" id="password" path="password" value="" placeholder="Enter password" />
											<form:errors path="password" class="control-label has-error" />
										</div>
										<div class="form-group">
											<form:input type="password" class="form-control" id="confirmpassword" path="matchingPassword" value="" placeholder="Confirm password" />
											<form:errors path="matchingPassword" class="control-label has-error" />
											<spring:hasBindErrors name="userRegistrationForm">
												<c:if test="${errors.hasGlobalErrors()}">
													<span class="has-error">${errors.getGlobalError().defaultMessage}</span>
												</c:if>
											</spring:hasBindErrors>
										</div>
										<form:input type="hidden" id="socialProvider" path="socialProvider" value="LOCAL" />
										<button type="submit" class="btn btn-primary btn-block" id="doRegister">Create Account</button>
									</form:form>
									<div class="clearfix"></div>
									<p class="content-divider center mt-3">
										<span>or</span>
									</p>
								</div>
								<p class="text-center">
									Already have an account?
									<a href="<c:url value='/login' />">Login Now</a>
								</p>
							</div>
						</div>
					</div>
				</div>
			</div>
		</section>
	</div>
</body>
</html>

home.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ page isELIgnored="false"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%>
<html>
<head>
<link rel="stylesheet" href="<c:url value='/webjars/bootstrap/css/bootstrap.min.css' />" />
<title>home</title>
</head>
<body>
	<div class="container">
		<sec:authentication property="principal.user" var="currentUser" scope="session" />
		<h1>Welcome ${currentUser.displayName} | <a href="<c:url value='/logout'/>">Logout</a></h1>
	</div>
</body>
</html>

style.css

.content-divider.center {
	text-align: center;
}

.content-divider {
	position: relative;
	z-index: 1;
}

.content-divider>span, .content-divider>a {
	background-color: #fff;
	color: #000;
	display: inline-block;
	padding: 2px 12px;
	border-radius: 3px;
	text-transform: uppercase;
	font-weight: 500;
}

.content-divider>span:before, .content-divider>a:before {
	content: "";
	position: absolute;
	top: 50%;
	left: 0;
	height: 1px;
	background-color: #ddd;
	width: 100%;
	z-index: -1;
}

.social-login .btn-img {
	width: 30px;
	height: auto;
	padding: 0.6rem 0;
	margin-right: 15px;
}

.btn-img-linkedin {
	width: auto;
	height: 50px;
	padding: 0.6rem 0;
	margin-right: 10px;
}

.form-group .has-error {
	color: red;
}

messages_en.properties

message.badCredentials=Invalid Credentials
message.regError=An account with this email already exists
message.regSucc=Registered successfully. Please login
NotEmpty=This field is required.
Size.userDto.password=Try one with at least 8 characters.
PasswordMatches.user=Password does not match!
message.logout.success=Logged out successfully

What’s next?

In this article, we have covered the view layer implementaion. In the next article, we’ll learn how to perform social login using spring security.

Read NextSpring boot User Registration and OAuth2 Social login with Facebook, Google, LinkedIn and Github – Part 3

This Post Has 4 Comments

  1. Paaku

    hi from where are you fetching displayname in home page on successful login could you plz explain

    1. Chinna

      When you login to the application, Spring will invoke the LocalUserDetailsService.loadUserByUsername() method which will query the database by email id and return an instance of LocalUser.

      This LocalUser class is the representation of Spring Security’s User principal and it is holding the actual User entity in the “user” instance variable.

      Post successful authentication, Spring will store this LocalUser object in the Spring Security Context.

      In the home.jsp, we are getting the LocalUser as User Principal from Spring Security Context. Hence, principal.user will return the User Entity which has all the user details stored in the user table in db.

      I hope it clarifies your query. Please let me know if you need more clarification.

  2. Joe

    Is there a source code repo on github for this?

    1. Chinna

      Yes. You can find the source code repo link at the end of Part 3.

Leave a Reply