STACKJAVA

Code ví dụ Spring Boot Security login bằng Google (Gmail)

Code ví dụ Spring Boot Security login bằng Google (Gmail)

(Xem lại: Code ví dụ JSP Servlet login bằng Google (Gmail/Google+))

(Xem thêm: Code ví dụ Spring Boot Security login bằng Facebook)

Các công nghệ sử dụng:

Tạo Spring Boot Project

Cấu trúc Project

Thư viện sử dụng:

Mình sử dụng thêm thư viện httpcomponents để gửi request bên trong code java.

<!-- org.apache.httpcomponents -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>fluent-hc</artifactId>
</dependency>

Tạo ứng dụng/project trên google để sử dụng chức năng login

Ở đây mình tạo ứng dụng “stackajva-demo-login” với:

(Xem lại: Tạo ứng dụng google+ để đăng nhập thay tài khoản)

File application.properties

google.app.id=80724656105-fg2ndheoujm7c7dd4ob1i9mq3ebdbjhb.apps.googleusercontent.com
google.app.secret=PrmoOSPhKsilVqpzxrzjOoEU
google.redirect.uri=http://localhost:8080/login-google
google.link.get.token=https://accounts.google.com/o/oauth2/token
google.link.get.user_info=https://www.googleapis.com/oauth2/v1/userinfo?access_token=

 File cấu hình security

package stackjava.com.sbgoogle.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        return bCryptPasswordEncoder;
    }
  
  @Autowired
  public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication().passwordEncoder(passwordEncoder()).
      withUser("kai").password("$2a$04$Q2Cq0k57zf2Vs/n3JXwzmerql9RzElr.J7aQd3/Sq0fw/BdDFPAj.").roles("ADMIN");
    auth.inMemoryAuthentication().passwordEncoder(passwordEncoder()).
      withUser("sena").password("$2a$04$Q2Cq0k57zf2Vs/n3JXwzmerql9RzElr.J7aQd3/Sq0fw/BdDFPAj.").roles("USER");
//		auth.inMemoryAuthentication().passwordEncoder(NoOpPasswordEncoder.getInstance()).withUser("sena").password("123456").roles("USER");
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {

    // Chỉ cho phép user có quyền ADMIN truy cập đường dẫn /admin/**
    http.authorizeRequests().antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')");

    // Chỉ cho phép user có quyền ADMIN hoặc USER truy cập đường dẫn /user/**
    http.authorizeRequests().antMatchers("/user/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')");

    // Khi người dùng đã login, với vai trò USER, Nhưng truy cập vào trang yêu cầu vai trò ADMIN, sẽ chuyển hướng tới trang /403
    http.authorizeRequests().and().exceptionHandling().accessDeniedPage("/403");

    // Cấu hình cho Login Form.
    http.authorizeRequests().and().formLogin()//
        .loginProcessingUrl("/j_spring_security_login")//
        .loginPage("/login")//
        .defaultSuccessUrl("/user")//
        .failureUrl("/login?message=error")//
        .usernameParameter("username")//
        .passwordParameter("password")
        // Cấu hình cho Logout Page.
        .and().logout().logoutUrl("/j_spring_security_logout").logoutSuccessUrl("/login?message=logout");

  }
}

File Controller.

package stackjava.com.sbgoogle.controller;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;

import org.apache.http.client.ClientProtocolException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import stackjava.com.sbgoogle.common.GooglePojo;
import stackjava.com.sbgoogle.common.GoogleUtils;

@Controller
public class BaseController {
  
  @Autowired
  private GoogleUtils googleUtils;

  @RequestMapping(value = { "/", "/login" })
  public String login() {
    return "login";
  }

  @RequestMapping("/login-google")
  public String loginGoogle(HttpServletRequest request) throws ClientProtocolException, IOException {
    String code = request.getParameter("code");
    
    if (code == null || code.isEmpty()) {
      return "redirect:/login?google=error";
    }

    String accessToken = googleUtils.getToken(code);
    
    GooglePojo googlePojo = googleUtils.getUserInfo(accessToken);
    UserDetails userDetail = googleUtils.buildUser(googlePojo);
    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetail, null,
        userDetail.getAuthorities());
    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
    SecurityContextHolder.getContext().setAuthentication(authentication);
    return "redirect:/user";
  }

  @RequestMapping("/user")
  public String user() {
    return "user";
  }

  @RequestMapping("/admin")
  public String admin() {
    return "admin";
  }

  @RequestMapping("/403")
  public String accessDenied() {
    return "403";
  }
}

Method loginGoogle xử lý kết quả trả về từ google

File truy vấn, gửi request tới google:

package stackjava.com.sbgoogle.common;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.fluent.Form;
import org.apache.http.client.fluent.Request;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
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.stereotype.Component;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

