Table of Contents
- Part-2: Authorising user using Spring Social (Google, FaceBook and LinkedIn) and Spring Security
- Introduction
- Step 1 – Adding Maven dependencies
- Step 2 – Registering user on site
- Step 3 – Saving User to Database
- Step 4 – Adding the Spring Security
- Step 6 – Adding UserDetails service, our own implementation
- Step 7 – Setting Security context (auto login)
- Step 8 – Changing the Login controller
- Step 9 – Modifying the Social Providers
- Step 10 – Controller for /secure URL
- Step 11 – Updating the properties file
- Conclusion
Part-2: Authorising user using Spring Social (Google, FaceBook and LinkedIn) and Spring Security
Introduction
In this part, I am going to use spring security to allow only logged in users or authenticated users to navigate to secure pages, any user attempting to go to secure pages will be redirected to the Login page for authentication.
Once the user is authenticated, we will save his details to in-memory DB and then the user can log out and log in again.
Spring Security framework provides both authentication and authorization feature for applications. It also helps in preventing attacks like session fixation, clickjacking, cross-site request forgery, etc and good thing is that it can be customized easily to fit in different use cases.
In this tutorial, we will add spring security with spring social API to register users and then add the log on and log out functionality.
Step 1 – Adding Maven dependencies
The first step to start will be adding maven dependencies for Spring-security and also for thymeleaf-extras-spring security for using spring-security tags for displaying logged in user and roles etc on pages.
Add the following dependencies to the POM.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity4</artifactId> </dependency> |
Step 2 – Registering user on site
When logging on to social networking platforms, we didn’t need any form as we fetch the user details through the API. However, to register a user we will need a simple form to capture their details. We will create a view called registration.html under src/main/resources/templates/registration.html.
This form will have server-side validation and minimal CSS/JS
Source code of Registration page is as below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | <!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <title>Login</title> <meta name="description" content="" /> <meta name="viewport" content="width=device-width" /> <base href="/" /> <link rel="stylesheet" type="text/css" href="/webjars/bootstrap/css/bootstrap.min.css" /> <script type="text/javascript" src="/webjars/jquery/jquery.min.js"></script> <script type="text/javascript" src="/webjars/bootstrap/js/bootstrap.min.js"></script> <link rel="stylesheet" href="/webjars/font-awesome/css/font-awesome.min.css"></link> </head> <body> <div class="container" style="width:80%"> <h1>Registration Page</h1> <br /> <form action="#" th:action="@{/registration}" th:object="${userBean}" method="post" > <div class="form-group" > <label for="email" class="control-label col-sm-2">Email*</label>:: <input type="text" th:field="*{email}" placeholder="Enter email"/> <div style="width:33%" th:if="${#fields.hasErrors('email')}" th:errors="*{email}" class="alert alert-danger">Email Error</div> </div> <div class="form-group"> <label for="firstName" class="control-label col-sm-2">First Name*</label>:: <input type="text" th:field="*{firstName}" /> <div style="width:33%" th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}" class="alert alert-danger">FirstName Error</div> </div> <div class="form-group"> <label for="lastName" class="control-label col-sm-2">Last Name*</label>:: <input type="text" th:field="*{lastName}" /> <div style="width:33%" th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}" class="alert alert-danger">LastName Error</div> </div> <div class="form-group"> <label for="password" class="control-label col-sm-2">Password*</label>:: <input type="text" th:field="*{password}" /> <div style="width:33%" th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="alert alert-danger">Password Error</div> </div> <div class="form-group"> <label for="passwordConfirm" class="control-label col-sm-2">Confirm Password*</label>:: <input type="text" th:field="*{passwordConfirm}" /> <div style="width:33%" th:if="${#fields.hasErrors('passwordConfirm')}" th:errors="*{passwordConfirm}" class="alert alert-danger">Password Error</div> </div> <div class="form-group"> <label for="title" class="control-label col-sm-2">Title</label>:: <select th:field="*{title}"> <option value="Mr" th:text="Mr"></option> <option value="Mrs" th:text="Mrs"></option> </select> <div th:if="${#fields.hasErrors('title')}" th:errors="*{title}">Title Error</div> </div> <div class="form-group"> <label for="country" class="control-label col-sm-2">Country</label>:: <select th:field="*{country}"> <option value="India" th:text="India"></option> <option value="UK" th:text="UK"></option> <option value="US" th:text="US"></option> <option value="Japan" th:text="Japan"></option> </select> <div th:if="${#fields.hasErrors('country')}" th:errors="*{country}" class="alert alert-danger">Country Error</div> </div> <input type="hidden" name="provider" value="registration" /> <div class="form-group"> <button type="submit" class="btn btn-primary">Register</button> </div> </form> </div> </body> </html> |
Modifying the Controller
To serve this Page, we will modify the existing Login controller
1 2 3 4 5 6 | @GetMapping("/registration") public String showRegistration(UserBean userBean) { return "registration"; } |
Modifying the Login Page
Now let’s modify the login page and add the registration link on the home page, also we will add username and password fields so once the user is registered they can also log in.
This HTML fragment will add a username/password field along with an error message, in case of the user not found.
Also, let’s add another code fragment which is to display the Logged In user, the role assigned and a Logout button
1 2 3 4 5 6 7 8 9 10 | <div th:fragment="logout" class="logout" sec:authorize="isAuthenticated()"> Logged in user: <b><span sec:authentication="name"></span></b> | Roles: <b><span sec:authentication="authorities"></span></b> <form action="#" th:action="@{/logout}" method="post"> <button type="submit" class="btn btn-danger btn-sm" <span class="glyphicon glyphicon-log-out"></span> Log out</button> </form> </div> |
We will also need to csrf token to our existing forms for Google, Facebook and LinkedIn
1 2 3 | <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" /> |
This is how the Complete login.html looks like.
Source code of login.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | <!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <title>Login</title> <meta name="description" content="" /> <meta name="viewport" content="width=device-width" /> <base href="/" /> <link rel="stylesheet" type="text/css" href="/webjars/bootstrap/css/bootstrap.min.css" /> <script type="text/javascript" src="/webjars/jquery/jquery.min.js"></script> <script type="text/javascript" src="/webjars/bootstrap/js/bootstrap.min.js"></script> <link rel="stylesheet" href="/webjars/font-awesome/css/font-awesome.min.css"></link> </head> <body> <div class="container"> <div th:fragment="logout" class="logout" sec:authorize="isAuthenticated()"> Logged in user: <b><span sec:authentication="name"></span></b> | Roles: <b><span sec:authentication="authorities"></span></b> <form action="#" th:action="@{/logout}" method="post"> <button type="submit" class="btn btn-danger btn-sm"> <span class="glyphicon glyphicon-log-out"></span> Log out</button> </form> </div> <br/> <h1>Login Using</h1> <form th:action="@{/login}" method="post" style="display: inline"> <label for="username">Email </label> : <input type="text" id="username" name="username" autofocus="autofocus" placeholder="Enter email"/> <br /> <label for="password">Password</label>: <input type="password" id="password" name="password" /> <br /> <p th:if="${loginError}" class="alert alert-danger">Wrong email or password combination</p> <button type="submit" class="btn btn-primary"> <span class="fa fa-user"></span>Login </button> </form> <form action="/connect/google" method="POST" style="display: inline"> <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" /> <input type="hidden" name="scope" value="profile email" /> <button type="submit" class="btn btn-danger"> Google <span class="fa fa-google-plus"></span> </button> </form> <form action="/connect/facebook" method="POST" style="display: inline"> <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" /> <input type="hidden" name="scope" value="public_profile,email" /> <button type="submit" class="btn btn-primary"> Facebook <span class="fa fa-facebook"></span> </button> </form> <form action="/connect/linkedin" method="POST" style="display: inline"> <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" /> <input type="hidden" name="scope" value="r_basicprofile,r_emailaddress" /> <button type="submit" class="btn btn-primary"> LinkedIn <span class="fa fa-linkedin"></span> </button> </form> <br /> <h3> <p class="bg-important"> <a href="/registration" th:href="@{/registration}">Create Account</a> </p> </h3> </div> </body> </html> |
Step 3 – Saving User to Database
Once the user is authenticated, we will need to save their details in a database. So we will use the annotations like @Entity and @Table to create an in-memory database(HSQLDB) and tables if they don’t exist. The DB details can be easily configured in application.properties,also the DB type can also be changed easily.
We will use the hibernate validator annotations @NotNull,@Size to make sure that input fields are validated on the server side before they can be, you can add client-side javascript if that suits.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | package com.login.model; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.Transient; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import org.hibernate.validator.constraints.Email; @Entity(name = "user") @Table(name = "user") public class UserBean implements Serializable{ private static final long serialVersionUID = 1L; @NotNull(message = "Email cannot be empty") @Email(message = "Email Format is not valid") @Size(min = 3, max = 30, message = "Email can not be empty") @Id private String email; @NotNull(message = "First Name cannot be empty") @Size(min = 3, max = 30, message = "First Name cannot be less than 3 characters") private String firstName; @NotNull(message = "Last Name cannot be empty") @Size(min = 3, max = 30, message = "Last Name cannot be less than 3 characters") private String lastName; private String title; private String country; private String password; @Transient private String passwordConfirm; private String provider; private String image; ......getter/setter methods here ... } |
Saving the Bean using JPA Repository
We will be using Spring Data to retrieve user details and save them.
Now we will create a new interface called UserRepository in package com.login.repository this interface will extend the JPARespository<T, ID> where T is UserBean in our Case and ID is the email(primary key).
We will define the abstract method findByEmail by passing email which has been defined as a primary key in UserBean class. To get the UserBean for a particular method all we need to do is inject the UserRepository and call findByemail method()
1 2 3 4 5 6 7 8 9 10 11 12 | package com.login.repository; import org.springframework.data.jpa.repository.JpaRepository; import com.login.model.UserBean; public interface UserRepository extends JpaRepository<UserBean, String> { UserBean findByEmail(String email); } |
The key advantage of using Spring Data is that it makes virtually our code DAO implementation-free, only above interface will be able to save and retrieve user details.
Step 4 – Adding the Spring Security
This is the crux of this tutorial, we will use the spring security to define which URL’s can be accessed only by insecurely and which one can only be accessed by login user.
We will add a class called SecurityConfig to allow users to
- Allow URLs which start with CSS/** and /connect** to be accessed by all users
- Allow /secure/* URL to be accessed only by logged in User
- If any secure/* is accessed by unauthenticated user, redirect him to login page
- In case of unsuccessful authentication, redirect user to /login-error
- Add logout functionality
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | package com.login.security; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; //@formatter:off @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/css/**", "/connect/**").permitAll() .antMatchers("/secure/**") .authenticated() .and() .formLogin() .loginPage("/login") .defaultSuccessUrl("/secure/user") .failureUrl("/login-error") .permitAll() .and() .logout() .permitAll(); } @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); } } // @formatter:on |
As you might have noticed that we have added a bean for BCryptPasswordEncoder, this bean will be used for encrypting the password as hash, which is one of the safest technique to store passwords and decrypting hash is very tough ( Maybe not as tough as mining a BitCoin 🙂 )
Also, there is another method configureglobal(AuthenticationManagerBuilder auth) which basically defines that we have defined our own custom implementation of UserDetailsService, we will talk about it later.
Move user.html to secure/user.html
Since now we want our user.html to be only presented for logged in users we will move user.html from src/main/resources/templates to src/main/resources/templates/secure/user.html
Also, we will add the thymleaf authentication tags to display logged in Username and role.
This is how the complete user.html looks like, make sure it is in src/main/resources/templates/secure/user.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | <!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <title>Login</title> <meta name="description" content="" /> <meta name="viewport" content="width=device-width" /> <meta name="ctx" th:content="${#httpServletRequest.getContextPath()}" /> <base href="/" /> <link rel="stylesheet" type="text/css" href="/webjars/bootstrap/css/bootstrap.min.css" /> <script type="text/javascript" src="/webjars/jquery/jquery.min.js"></script> <script type="text/javascript" src="/webjars/bootstrap/js/bootstrap.min.js"></script> <link rel="stylesheet" href="/webjars/font-awesome/css/font-awesome.min.css"></link> </head> <body> <div class="container"> <h1>Secure Page</h1> <div th:fragment="logout" class="logout" sec:authorize="isAuthenticated()"> Logged in user: <b><span sec:authentication="name"></span></b> | Roles: <b><span sec:authentication="authorities"></span></b> <form action="#" th:action="@{/logout}" method="post"> <button type="submit" class="btn btn-danger btn-sm"> <span class="glyphicon glyphicon-log-out"></span> Log out</button> </form> </div> <br/> <form th:object="${loggedInUser}" method="post"> <div class="row"> <label for="email">Email :</label> <span th:text="*{email}" /> </div> <div class="row"> <label for="firstName">Name:</label> <span th:text="*{firstName}" /> <span th:text="*{lastName}" /> </div> <div class="row"> <label for="image">Image:</label> <img th:attr="src=@{*{image}}" style="width: 150px; height: 150px;"/> </div> </form> <br /> <a href="/login" th:href="@{/login}" class="btn btn-info btn-lg"> <span class="glyphicon glyphicon-chevron-left"></span> Login using other social Providers </a> </div> </body> </html> |
Step 6 – Adding UserDetails service, our own implementation
This is the crucial step as would define here what to do when the user puts in his username/password combination.
If we want user authentication by a DAO class, we need to implement the UserDetailsService interface. This interface has loadUserByUsername() method which is used to validate the user, of course, we need to provide the implementation.
Remeber this method in Security config
1 2 3 4 5 6 | @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); } |
Providing our own implementation of UserDetailsService
We will write the implementation of method loadUserByUsername.In this method, we will do couple of things
- Find Username in DB, if not found throw an exception
- If the user is found, login user and return the User object of type org.springframework.security.core.userdetails.User, spring will automatically update the Security context for us.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | package com.login.security.service.impl; import java.util.HashSet; import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.login.model.UserBean; import com.login.repository.UserRepository; @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserRepository userRepository; @Override @Transactional(readOnly = true) public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { UserBean user = userRepository.findByEmail(email); if (user == null) { throw new UsernameNotFoundException("No user found with email: " + email); } Set<GrantedAuthority> grantedAuthorities = new HashSet<>(); grantedAuthorities.add(new SimpleGrantedAuthority("LOGGED_USER")); return new User(user.getEmail(), user.getPassword(), grantedAuthorities); } } |
Step 7 – Setting Security context (auto login)
Once the user is logged in by social providers or by registering a user we need to update security context by setting the authentication
As per the spring API – “Authentication represents the token for an authentication request or for an authenticated principal once the request has been processed by the AuthenticationManager.authenticate(Authentication) method.
Once the request has been authenticated, the Authentication will usually be stored in a thread-local SecurityContext managed by the SecurityContextHolder by the authentication mechanism which is being used. An explicit authentication can be achieved, without using one of Spring Security’s authentication mechanisms, by creating an Authentication instance and using the code:
SecurityContextHolder.getContext().setAuthentication(authentication)
Which is exactly we are going to do in our method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | package com.login.autologin; import java.util.HashSet; import java.util.Set; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import com.login.model.UserBean; @Service public class Autologin { public void setSecuritycontext(UserBean userForm) { Set<GrantedAuthority> grantedAuthorities = new HashSet<>(); grantedAuthorities.add(new SimpleGrantedAuthority(userForm.getProvider().toUpperCase())); Authentication authentication = new UsernamePasswordAuthenticationToken(userForm.getEmail(), userForm.getPassword(), grantedAuthorities); SecurityContextHolder.getContext().setAuthentication(authentication); } } |
Step 8 – Changing the Login controller
Since now we have most of the things in place, we need to add the controller so when the registration form is submitted,
- We need to save the user details on DB
- Update the security context and set the authentication object
- Redirect user to secure page.
Also, since in our Security config, we defined a “/login-error” path, we will handle that too in the same controller.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | @Autowired private UserRepository userRepository; @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; @Autowired private Autologin autologin; @PostMapping("/registration") public String registerUser(HttpServletResponse httpServletResponse, Model model, @Valid UserBean userBean, BindingResult bindingResult) { if (bindingResult.hasErrors()) { return "registration"; } userBean.setProvider("REGISTRATION"); // Save the details in DB if (StringUtils.isNotEmpty(userBean.getPassword())) { userBean.setPassword(bCryptPasswordEncoder.encode(userBean.getPassword())); } userRepository.save(userBean); autologin.setSecuritycontext(userBean); model.addAttribute("loggedInUser", userBean); return "secure/user"; } /** If we can't find a user/email combination */ @RequestMapping("/login-error") public String loginError(Model model) { model.addAttribute("loginError", true); return "login"; } |
Step 9 – Modifying the Social Providers
In previous post we had created the FacebookProvider, GoogleProvider and LinkedInProvider now we need to make some changes in them so they
- Save the user details on DB
- Update the security context and set the authentication object
- Redirect the user to secure page.
In our class BaseProvider.java, we will add saveUserDetails and autoLoginUser method
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; @Autowired private UserRepository userRepository; @Autowired protected Autologin autologin; protected void saveUserDetails(UserBean userBean) { if (StringUtils.isNotEmpty(userBean.getPassword())) { userBean.setPassword(bCryptPasswordEncoder.encode(userBean.getPassword())); } userRepository.save(userBean); } public void autoLoginUser(UserBean userBean) { autologin.setSecuritycontext(userBean); } |
In our provider classes(FacebookProvider, GoogleProvider and LinkedInProvider) we just need to add the code to
- Save the details in DB
baseProvider.saveUserDetails(userForm);
- Login the User
baseProvider.autoLoginUser(userForm);
- and also return back the secure page
return "secure/user"
You will need to do these changes in all 3 classes(GoogleProvider, FaceBookProvider and LinkedInProvider)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public String getLinkedInUserData(Model model, UserBean userForm) { ConnectionRepository connectionRepository = baseProvider.getConnectionRepository(); if (connectionRepository.findPrimaryConnection(LinkedIn.class) == null) { return REDIRECT_LOGIN; } populateUserDetailsFromLinkedIn(userForm); //Save the details in DB baseProvider.saveUserDetails(userForm); //Login the User baseProvider.autoLoginUser(userForm); model.addAttribute("loggedInUser",userForm); return "secure/user"; } |
Step 10 – Controller for /secure URL
Remeber, we have added in our defaultSuccessUrl("/secure/user") SecurityConfig, well this is the page where we want the user to redirect, once they are authenticated.
Also on user/secure.html, we have defined th:object="${loggedInUser}" this attribute also needs to be initialized once the user is logged in by security config. So the question is how do we initialize the "${loggedInUser}" model attribute. If we don’t initialise it we will get an error.
An easy way to fix this problem is to use annotation called @ModelAttribute @ModelAttribute("loggedInUser"). If this annotation has been used all the RequestMapping method in the controller will be called only after the annotated method.
We can define a method where we will get the Authentication object and then use that object to find the user details.
1 2 3 4 5 6 7 8 | @ModelAttribute("loggedInUser") public void secure(Model model) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); UserBean user = userRepository.findByEmail(auth.getName()); model.addAttribute("loggedInUser", user); } |
Also, we need to define the method for mapping @GetMapping("/secure/user") as shown below. Since we need to initialize model attribute ${loggedInUser} only for @GetMapping("/secure/user") we need to define it in a separate controller class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | package com.login.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import com.login.model.UserBean; import com.login.repository.UserRepository; @Controller public class LoggedInUserController { @Autowired private UserRepository userRepository; @ModelAttribute("loggedInUser") public void secure(Model model) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); UserBean user = userRepository.findByEmail(auth.getName()); model.addAttribute("loggedInUser", user); } @GetMapping("/secure/user") public String securePage() { return "secure/user"; } } |
Step 11 – Updating the properties file
Since we are using HSQLDB in this example and spring boot will configure most of the things for us, we don’t need to do more than defining a couple of properties for initializing the database. Add these properties in application.properties
1 2 3 4 | spring.jpa.hibernate.ddl-auto=create spring.jpa.show-sql=true |
Conclusion
Spring Social and Spring Security are very flexible and easily customizable for different use cases. They both can be used together to provide a seamless and smooth log in experience for users. The above code is just a step in explaining how both the offerings (Spring Social and Spring Security) can be used in unison.
For readers benefit, above code can be cloned from Github
This is how the project structure looks like
This is how the screen looks like after user is logged in, the logged in user and the roles are being displayed on top of screen.
When i running the demo, I choose login with google+, i callback url is localhost:3000, but the forward url
redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fconnect%2Fgoogle, it’s auto add /connect/google, I don’t know why, so google has a error 400, The redirect URI in the request, http://localhost:3000/connect/google, does not match the ones authorized for the OAuth client.
Can you please have a look at this section
http://www.littlebigextra.com/part-1-authorising-user-using-spring-social-google-facebook-and-linkedin-and-spring-security/#Step-6-Fixing-some-issues-before-we-can-run-8211-Changing-the-default-spring-social-redirect-face-flow
Basically, I am overriding the method and returns “redirect:/”+providerId; which will eventually become redirect the code to Login controller “/google”.
Awesome post
Thanks for your kind words..
I have written reply for wrong part.So I am writing again. Thanks for this useful tutorial. When I use with spring boot 2.2.1 version SocialAutoConfigurerAdapter class can not be resolved. How to use this adapter with 2.2.1 and also non auto config.