The post How to enable communication over https between 2 spring boot applications using self signed certificate appeared first on Little Big Extra.
]]>
In this tutorial, we will try to cover how we can enable HTTPS communication over 2 Spring boot applications. HTTPS was developed for exchanging confidential information in a secured manner by making use of encryption using public and private keys in order to prevent unauthorized access.
In the production environment, you will need to install a certificate issued by a certificate authority (CA) which will validate identity and then and issue certificates. CA certificates contain a public key corresponding to a private key. The CA owns the private key and uses it to sign the certificates it issues. Certificates help prevent the use of fake public keys for impersonation.
However, CA-signed certificates might not be available in the lower environments like DEV or for local testing, in this case, you might want to establish that your API’s are able to talk over HTTPS and this is where you can make use of the self-signed certificate.
We will use a self-signed certificate, to generate one we will need OpenSSL installed on our machine. In MacOs and Linux machines it is preinstalled, however, in windows you have to install it. Keytool can also be used to generate the self-signed certificates
openssl req -x509 -nodes -newkey rsa:2048 -keyout private.key -out selfsigned.pem -subj '/C=gb/ST=XX/L=XX/O=XX/OU=XX/CN=localhost/emailAddress=postmaster@example.com' -days 365
Also please note that above command also defines the country, state, location, organization name for simplification only XX has been added and the validity for above certificate is for a year which is controlled by ‘-days 365’. Feel free to change as per your needs.
openssl x509 -in selfsigned.pem -text -noout
If the certificate has been generated successfully, then basically above command should generate something like this, basically listing out the expiry date, the signature algorithm etc.
Certificate: Data: Version: 1 (0x0) Serial Number: 10095699467019317110 (0x8c1b212d0ac96f76) Signature Algorithm: sha256WithRSAEncryption Issuer: C=gb, ST=XX, L=XX, O=XX, OU=XX, CN=loclhost/emailAddress=postmaster@example.com Validity Not Before: Jun 27 10:52:32 2018 GMT Not After : Jun 27 10:52:32 2019 GMT Subject: C=gb, ST=XX, L=XX, O=XX, OU=XX, CN=loclhost/emailAddress=postmaster@example.com Subject Public Key Info:
openssl pkcs12 -export -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -export -in selfsigned.pem -inkey private.key -name selfsigned -out keystore.p12
keytool -importkeystore -srcstoretype PKCS12 -srckeystore keystore.p12 -destkeystore keystore.jks -deststoretype pkcs12
Add the following configuration to your spring boot application.yaml file and copy the above-generated keystore.jks file in src/main/resources folder
server: ssl: key-store-type: PKCS12 key-store: 'classpath:keystore.jks' key-store-password: changeit key-alias: selfsigned
Now restart your application and see if you can see something in the logs ” Tomcat started on port(s): 8080 (https) with context path.. ”
This confirms that application will work only on https and not on http.
Considering the above changes went well and both the Spring boot application has started and run on different ports with HTTPS enabled (server.port: port_number property can be used to configure different port numbers)
To call the other spring Boot application all we need is change the URL to use ‘https’
You can use RestTemplate to make a call
@Autowired RestTemplate restTemplate; void someRandomFunction(){ restTemplate.getForObject("https://localhost:8090/SpringBootApp1", Response.class) }
If you get the below exception
org.springframework.web.client.ResourceAccessException: I/O error on GET request for "https://localhost:8100/customer/session/1b63e4b8-a91b-4742-b70a-8f113271212": sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
If you get SSL Handshake exception
****javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No subject alternative names present at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
The post How to enable communication over https between 2 spring boot applications using self signed certificate appeared first on Little Big Extra.
]]>The post Part-2: Authorising user using Spring Social (Google, Facebook, LinkedIn) and Spring Security appeared first on Little Big Extra.
]]>
In the last part, I had demonstrated how we can use spring-social to authorize user using Facebook, Google and LinkedIn API’s. If you have not read the last part, I would request you to have a look and then come back to this part.
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.
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
<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>
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
<!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>
To serve this Page, we will modify the existing Login controller
@GetMapping("/registration") public String showRegistration(UserBean userBean) { return "registration"; }
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
<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
<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
<!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>
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.
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 ... }
We will be using 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()
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.
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
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.
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
<!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>
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
@Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder()); }
We will write the implementation of method loadUserByUsername.In this method, we will do couple of things
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); } }
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.
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); } }
Since now we have most of the things in place, we need to add the controller so when the registration form is submitted,
Also, since in our Security config, we defined a “/login-error” path, we will handle that too in the same controller.
@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"; }
In previous post we had created the FacebookProvider, GoogleProvider and LinkedInProvider now we need to make some changes in them so they
In our class BaseProvider.java, we will add saveUserDetails and autoLoginUser method
@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
baseProvider.saveUserDetails(userForm);
baseProvider.autoLoginUser(userForm);
return "secure/user"
You will need to do these changes in all 3 classes(GoogleProvider, FaceBookProvider and LinkedInProvider)
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"; }
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.
@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.
@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.
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"; } }
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
spring.jpa.hibernate.ddl-auto=create spring.jpa.show-sql=true
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
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.
The post Part-2: Authorising user using Spring Social (Google, Facebook, LinkedIn) and Spring Security appeared first on Little Big Extra.
]]>The post Part -1 : Authorising user using Spring Social (Google, FaceBook and LinkedIn) and Spring Security appeared first on Little Big Extra.
]]>Social Logins are becoming increasingly popular across web applications, they not only offer good user experience are safe and also saves the user from password fatigue. Imagine losing a customer because they can not log on to the application as they cannot remember a password are not bothered to reset passwords etc. A social login is a kind of single sign-on where you use login information of a social network like Facebook, Twitter, Google+ to log on to a third website, instead of creating a new log-in account specially for that website.
Earlier I have written few blogs about Spring-Social features where I had written about few problems like no support for Google, only one user allowed by spring-social etc and also few exceptions which occur and then how to fix them.I thought of collating all the problems and writing a step by step tutorial explaining in detail, on how to register a user using spring social.
The main aim of this tutorial will be to integrate spring-security and spring-social together and create a web application where users can be registered either by
In this first part of the tutorial, our aim will be to get data from spring-social and then display the details on the page, in the second part we will integrate the spring-security where we will ask the user to enter his details and then store on DB.
Assuming that you know how to create a simple spring boot project, let us move on to add maven-dependencies required to run the project. If not, then you can create a spring-starter project and add thymleaf and web support.
The first step would be to add spring-social maven repositories, following dependencies are required
<dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-core</artifactId> <version>2.0.0.M2</version> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-config</artifactId> <version>2.0.0.M2</version> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-facebook</artifactId> <version>3.0.0.M1</version> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-linkedin</artifactId> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-google</artifactId> <version>1.0.0.RELEASE</version> </dependency>
In case you get the repository not available error, you might have to add the following repository to your pom.
<repositories> <repository> <id>alfresco-public</id> <url>https://artifacts.alfresco.com/nexus/content/groups/public</url> </repository> </repositories>
To make our web pages look nice we will add some Bootstrap CSS, Font-awesome and Jquery to them and also we will use thymleaf for HTML. Read Here if you want to know more in detail.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>font-awesome</artifactId> <version>4.7.0</version> </dependency>
To avoid any mistakes, here is the complete POM.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.login</groupId> <artifactId>SpringLogin</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>SpringLogin</name> <description>Spring Login - Example POC</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.8.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-core</artifactId> <version>2.0.0.M2</version> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-config</artifactId> <version>2.0.0.M2</version> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-facebook</artifactId> <version>3.0.0.M1</version> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-linkedin</artifactId> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-google</artifactId> <version>1.0.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> --> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.1</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>font-awesome</artifactId> <version>4.7.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> </dependency> </dependencies> <repositories> <repository> <id>alfresco-public</id> <url>https://artifacts.alfresco.com/nexus/content/groups/public</url> </repository> </repositories> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
Once the dependencies have been resolved be will create a View and Controller
To start we will create a view which will have 3 buttons each for Google, Facebook and Login. When the user will click on any of them he will be asked for authorization for the selected provider, upon verification his details will be rendered on the page.
Create a file under src/main/resources/templates/login.html with the following content. Please note that this view has 3 different forms each for different provider
<!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"> <h1>Login Using</h1> <form action="/connect/google" method="POST" style="display: inline"> <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" 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" name="scope" value="r_basicprofile,r_emailaddress" /> <button type="submit" class="btn btn-primary"> LinkedIn <span class="fa fa-linkedin"></span> </button> </form> </div> </body> </html>
Let us create a controller class called LoginController in package com.login.controller with a simple method which will map the above page to URL / or /login.
@Controller public class LoginController { @RequestMapping(value = { "/","/login" }) public String login() { return "login"; } }
Now run the above application and at this point, a page like this should appear and of course, it won’t do anything yet.
Once we verify the user via our social provider, we will show the user details on another page. We can store these details in a POJO called UserBean.
Let us define a class called UserBean.java as shown below in package com.login.model
package com.login.model; import java.beans.Transient; import java.io.Serializable; public class UserBean implements Serializable{ private static final long serialVersionUID = 1L; private String firstName; private String lastName; private String email; private String title; private String country; private String password; private String passwordConfirm; private String provider; private String image; public String getEmail() { return email; } public String getImage() { return image; } public void setImage(String image) { this.image = image; } public void setEmail(String email) { this.email = email; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getPasswordConfirm() { return passwordConfirm; } public void setPasswordConfirm(String passwordConfirm) { this.passwordConfirm = passwordConfirm; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getProvider() { return provider; } public void setProvider(String provider) { this.provider = provider; } }
For Facebook, Google and LinkedIn we will create a Provider class which will be used to do authentication and save user details to the UserBean, which can be later displayed on the page. To start off with we will create a BaseProvider which will have a constructor where all the providers will be initialized.
This BaseProvider is created by injecting the Facebook, Google, LinkedIn and ConnectionRepository repository. These objects are a reference to Spring Social’s Facebook, Google, LinkedIn and ConnectionRepository API binding.
package com.login.social.providers; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.social.connect.ConnectionRepository; import org.springframework.social.facebook.api.Facebook; import org.springframework.social.google.api.Google; import org.springframework.social.linkedin.api.LinkedIn; @Configuration @Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) public class BaseProvider { private Facebook facebook; private Google google; private LinkedIn linkedIn; private ConnectionRepository connectionRepository; public BaseProvider(Facebook facebook,Google google, LinkedIn linkedIn, ConnectionRepository connectionRepository) { this.facebook = facebook; this.connectionRepository = connectionRepository; this.google=google; this.linkedIn= linkedIn; } public Facebook getFacebook() { return facebook; } public void setFacebook(Facebook facebook) { this.facebook = facebook; } public ConnectionRepository getConnectionRepository() { return connectionRepository; } public void setConnectionRepository(ConnectionRepository connectionRepository) { this.connectionRepository = connectionRepository; } public Google getGoogle() { return google; } public void setGoogle(Google google) { this.google = google; } public LinkedIn getLinkedIn() { return linkedIn; } public void setLinkedIn(LinkedIn linkedIn) { this.linkedIn = linkedIn; } }
Now let us create provider class for each provider
We will use the below Facebook provider to authorize the user and then access Facebook Data. We will save the fetched user data into our UserBean.
package com.login.social.providers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.social.connect.ConnectionRepository; import org.springframework.social.facebook.api.Facebook; import org.springframework.social.facebook.api.User; import org.springframework.stereotype.Service; import org.springframework.ui.Model; import com.login.model.UserBean; @Service public class FacebookProvider { private static final String FACEBOOK = "facebook"; private static final String REDIRECT_LOGIN = "redirect:/login"; @Autowired BaseProvider baseProvider ; public String getFacebookUserData(Model model, UserBean userForm) { ConnectionRepository connectionRepository = baseProvider.getConnectionRepository(); if (connectionRepository.findPrimaryConnection(Facebook.class) == null) { return REDIRECT_LOGIN; } populateUserDetailsFromFacebook(userForm); model.addAttribute("loggedInUser",userForm); return "user"; } protected void populateUserDetailsFromFacebook(UserBean userForm) { Facebook facebook = baseProvider.getFacebook(); User user = facebook.userOperations().getUserProfile(); userForm.setEmail(user.getEmail()); userForm.setFirstName(user.getFirstName()); userForm.setLastName(user.getLastName()); userForm.setImage(user.getCover().getSource()); userForm.setProvider(FACEBOOK); } }
We will use the below google provider to authorize the user and then access Google Data. We will save the fetched user data into our UserBean.
package com.login.social.providers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.social.connect.ConnectionRepository; import org.springframework.social.google.api.Google; import org.springframework.social.google.api.plus.Person; import org.springframework.stereotype.Service; import org.springframework.ui.Model; import com.login.model.UserBean; @Service public class GoogleProvider { private static final String REDIRECT_CONNECT_GOOGLE = "redirect:/login"; private static final String GOOGLE = "google"; @Autowired BaseProvider socialLoginBean ; public String getGoogleUserData(Model model, UserBean userForm) { ConnectionRepository connectionRepository = socialLoginBean.getConnectionRepository(); if (connectionRepository.findPrimaryConnection(Google.class) == null) { return REDIRECT_CONNECT_GOOGLE; } populateUserDetailsFromGoogle(userForm); model.addAttribute("loggedInUser",userForm); return "user"; } protected void populateUserDetailsFromGoogle(UserBean userform) { Google google = socialLoginBean.getGoogle(); Person googleUser = google.plusOperations().getGoogleProfile(); userform.setEmail(googleUser.getAccountEmail()); userform.setFirstName(googleUser.getGivenName()); userform.setLastName(googleUser.getFamilyName()); userform.setImage(googleUser.getImageUrl()); userform.setProvider(GOOGLE); } }
We will use the below LinkedIn provider to authorize the user and then accessLinkedIn Data. We will save the fetched user data into our UserBean.
package com.login.social.providers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.social.connect.ConnectionRepository; import org.springframework.social.linkedin.api.LinkedIn; import org.springframework.social.linkedin.api.LinkedInProfileFull; import org.springframework.stereotype.Service; import org.springframework.ui.Model; import com.login.model.UserBean; @Service public class LinkedInProvider { private static final String LINKED_IN = "linkedIn"; private static final String REDIRECT_LOGIN = "redirect:/login"; @Autowired BaseProvider socialLoginBean ; public String getLinkedInUserData(Model model, UserBean userForm) { ConnectionRepository connectionRepository = socialLoginBean.getConnectionRepository(); if (connectionRepository.findPrimaryConnection(LinkedIn.class) == null) { return REDIRECT_LOGIN; } populateUserDetailsFromLinkedIn(userForm); model.addAttribute("loggedInUser",userForm); return "user"; } private void populateUserDetailsFromLinkedIn(UserBean userForm) { LinkedIn linkedIn = socialLoginBean.getLinkedIn(); LinkedInProfileFull linkedInUser = linkedIn.profileOperations().getUserProfileFull(); userForm.setEmail(linkedInUser.getEmailAddress()); userForm.setFirstName(linkedInUser.getFirstName()); userForm.setLastName(linkedInUser.getLastName()); userForm.setImage(linkedInUser.getProfilePictureUrl()); userForm.setProvider(LINKED_IN); } }
If you would have noticed by now, spring social doesn’t support Google so to enable autoconfiguration of spring-social-google we need to
Spring social do have a spring-social-google project but it doesn’t have autoconfiguration for Google. So google authorization doesn’t work with spring boot autoconfigure straight away.
To enable autoconfigure of spring-social-google we need to
FacebookAutoConfiguration class you should be able to find it either in org.springframework.boot.autoconfigure.social package in spring-autoconfigure.jar does the autoconfiguration for Facebook, the cheat sheet or the trick would be to copy this File and replace Facebook with Google to enable GoogleAutoConfiguration
Alternatively, copy the below class and put in some package, make sure it is available in classpath(src/main/java or inside another source folder)
package com.login.config.google; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.social.SocialAutoConfigurerAdapter; import org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.ScopedProxyMode; import org.springframework.social.config.annotation.EnableSocial; import org.springframework.social.config.annotation.SocialConfigurerAdapter; import org.springframework.social.connect.Connection; import org.springframework.social.connect.ConnectionFactory; import org.springframework.social.connect.ConnectionRepository; import org.springframework.social.connect.web.GenericConnectionStatusView; import org.springframework.social.google.api.Google; import org.springframework.social.google.connect.GoogleConnectionFactory; @Configuration @ConditionalOnClass({ SocialConfigurerAdapter.class, GoogleConnectionFactory.class }) @ConditionalOnProperty(prefix = "spring.social.google", name = "app-id") @AutoConfigureBefore(SocialWebAutoConfiguration.class) @AutoConfigureAfter(WebMvcAutoConfiguration.class) public class GoogleAutoConfiguration { @Configuration @EnableSocial @EnableConfigurationProperties(GoogleProperties.class) @ConditionalOnWebApplication protected static class GoogleConfigurerAdapter extends SocialAutoConfigurerAdapter { private final GoogleProperties properties; protected GoogleConfigurerAdapter(GoogleProperties properties) { this.properties = properties; } @Bean @ConditionalOnMissingBean(Google.class) @Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES) public Google google(ConnectionRepository repository) { Connection<Google> connection = repository.findPrimaryConnection(Google.class); return connection != null ? connection.getApi() : null; } @Bean(name = { "connect/googleConnect", "connect/googleConnected" }) @ConditionalOnProperty(prefix = "spring.social", name = "auto-connection-views") public GenericConnectionStatusView googleConnectView() { return new GenericConnectionStatusView("google", "Google"); } @Override protected ConnectionFactory<?> createConnectionFactory() { return new GoogleConnectionFactory(this.properties.getAppId(), this.properties.getAppSecret()); } } }
In the same package add the below class, this is needed so when we add secret keys in properties file for google. We wont see any error.
package com.login.config.google; import org.springframework.boot.autoconfigure.social.SocialProperties; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "spring.social.google") public class GoogleProperties extends SocialProperties{ }
Spring Social is quick and easy to start but it needs some configuring before it can be used for production. The default flow for
The Views are predefined so the user flow is login.html -> connect/facebookConnected.html by default, irrespective of what our submit URL is, the facebook will override the flow and redirect you to connect/facebookConnected.html.
Read Here: How to change the default spring social redirect page flow
We will add a ChangeDefaultFlowController which will override the connectedView method and redirect the flow to “/facebook”, “/google” or “/LinkedIn”
package com.login.controller; import org.springframework.social.connect.ConnectionFactoryLocator; import org.springframework.social.connect.ConnectionRepository; import org.springframework.social.connect.web.ConnectController; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/connect") public class ChangeDefaultFlowController extends ConnectController { public ChangeDefaultFlowController(ConnectionFactoryLocator connectionFactoryLocator, ConnectionRepository connectionRepository) { super(connectionFactoryLocator, connectionRepository); } @Override protected String connectedView(String providerId) { return "redirect:/"+providerId; } }
If you test the Spring Facebook example, accessing FB data you will realise that it only supports one user. So when the first users log in to Facebook, only his details will be shared across all new sessions/users and they won’t be asked for any kind of authentication. One of the forum says that this example is supposed to demonstrate what can be done and is not intended for production use.
Read Here: Spring Social Facebook Authentication Example for multiple users
To fix this problem you need to override a method which always returns a string called “anonymous”.The solution is to override the “anonymous�? as the UserId for each new user/session. So for each session, we can simply return a SessionID, however, it may not be unique enough to identify users, especially if it’sh being cached or stored somewhere in a connection database.
Using a Universally Unique Identifier(UUID) would be a safer bet.So we will store a new UUID in session so it persists between different requests but is alive only till the session is valid. See the below method which does the trick.
package com.login.identifier; import java.util.UUID; import org.springframework.context.annotation.Configuration; import org.springframework.social.UserIdSource; import org.springframework.social.config.annotation.SocialConfigurerAdapter; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; @Configuration public class SessionIdentifier extends SocialConfigurerAdapter { @Override public UserIdSource getUserIdSource() { return new SessionIdUserIdSource(); } private static final class SessionIdUserIdSource implements UserIdSource { @Override public String getUserId() { RequestAttributes request = RequestContextHolder.currentRequestAttributes(); String uuid = (String) request.getAttribute("_socialUserUUID", RequestAttributes.SCOPE_SESSION); if (uuid == null) { uuid = UUID.randomUUID().toString(); request.setAttribute("_socialUserUUID", uuid, RequestAttributes.SCOPE_SESSION); } return uuid; } } }
Now since we have added all the fixes, its time to update the controller so our view can submit the forms to Controller
package com.login.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.login.model.UserBean; import com.login.social.providers.FacebookProvider; import com.login.social.providers.GoogleProvider; import com.login.social.providers.LinkedInProvider; @Controller public class LoginController { @Autowired FacebookProvider facebookProvider; @Autowired GoogleProvider googleProvider; @Autowired LinkedInProvider linkedInProvider; @RequestMapping(value = "/facebook", method = RequestMethod.GET) public String loginToFacebook(Model model) { return facebookProvider.getFacebookUserData(model, new UserBean()); } @RequestMapping(value = "/google", method = RequestMethod.GET) public String loginToGoogle(Model model) { return googleProvider.getGoogleUserData(model, new UserBean()); } @RequestMapping(value = "/linkedin", method = RequestMethod.GET) public String helloFacebook(Model model) { return linkedInProvider.getLinkedInUserData(model, new UserBean()); } @RequestMapping(value = { "/","/login" }) public String login() { return "login"; } }
Assuming that you know how to register the API and public/private keys on Google/FB and LinkedIn for this example purpose you can use the below properties file.
Also, notice that server starts on port 3000(don’t change that, since the URL registered with these keys is localhost:3000
spring.social.facebook.appId=384261248599251 spring.social.facebook.appSecret=fd7fa1c5f5a267f463263a0ce7ff2025 spring.social.linkedin.app-id=771mrzk94hye1w spring.social.linkedin.app-secret=iIJFgBf9lCb18zYe spring.social.google.appId=12894100090-tqso3lih5o42isneort886la2pesafmp.apps.googleusercontent.com spring.social.google.appSecret=9xfU16efvxQ-BTMsXT9wOLpw server.port:3000
This is how the project structure will look like
To show the authenticated user on the page, we will display the UserBean using thymleaf. Add the below html page under src/main/resources/static/ as user.html
<!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"> <br /><br /><br /> <form th:object="${loggedInUser}" method="post"> <div class="row"> <label for="username">Username:</label> <span th:text="${loggedInUser.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">Name:</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>
Now run the Project and access the application on http://localhost:3000/login.
You should be greeted by an authentication popup depending upon your provider selected and then you should be redirected to the page where your username, name, profile image will be displayed as below.
Spring Social API helps us in implementing an authentication mechanism with social providers. It is easy to use and we have seen how to configure it to tailor it to our needs.
In the next part we will use spring-security to register users and then only allow logged in users to navigate to secure pages. The link has been provided below for second part.
The post Part -1 : Authorising user using Spring Social (Google, FaceBook and LinkedIn) and Spring Security appeared first on Little Big Extra.
]]>The post How to use Spring Profiles with Docker Containers appeared first on Little Big Extra.
]]>
Spring Profiles are an effective way of implementing environment independent code. The properties file or @Beans can be selected dynamically at run time based on the profile injected.
Assuming that you are quite familiar with the spring profiles and looking for injecting profiles in a Docker environment. There are couple of ways of doing it namely
In this tutorial, I will try to capture all these 3 scenarios.
Read Here: How to create Docker image of Standalone Spring MVC project
From command prompt of your system, any spring boot application can be run with “java -jar” command.The profiles need to be passed as an argument like this “-Dspring.profiles.active=dev“. For Spring MVC applications other 2 below methods will work fine.
java -Djava.security.egd=file:/dev/./urandom -Dspring.profiles.active=dev -jar rest-api.jar
Similarly, when using dockerfile we need to pass the profile as an argument, have a look at one of the Dockerfile for creating a spring boot docker image
Below an example on spring boot project dockerfile
FROM java:8 ADD target/my-api.jar rest-api.jar RUN bash -c 'touch /pegasus.jar' ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom","-Dspring.profiles.active=dev","-jar","/rest-api.jar"]
Pay attention to the last line ENTRYPOINT, in this line we are passing the java command to execute the jar file and all arguments have been passed as comma separated values. “-Dspring.profiles.active=dev” is where we are passing the dev profile, you can replace dev with the profile name you want.
You can also pass spring profile as an environment variable while using docker run command using the -e flag. The option -e “SPRING_PROFILES_ACTIVE=dev” will inject the dev profile to the Docker container.
docker run -d -p 8080:8080 -e "SPRING_PROFILES_ACTIVE=dev" --name rest-api dockerImage:latest
If you are on DockerSwarm or using compose file for deploying docker images the Spring profile can be passed using the environment: tag in a docker-compose file, as shown below
version: "3" services: rest-api: image: rest-api:0.0.1 ports: - "8080:8080" environment: - "SPRING_PROFILES_ACTIVE=dev"
The post How to use Spring Profiles with Docker Containers appeared first on Little Big Extra.
]]>The post Part 3 :Authorising user using Spring Social (Google, Facebook, LinkedIn) and Spring Security appeared first on Little Big Extra.
]]>In Part-1, We discussed how to authenticate with social providers like Facebook, Google, and LinkedIn. Along with that, we changed the default page flow for spring social, enabled it to be used by multiple users and provided support for Google.
In Part-2, We used spring-security to allow only logged in users to see secure pages, also we added registration page to register users.
Well if you have read and run the application up to Part-2 then let me tell you that there is a bug in the code. Also, there might be a case where social network providers may not send all the required data.
Create an account using registration and then log out, log in again now using social provider(same email id). Now if you try to log in using the email/password combination, you will receive an error.
In Simple terms to replicate the above bug
It won’t work because when we logged in through social provider it basically overwrote the password with the blank.
All social providers send back the data differently and its very possible they may not send all the fields which you want for e.g Facebook may not return country, while Google may not return Title.
We will do a couple of things in this tutorial the first being
We will add a method in UserRepository Interface called saveWithoutPassword and in this method, we will save all fields except the password.
This way we will make sure that the passwords saved are not overwritten.
@Modifying @Transactional @Query("UPDATE user SET firstName = :firstName, lastName = :lastName, country = :country, image = :image " + " WHERE email = :email") void saveWithoutPassword(@Param("firstName") String firstname, @Param("lastName") String lastname, @Param("country") String country, @Param("image") String image, @Param("email") String email);
In Base Provider class we will check if the password is empty, which indicates the user might have logged in from the Social provider so save all details except passwords by calling the saveWithoutPassword method.
protected void saveUserDetails(UserBean userBean) { if (StringUtils.isNotEmpty(userBean.getPassword())) { userBean.setPassword(bCryptPasswordEncoder.encode(userBean.getPassword())); } userRepository.saveWithoutPassword(userBean.getFirstName(), userBean.getLastName(), userBean.getCountry(), userBean.getImage(), userBean.getEmail()); }
Every Social Provider has some mandatory and not mandatory fields, which means they might not be capturing all the information. Also, some social providers may not provide all the information, which means that we should check if all information we need from our end is present and if absent ask the user to fill the missing information.
In BaseProvider class, we will add the method to check if all mandatory fields are not null and not blank.
protected boolean isAllInformationAvailable(UserBean userBean) { return StringUtils.isNotEmpty(userBean.getEmail()) && StringUtils.isNotEmpty(userBean.getFirstName()) && StringUtils.isNotEmpty(userBean.getLastName()) && StringUtils.isNotEmpty(userBean.getTitle()) && StringUtils.isNotEmpty(userBean.getCountry()); }
and add the below condition in the method before saving, where we check if any information is missing then redirect the user to “incompleteInfo” page.
//Check if all Info has been collected if(!baseProvider.isAllInformationAvailable(userBean)) { model.addAttribute("userBean", userBean); return "incompleteInfo"; }
the complete method will look like this, you will need to do same for all social providers(Facebook, Google, and LinkedIn). Below is an example of class FaceBookProvider method.
public String getFacebookUserData(Model model, UserBean userBean) { ConnectionRepository connectionRepository = baseProvider.getConnectionRepository(); if (connectionRepository.findPrimaryConnection(Facebook.class) == null) { return REDIRECT_LOGIN; } //Populate the Bean populateUserDetailsFromFacebook(userBean); //Check if all Info has been collected if(!baseProvider.isAllInformationAvailable(userBean)) { model.addAttribute("userBean", userBean); return "incompleteInfo"; } //Save the details in DB baseProvider.saveUserDetails(userBean); //Login the User baseProvider.autoLoginUser(userBean); model.addAttribute("loggedInUser",userBean); return "secure/user"; }
Add the incompleteInfo.html in src/main/resources/templates folder. Basically, in this HTML file using thymleaf constructs, we will check which fields of Userbean has not been populated and render the HTML fields where they are null.
<!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"> <h2>Sorry, but we could not find all the information so please fill the following fields</h2> <form th:action="@{/registration}" th:object="${userBean}" method="post"> <table> <tr th:unless="${userBean.email}"> <td>Email:</td> <td><input type="text" th:field="*{email}" /></td> <td th:if="${#fields.hasErrors('email')}" th:errors="*{email}">Name Error</td> </tr> <tr th:if="${userBean.email}"> <input type="hidden" th:field="*{email}" /> </tr> <tr th:unless="${userBean.title}"> <td>Title:</td> <td><select th:field="*{title}"> <option value="Mr" th:text="Mr"></option> <option value="Mrs" th:text="Mrs"></option> </select></td> <td th:if="${#fields.hasErrors('title')}" th:errors="*{title}">Title Error</td> </tr> <tr th:if="${userBean.title}"> <input type="hidden" th:field="*{title}" /> </tr> <tr th:unless="${userBean.firstName}"> <td>Email:</td> <td><input type="text" th:field="*{firstName}" /></td> <td th:if="${#fields.hasErrors('firstName')}" th:errors="*{email}">Name Error</td> </tr> <tr th:if="${userBean.firstName}"> <input type="hidden" th:field="*{firstName}" /> </tr> <tr th:unless="${userBean.lastName}"> <td>Email:</td> <td><input type="text" th:field="*{lastName}" /></td> <td th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}">Name Error</td> </tr> <tr th:if="${userBean.lastName}"> <input type="hidden" th:field="*{lastName}" /> </tr> <tr th:unless="${userBean.country}"> <td>Country:</td> <td><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="Iraq" th:text="Iraq"></option> </select></td> <td th:if="${#fields.hasErrors('country')}" th:errors="*{country}">Country Error</td> </tr> <tr th:if="${userBean.country}"> <input type="hidden" th:field="*{country}" /> </tr> <input type="hidden" th:field="*{password}" /> <input type="hidden" th:field="*{passwordConfirm}" /> <tr> <td><button type="submit">Submit</button></td> </tr> </table> </form> </div> </body> </html>
That’s all, we have successfully created a login application which integrates with the different social providers and also allows us to register new users.
The above code can be .
The post Part 3 :Authorising user using Spring Social (Google, Facebook, LinkedIn) and Spring Security appeared first on Little Big Extra.
]]>The post How to consume REST based web service in Spring BOOT appeared first on Little Big Extra.
]]>In my last tutorial I wrote about Consuming a secure SOAP based web service in Spring Boot application, In this tutorial, I will talk about consuming a simple unsecured REST service in Spring Boot
Consuming REST service is very simple and less ad-hoc than SOAP service
RestTemplate is spring’s central class for synchronous client side HTTP access.It enforces REST principles and simplifies communication by handlings HTTP connections leaving application code to provide URLs and extract results.
In our code, we will create a bean where we will instantiate a new RestTemplate
@Bean public RestTemplate rest() { return new RestTemplate(); }
Now we have rest template instance we can use the RestTemplate methods to call web service
You can use Rest Template getForObject or getForEntity methods to make an HTTP GET call. Both of these operations need a URL and the ResponseObject class.
A simple example would be like below.
@GetMapping("/availableOperations") String getAvailableOperations() { return restTemplate.getForObject(allAvailableOperations, String.class); }
Consuming a service by POST means that we will be sending some information over HTTP to the requested service and that service based on will request will process things at its end like updating a DB, recording a transaction or something similar and will give us the result back.
In Post also we can use Response Template postForObject and postForEntity Method to send a request
In below example, we are using an HTTP GET @GetMapping(“/Availability”) method which can be directly called by a browser using a URL like http://localhost:8080/Availability. In this method, we will call the postForObject method and pass the endpoint URL of the service, the request object it needs and the Response type we expect.
@GetMapping("/availability") String getURLAvailability() { return restTemplate.postForObject(url, requestObject(), String.class); }
Method post for Entity can be used exactly as shown above, however, the return type is ResponseEntity which represents entire HTTP Response, it has HTTP response like 200,404 etc and response body.
In the below method we have to use a REST CLIENT ( like Chrome plugins Postman or AdvancedRestClient) and directly post the JSON request.
@RequestBodywill automatically map the JSON object to Request object, there is no need to map any elements or create a new request object.
@PostMapping("/availability") public String postlogdingAvailability(@RequestBody Availability availabilityRequest) { ResponseEntity response = restTemplate.postForEntity(url, availabilityRequest, String.class); return response.getBody(); }
Here is how the complete code looks like
@RestController @RequestMapping("/") public class ServiceController { @Autowired RestTemplate restTemplate; @Value("${operations.restURL}") String serviceURL; //Consuming a service by GET method @GetMapping("/availableOperations") String getAvailableOperations() { return restTemplate.getForObject(serviceURL, String.class); } /** Consuming a service by postForObject method, this method is exposed as a get operation if user doesn't post a request object we will create a new request and post it to the URL/service endpoint */ @GetMapping("/availability") String getAvailability() { return restTemplate.postForObject(serviceURL, createAvailabilityRequest(), String.class); } /** Consuming a service by postForEntity method, this method is exposed as a post operation if user post a request object(JSON) it will be automatically mapped to Request parameter. */ @PostMapping("/availability") public String postAvailability(@RequestBody Availability availabilityRequest) { ResponseEntity<String> response = restTemplate.postForEntity(serviceURL, availabilityRequest, String.class); return response.getBody(); } private Object createAvailabilityRequest() { AddOnAvailability addOnAvailability = new AddOnAvailability(); addOnAvailability.setCheckInDate("09/14/2023"); addOnAvailability.setCheckOutDate("09/16/2023"); addOnAvailability.setItemCode(""); addOnAvailability.setAddOnCategory("10"); return addOnAvailability; } @Bean public RestTemplate rest() { return new RestTemplate(); } }
operations: serviceURL: https://localhost:8080/addonavailability # This is the rest service end point which needs to be consumed.
Following dependencies were enough for above example to work
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> </dependencies>
The post How to consume REST based web service in Spring BOOT appeared first on Little Big Extra.
]]>The post How to consume a secure SOAP Web service in Spring Boot Application appeared first on Little Big Extra.
]]>Consuming a SOAP based web service is one of the common use cases a developer will come across. There are different implementations like JAX-WS, Axis1/2 and CXF which helps us in calling the web services easily.
While the JAX-WS is the basic implementation built into JDK library for any complex stuff like WS-Security etc we can use Axis or CXF. Apache CXF is JAX-Ws compliant and supports exposing REST as well as SOAP.
For below tutorial, I am going to use CXF implementation
To consume a secure web service we need to follow things in nutshell
To make a call to a secure web service we need to download the associated CXF jars which will be used later.
<dependencies> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-ws-policy</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-tools-common</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-ws-security</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> </dependencies>
Apache CXF has CXF-code gen-plugin which can be used to generate Java Classes from WSDL.It can be configured in different ways I have configured it below to generate all the classes in src/main/generated folder under the package com.example.generated
Here is how the plugin looks like
<plugin> <groupId>org.apache.cxf</groupId> <artifactId>cxf-codegen-plugin</artifactId> <version>${cxf.version}</version> <executions> <execution> <id>generate-sources</id> <phase>generate-sources</phase> <configuration> <additionalJvmArgs> -Djavax.xml.accessExternalDTD=all -Djavax.xml.accessExternalSchema=all </additionalJvmArgs> <sourceRoot>${basedir}/src/main/generated</sourceRoot> <wsdlOptions> <wsdlOption> <extraargs> <extraarg>-verbose</extraarg> <extraarg>-p</extraarg> <extraarg>com.example.generated</extraarg> <extraarg>-exsh</extraarg> <extraarg>true</extraarg> </extraargs> <wsdl>${basedir}/src/main/resources/wsdl/MyWSDL.wsdl</wsdl> </wsdlOption> </wsdlOptions> </configuration> <goals> <goal>wsdl2java</goal> </goals> </execution> </executions> </plugin>
Since in previous step the artifacts were generated under src/main/generated folder we need to make the generated folder as a source folder so all classes will be available and code can be compiled.
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>build-helper-maven-plugin</artifactId> <version>3.0.0</version> <executions> <execution> <id>add-source</id> <phase>generate-sources</phase> <goals> <goal>add-source</goal> </goals> <configuration> <sources> <source>${basedir}/src/main/generated</source> </sources> </configuration> </execution> </executions> </plugin>
POM should have this plugin to be an executable spring boot jar.
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin>
Here is how the complete POM looks like, By running mvn generate-sources, CXF will generate artifacts in the defined folder in our case (src/main/generated)
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>sample-api</artifactId> <groupId>com.example</groupId> <name>MY API</name> <description>Example API </description> <version>0.0.1-SNAPSHOT</version> <properties> <main.basedir>${basedir}/../..</main.basedir> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <cxf.version>3.1.10</cxf.version> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.3.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-ws-policy</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-tools-common</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-ws-security</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.cxf</groupId> <artifactId>cxf-codegen-plugin</artifactId> <version>${cxf.version}</version> <executions> <execution> <id>generate-sources</id> <phase>generate-sources</phase> <configuration> <additionalJvmArgs> -Djavax.xml.accessExternalDTD=all -Djavax.xml.accessExternalSchema=all </additionalJvmArgs> <sourceRoot>${basedir}/src/main/generated</sourceRoot> <wsdlOptions> <wsdlOption> <extraargs> <extraarg>-verbose</extraarg> <extraarg>-p</extraarg> <extraarg>com.example.generated</extraarg> <extraarg>-exsh</extraarg> <extraarg>true</extraarg> </extraargs> <wsdl>${basedir}/src/main/resources/wsdl/MyWSDL.wsdl</wsdl> </wsdlOption> </wsdlOptions> </configuration> <goals> <goal>wsdl2java</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>build-helper-maven-plugin</artifactId> <version>3.0.0</version> <executions> <execution> <id>add-source</id> <phase>generate-sources</phase> <goals> <goal>add-source</goal> </goals> <configuration> <sources> <source>${basedir}/src/main/generated</source> </sources> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
From our generated classes we need to find the Service Class and right operation which we need to call. The code will generate classes like ObjectFactory which contains various namespaces along with a ServiceInterface which contains the service operations
Open the interface and look for methods which have annotation
@WebMethod(action = "serviceOperation")these are the various service operations which have been exposed by WSDL
@WebService(targetNamespace = "http://crmCommon/content/outboundMessage/", name = "MYServicePortType") @XmlSeeAlso({ObjectFactory.class}) public interface MyService { @WebMethod(action = "serviceOperation") @RequestWrapper(localName = "serviceOperation", targetNamespace = "http://crmCommon/content/outboundMessage/types/", className = "com.gen.lookup.ProcessType") @ResponseWrapper(localName = "serviceOperationResponse", targetNamespace = "http://crmCommon/content/outboundMessage/types/", className = "com.gen.lookup.ProcessResponseType") public void serviceOperation(
Another class is also known as Port which implements the service endpoint interface defined by Service will also be in generated folder. We Will use these classes to make a call to web service.
@WebServiceClient(name = "Source_WEB_Contact_Contact_ENDPOINTPortType", wsdlLocation = "file://src/main/resources/wsdl/LookUp.wsdl", targetNamespace = "http:///crmCommon/content/outboundMessage/") public class MyServicePort extends Service { public final static URL WSDL_LOCATION; public final static QName SERVICE = new QName("http:///crmCommon/content/outboundMessage/", "Source_WEB_Contact_Contact_ENDPOINTPortType"); public final static QName SourceWEBContactContactENDPOINTPortTypePt = new QName("http:///crmCommon/content/outboundMessage/", "Source_WEB_Contact_Contact_ENDPOINTPortType_pt"); static {
Create a new instance of Service class
MyServicePort service = new MyServicePort();
The method
getPortreturns a proxy. The parameter in below method specifies the service endpoint interface that is supported by the returned proxy.
MyService port = service.getPort(MyService.class);
Use the BindingProvider interface and type cast port to be of type BindingProvider
BindingProvider provider = (BindingProvider) port;
Use the properties ws-security.username and ws-security.password to define the UserName and Password
provider.getRequestContext().put("ws-security.username", "myusername"); provider.getRequestContext().put("ws-security.password", "mypassword");
provider.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "https://mycustomerURL/replacethis");
If the security policy is defined in the WSDL requires a timestamp to avoid replay attacks CXF will automatically add the timestamp to the request.
Invoke the port’s method(service operation)
port.serviceOperation(request);
That’s all, You can add below Logging In and Logging Out interceptor to print request response in code
org.apache.cxf.endpoint.Client client = org.apache.cxf.frontend.ClientProxy.getClient(port); org.apache.cxf.endpoint.Endpoint cxfEndpoint = client.getEndpoint(); cxfEndpoint.getOutInterceptors().add(new LoggingInInterceptor()); cxfEndpoint.getOutInterceptors().add(new LoggingOutInterceptor());
The post How to consume a secure SOAP Web service in Spring Boot Application appeared first on Little Big Extra.
]]>The post How to Add Bootstrap Css and JQuery To Spring Boot MVC Application appeared first on Little Big Extra.
]]>Twitter Bootstrap is one of the most popular front-end frameworks for responsive web design and development.
Font Awesome provides us with scalable vector icons that can be customised. Needless to say, anything about Jquery, since it is the number 1 Javascript framework and has been so from last 10 years.
It is Imperative that for your Spring Boot MVC based application you will require either of the above technologies to make your web pages look better.
We need to add dependency for
Apart from above, we need to add a dependency for web jars locator which lets us locate the CSS and JS files without providing the exact version of files.
<dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator</artifactId> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>font-awesome</artifactId> <version>4.7.0</version> </dependency>
Make sure you have either of the annotations in your Spring Boot
@SpringBootApplication
@EnableWebMvc
In the HTML page, you need to provide link to CSS and javascript src files location. Add the below head section in your *.html file
Note that the path doesn’t contain any specific version of CSS file or js files, thanks to web jars-locator dependency. That would mean that even if you change the version in your pom.xml there is no need to change any reference in HTML
<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>
You can use any of the above CSS classes from bootstrap and font awesome or js files from Jquery with your HTML pages.
<div class="container"> <a href="/login" class="btn btn-primary"><span class="fa fa-user"></span> SignIn</a> <a href="/logout" class="btn btn-danger">Logout <span class="fa fa-sign-out"></span> </a> <a href="/facebook" class="btn btn-primary">Facebook <span class="fa fa-facebook"></span> </a> <a href="/google" class="btn btn-danger"> Google <span class="fa fa-google-plus"></span> </a> <a href="/linkedin" class="btn btn-primary">LinkedIn <span class="fa fa-linkedin"></span> </a> </div>
For above code, I got the following output. Bootstrap button classes and icons from font-awesome have been applied to the buttons.
Follow this Video for reference
The post How to Add Bootstrap Css and JQuery To Spring Boot MVC Application appeared first on Little Big Extra.
]]>The post How to access LinkedIn data using spring-social appeared first on Little Big Extra.
]]>Spring Social provides the capability which enables our application to interact with LinkedIn Rest API and get user data. Considering how popular social logins have become recently, it is a nice feature to have on your website.
I would suggest you to first clone the as it does have required page structure and dependencies
Add the spring-social-google dependency
org.springframework.social spring-social-linkedin 1.0.0.RELEASE
Now update the application.properties with your secret and client key
spring.social.linkedin.app-id=771mrzk94hye1w spring.social.linkedin.app-secret=iIJFgBf9lCb18zYe
Under src/main/resources/templates/connect you should have facebookConnect.html and facebookConnected.html, if you have cloned/downloaded the spring tutorial properly.
Again replace facebook with linkedin in HTML and save them as linkedinConnected.html and linkedinConnect.html or copy them as shown below. Make sure the scope is properly defined in your request.
<input type="hidden" name="scope" value="r_basicprofile,r_emailaddress" />
linkedinConnect.html
<html> <head> <title>Hello LinkedIn</title> </head> <body> <h3>Connect to Linkedin</h3> <form action="/connect/linkedin" method="POST"> <input type="hidden" name="scope" value="r_basicprofile,r_emailaddress" /> <div class="formInfo"> <p>You aren't connected to LinkedIn yet. Click the button to connect this application with your account.</p> </div> <p><button type="submit">Connect to LinkedIn</button></p> </form> </body> </html>
linkedinConnected.html
<html> <head> <title>Hello Facebook</title> </head> <body> <h3>Connected to LinkedIn</h3> <p> You are now connected to your LinkedIn account. Click <a href="/linkedin">here</a> to see some entries from your LinkedIn feed. </p> </body> </html>
We need to add a rest controller so it can handle LinkedIn authorization requests
@Controller @RequestMapping("/linkedin") public class LinkedInController { private LinkedIn linkedIn; private ConnectionRepository connectionRepository; public LinkedInController(LinkedIn linkedIn, ConnectionRepository connectionRepository) { this.linkedIn = linkedIn; this.connectionRepository = connectionRepository; } @GetMapping public String helloFacebook(Model model) { if (connectionRepository.findPrimaryConnection(LinkedIn.class) == null) { return "redirect:/connect/linkedin"; } ProfileOperations user = linkedIn.profileOperations(); System.out.println(user); model.addAttribute("linkedInProfile",linkedIn.profileOperations().getUserProfileFull()); return "linkedin"; } }
In src/main/resources add a file called “linkedin.html” at same level as “hello.html”
<html> <head> <title>Hello Facebook</title> </head> <body> <h3>Hello, <span th:text="${linkedInProfile.firstName}"></span><span th:text="${linkedInProfile.lastName}"></span></h3> <h4><span th:text="${linkedInProfile.emailAddress}"></span></h4> <h4><span th:text="${linkedInProfile.id}"></span></h4> <h4><span th:text="${linkedInProfile.location.country}"></span></h4> </body> </html>
Run the application on localhost: port/linkedin and you should be authenticated. Drop comments if you face any problem
If you want to change the default page flow (linkedinconnect.tml to connect/linkedinConnected.html),please follow below link
The post How to access LinkedIn data using spring-social appeared first on Little Big Extra.
]]>The post How to access google data using spring-social and spring boot appeared first on Little Big Extra.
]]>Spring social do have a spring-social-google project but it doesn’t have autoconfiguration for Google. So google authorization doesn’t work with spring boot autoconfigure straight away. I had created a pull request and merged the code to spring repository but got the following reply
Thanks for the PR. We’ve already considered this in #2548 where we decided that it isn’t something that we want to include in Spring Boot. That’s still the case so I’m going to close this. Thanks anyway.
So it doesn’t look like sooner or later they are going to add autoconfigure functionality to Spring-Social-Google
I would suggest you to first clone the as it does have required page structure and dependencies
Add the spring-social-google dependency
<dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-google</artifactId> <version>1.0.0.RELEASE</version> </dependency>
Do Ctrl+Shift+T in your IDE(eclipse) and look for FacebookAutoConfiguration class you should be able to find it either in org.springframework.boot.autoconfigure.social package in spring-autoconfigure.jar
Copy this File and replace Facebook with Google. Alternatively, copy the below class and put in some package, make sure it is available in classpath(src/main/java or inside another source folder)
@Configuration @ConditionalOnClass({ SocialConfigurerAdapter.class, GoogleConnectionFactory.class }) @ConditionalOnProperty(prefix = "spring.social.google", name = "app-id") @AutoConfigureBefore(SocialWebAutoConfiguration.class) @AutoConfigureAfter(WebMvcAutoConfiguration.class) public class GoogleAutoConfiguration { @Configuration @EnableSocial @EnableConfigurationProperties(GoogleProperties.class) @ConditionalOnWebApplication protected static class GoogleConfigurerAdapter extends SocialAutoConfigurerAdapter { private final GoogleProperties properties; protected GoogleConfigurerAdapter(GoogleProperties properties) { this.properties = properties; } @Bean @ConditionalOnMissingBean(Google.class) @Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES) public Google google(ConnectionRepository repository) { Connection connection = repository.findPrimaryConnection(Google.class); return connection != null ? connection.getApi() : null; } @Bean(name = { "connect/googleConnect", "connect/googleConnected" }) @ConditionalOnProperty(prefix = "spring.social", name = "auto-connection-views") public GenericConnectionStatusView googleConnectView() { return new GenericConnectionStatusView("google", "Google"); } @Override protected ConnectionFactory<?> createConnectionFactory() { return new GoogleConnectionFactory(this.properties.getAppId(), this.properties.getAppSecret()); } } }
In the same package add the below class
import org.springframework.boot.autoconfigure.social.SocialProperties; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "spring.social.google") public class GoogleProperties extends SocialProperties{ }
Now update the application.properties with your secret and client key
spring.social.google.appId=12894100090-tqso3lih5o42isneort886la2pesafmp.apps.googleusercontent.com spring.social.google.appSecret=9xfU16efvxQ-BTMsXT9wO
Under src/main/resources/templates/connect you should have facebookConnect.html and facebookConnected.html, if you have cloned the project properly.Again replace facebook with google in HTML and save them as googleConnected.html and googleConnect.html or copy them as shown below.
googleConnect.html
<html> <head> <title>Hello Google</title> </head> <body> <h3>Connect to Google</h3> <form action="/connect/google" method="POST"> <input type="hidden" name="scope" value="profile" /> <div class="formInfo"> <p>You aren't connected to Google yet. Click the button to connect this application with your account.</p> </div> <p><button type="submit">Connect to Google</button></p> </form> </body> </html>
googleConnected.html
<html> <head> <title>Hello Google</title> </head> <body> <h3>Connected to Google</h3> <p> You are now connected to your Google account. Click <a href="/google">here</a> to see some entries. </p> </body> </html>
We need to add a rest controller so it can handle google authorization requests
@Controller @RequestMapping("/google") public class GoogleController { private Google google; private ConnectionRepository connectionRepository; public GoogleController(Google google, ConnectionRepository connectionRepository) { this.google = google; this.connectionRepository = connectionRepository; } @GetMapping public String helloFacebook(Model model) { if (connectionRepository.findPrimaryConnection(Google.class) == null) { return "redirect:/connect/google"; } Person user = google.plusOperations().getGoogleProfile(); System.out.println(google.isAuthorized()); System.out.println(": "+user.getGivenName() +" : "+ user.getEmailAddresses() +" : "+ user.getFamilyName()); model.addAttribute("googleProfile", user); return "google"; } }
In src/main/resources add a file called “google.html” at same level as “hello.html”
<html> <head> <title>Hello Google</title> </head> <body> <h3>Hello, <span th:text="${googleProfile.givenName}"></span><span th:text="${googleProfile.familyName}"></span></h3> <h4><span th:text="${googleProfile.id}"></span></h4> <div th:each="post:${googleProfile.emailAddresses}"> Email : <b th:text="${post}"></b> <hr/> </div> </body> </html>
Run the application on localhost: port/google and you should be authenticated. Drop comments if you face any problem
The post How to access google data using spring-social and spring boot appeared first on Little Big Extra.
]]>