@Component
public class GoogleUtils {

  @Autowired
  private Environment env;

  public String getToken(final String code) throws ClientProtocolException, IOException {
    String link = env.getProperty("google.link.get.token");

    String response = Request.Post(link)
        .bodyForm(Form.form().add("client_id", env.getProperty("google.app.id"))
            .add("client_secret", env.getProperty("google.app.secret"))
            .add("redirect_uri", env.getProperty("google.redirect.uri")).add("code", code)
            .add("grant_type", "authorization_code").build())
        .execute().returnContent().asString();

    ObjectMapper mapper = new ObjectMapper();
    JsonNode node = mapper.readTree(response).get("access_token");
    return node.textValue();
  }

  public GooglePojo getUserInfo(final String accessToken) throws ClientProtocolException, IOException {
    String link = env.getProperty("google.link.get.user_info") + accessToken;
    String response = Request.Get(link).execute().returnContent().asString();
    ObjectMapper mapper = new ObjectMapper();
    GooglePojo googlePojo = mapper.readValue(response, GooglePojo.class);
    System.out.println(googlePojo);
    return googlePojo;

  }

  public UserDetails buildUser(GooglePojo googlePojo) {
    boolean enabled = true;
    boolean accountNonExpired = true;
    boolean credentialsNonExpired = true;
    boolean accountNonLocked = true;
    List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
    authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
    UserDetails userDetail = new User(googlePojo.getEmail(),
        "", enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
    return userDetail;
  }

}

 File GooglePojo.java

Dùng để chứa các thông tin tài khoản (email, name…) gửi về từ google.

package stackjava.com.sbgoogle.common;

public class GooglePojo {

  private String id;
  private String email;
  private boolean verified_email;
  private String name;
  private String given_name;
  private String family_name;
  private String link;
  private String picture;

  // getter-setter
}

Các file view:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spring Boot Security Hello</title>
</head>
<body>
  <h2>Spring Boot Security Login with Google+</h2>
  <a href="https://accounts.google.com/o/oauth2/auth?scope=email&redirect_uri=http://localhost:8080/login-google&response_type=code
    &client_id=80724656105-fg2ndheoujm7c7dd4ob1i9mq3ebdbjhb.apps.googleusercontent.com&approval_prompt=force">Login With Gmail</a>	
  
  <br/>
  <form name='login-form' th:action="@{/j_spring_security_login}" method='POST'>
    <table>
      <tr>
        <td>Username:</td>
        <td><input type='text' name='username' value=''></td>
      </tr>
      <tr>
        <td>Password:</td>
        <td><input type='password' name='password' /></td>
      </tr>
      <tr>
        <td><input name="submit" type="submit" value="submit" /></td>
      </tr>
    </table>
    <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
  </form>
</body>
</html>

Đường link https://accounts.google.com/o/oauth2/...force dùng để gọi hộp thoại đăng nhập và cài đặt URL chuyển hướng

<!DOCTYPE html>
<html>
<head>
<title>Spring Boot Security Hello</title>
</head>
<body>
  <h2>User Page</h2>
  <h3>
    Welcome : <span th:utext="${#request.userPrincipal.name}"></span>
  </h3>

  <a th:href="@{/admin}">Admin Page</a>

  <br/><br/>
  <form th:action="@{/j_spring_security_logout}" method="post">
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
    <input type="submit" value="Logout" />
  </form>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Spring Boot Security Hello</title>
</head>
<body>
  <h2>Admin Page</h2>
  <h3>
    Hello : <span th:utext="${#request.userPrincipal.name}"></span>
  </h3>
  
  
  <a th:href="@{/user}">User Page</a>

  <br/><br/>
  <form th:action="@{/j_spring_security_logout}" method="post">
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
    <input type="submit" value="Logout" />
  </form>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Spring Boot Security Hello</title>
</head>
<body>
  <h2>Access Denied Page - 403</h2>
  
  <h3>
    Hi : <span th:utext="${#request.userPrincipal.name} + ' - you do not have permission to access this page'"></span>
  </h3>

  <a th:href="@{/user}">User Page</a>

  <br/><br/>
  <form th:action="@{/j_spring_security_logout}" method="post">
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
    <input type="submit" value="Logout" />
  </form>
</body>
</html>

Demo:

Đăng nhập bình thường bằng tài khoản kai/123456

Đăng nhập bằng tài khoản google

Truy cập trang admin.html bị lỗi vì tài khoản đăng nhập bằng google chỉ có role user.

Code ví dụ Spring Boot Security login bằng Google (Gmail) stackjava.com

Okay, Done!

Download code ví dụ trên tại đây.

 

References:

https://developers.google.com/+/web/signin/