post-image

Login bằng Facebook – Spring MVC Security

1. Tổng quan

Ở bài này mình cũng sẽ không dùng Facebook SDK hay Spring Social mà sẽ tạo thủ công quy trình đăng nhập ứng dụng bằng facebook để mọi người hiểu rõ nguyên lý và luồng chạy. 

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

  • Spring 5.0.2.RELEASE
  • Spring Security 5.0.2.RELEASE
  • Maven
  • Tomcat
  • JDK 1.8
  • Eclipse + Spring Tool Suite

Tạo ứng dụng trên facebook

Ở đây mình tạo ứng dụng trên facebook để thực hiện chức năng login với app id = ‘359123991240252’ và key = ‘d07e182d8495df6930665d6c39fbe8ac’

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

Tạo Maven Project

Code ví dụ Spring MVC Security, login bằng Facebook.

Thư viện sử dụng:

  • pom.xml
<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>stackjava.com</groupId>

<artifactId>AccessFacebook</artifactId>

<version>0.0.1-SNAPSHOT</version>

<packaging>war</packaging>


<properties>

<spring.version>5.0.2.RELEASE</spring.version>

<spring.security.version>5.0.2.RELEASE</spring.security.version>

<jstl.version>1.2</jstl.version>


</properties>


<dependencies>

<!-- Spring MVC -->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-webmvc</artifactId>

<version>${spring.version}</version>

</dependency>


<!-- Spring Security -->

<dependency>

<groupId>org.springframework.security</groupId>

<artifactId>spring-security-web</artifactId>

<version>${spring.security.version}</version>

</dependency>

<dependency>

<groupId>org.springframework.security</groupId>

<artifactId>spring-security-config</artifactId>

<version>${spring.security.version}</version>

</dependency>


<!-- JSP/Servlet -->

<dependency>

<groupId>javax.servlet.jsp</groupId>

<artifactId>jsp-api</artifactId>

<version>2.2</version>

<scope>provided</scope>

</dependency>

<dependency>

<groupId>javax.servlet</groupId>

<artifactId>servlet-api</artifactId>

<version>2.5</version>

<scope>provided</scope>

</dependency>


<!-- jstl for jsp page -->

<dependency>

<groupId>jstl</groupId>

<artifactId>jstl</artifactId>

<version>${jstl.version}</version>

</dependency>


<!-- org.apache.httpcomponents -->

<dependency>

<groupId>org.apache.httpcomponents</groupId>

<artifactId>httpcore</artifactId>

<version>4.4.9</version>

</dependency>

<dependency>

<groupId>org.apache.httpcomponents</groupId>

<artifactId>httpclient</artifactId>

<version>4.5.5</version>

</dependency>

<dependency>

<groupId>org.apache.httpcomponents</groupId>

<artifactId>fluent-hc</artifactId>

<version>4.5.5</version>

</dependency>


<!-- restfb -->

<dependency>

<groupId>com.restfb</groupId>

<artifactId>restfb</artifactId>

<version>2.3.0</version>

</dependency>




<!-- Jackson -->

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId>

<version>2.9.3</version>

</dependency>

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-core</artifactId>

<version>2.9.3</version>

</dependency>

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-annotations</artifactId>

<version>2.9.3</version>

</dependency>




</dependencies>

</project>

Mình sử dụng thêm thư viện httpcomponents và restfb để gửi request bên trong controller tới facebook. Thư viện Jackson  để xử lý dữ liệu JSON

File cấu hình Spring MVC

  • spring-mvc-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">


<context:component-scan base-package="stackjava.com.accessfacebook" />


<bean

class="org.springframework.web.servlet.view.InternalResourceViewResolver">

<property name="prefix">

<value>/WEB-INF/views/jsp/</value>

</property>

<property name="suffix">

<value>.jsp</value>

</property>

</bean>




</beans>
File cấu hình Spring Security
  • spring-security.xml
<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:beans="http://www.springframework.org/schema/beans"

xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

 http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">


<http auto-config="true">

<intercept-url pattern="/admin**" access="hasRole('ROLE_ADMIN')" />

<intercept-url pattern="/user**" access="hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')" />

<access-denied-handler error-page="/403"/>

<form-login

login-page="/login"

login-processing-url="/j_spring_security_login"

default-target-url="/user"

authentication-failure-url="/login?message=error"

username-parameter="username"

password-parameter="password" />

<logout logout-url="/j_spring_security_logout"

logout-success-url="/login?message=logout" delete-cookies="JSESSIONID" />

</http>


<authentication-manager>

<authentication-provider>

<user-service>

<user name="kai" password="{noop}123456" authorities="ROLE_ADMIN" />

<user name="sena" password="{noop}123456" authorities="ROLE_USER" />

</user-service>

</authentication-provider>

</authentication-manager>

</beans:beans>

 File controller:

  • BaseController.java
package stackjava.com.accessfacebook.controller;


import java.io.IOException;


import javax.servlet.http.HttpServletRequest;


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.ui.Model;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestParam;


import stackjava.com.accessfacebook.utils.RestFB;


@Controller

public class BaseController {


@Autowired

private RestFB restFB;


@RequestMapping(value = { "/", "/login" })

public String login(@RequestParam(required = false) String message, final Model model) {

if (message != null && !message.isEmpty()) {

if (message.equals("logout")) {

 model.addAttribute("message", "Logout!");

}

if (message.equals("error")) {

 model.addAttribute("message", "Login Failed!");

}

if (message.equals("facebook_denied")) {

 model.addAttribute("message", "Login by Facebook Failed!");

}

}

return "login";

}




@RequestMapping("/login-facebook")

public String loginFacebook(HttpServletRequest request) {

String code = request.getParameter("code");

String accessToken = "";

try {

 accessToken = restFB.getToken(code);

} catch (IOException e) {

return "login?facebook=error";

}




 com.restfb.types.User user = restFB.getUserInfo(accessToken);

 UserDetails userDetail = restFB.buildUser(user);

 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 loginFacebook xử lý kết quả trả về từ facebook

  • Nếu user không đồng ý đăng nhập bằng  facebook thì sẽ quay ngược trở về trang login
  • Nếu user đồng ý đăng nhập bằng facebook thì:
    • Lấy code mà facebook gửi về sau đó đổi code sang access token
    • Sử dụng access token lấy thông tin user (có thể thực hiện lưu lại thông tin vào database để quản lý)
    • Chuyển thông tin user sang đối tượng UserDetails để spring security quản lý
    • Sử dụng đối tượng UserDetails trên giống như thông tin authentication (tương đương với đăng nhập bằng username/password)

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

  • RestFB.java
package stackjava.com.accessfacebook.utils;


import java.io.IOException;

import java.util.ArrayList;

import java.util.List;


import org.apache.http.client.ClientProtocolException;

import org.apache.http.client.fluent.Request;

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;

import com.restfb.DefaultFacebookClient;

import com.restfb.FacebookClient;

import com.restfb.Version;


@Component

public class RestFB {


public static String FACEBOOK_APP_ID = "359123991240252";

public static String FACEBOOK_APP_SECRET = "d07e182d8495df6930665d6c39fbe8ac";

public static String FACEBOOK_REDIRECT_URL = "https://localhost:8443/AccessFacebook/login-facebook";

public static String FACEBOOK_LINK_GET_TOKEN = "https://graph.facebook.com/oauth/access_token?client_id=%s&client_secret=%s&redirect_uri=%s&code=%s";


public String getToken(final String code) throws ClientProtocolException, IOException {

String link = String.format(FACEBOOK_LINK_GET_TOKEN, FACEBOOK_APP_ID, FACEBOOK_APP_SECRET, FACEBOOK_REDIRECT_URL, code);

String response = Request.Get(link).execute().returnContent().asString();

 ObjectMapper mapper = new ObjectMapper();

 JsonNode node = mapper.readTree(response).get("access_token");

return node.textValue();

}


public com.restfb.types.User getUserInfo(final String accessToken) {

 FacebookClient facebookClient = new DefaultFacebookClient(accessToken, FACEBOOK_APP_SECRET, Version.LATEST);

return facebookClient.fetchObject("me", com.restfb.types.User.class);

}

public UserDetails buildUser(com.restfb.types.User user) {

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(user.getId(), "", enabled, accountNonExpired, credentialsNonExpired,

 accountNonLocked, authorities);

return userDetail;

}

}

Nếu không dùng thư viện RestFB thì bạn có thể dùng URL sau để lấy thông tin của user:
https://graph.facebook.com/me?access_token=... thông tin trả về sẽ gồm id và name.

(Bạn cũng có thể lấy thêm nhiều trường khác như email, comment, image… nhưng cần phải có thêm permission, ở đây mình chỉ thực hiện đăng nhập nên chỉ cần permission để lấy public profile)

 Các file views:

  • login.jsp
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<html>

<head>

<title>login</title>

</head>

<body>

<h1>Spring MVC-Security Login Form</h1>

<h2>${message}</h2>




<a href="https://www.facebook.com/dialog/oauth?client_id=359123991240252&redirect_uri=https://localhost:8443/AccessFacebook/login-facebook">Login Facebook</a>




<form name='loginForm' action="<c:url value='j_spring_security_login' />" method='POST'>

<table>

<tr>

<td>User:</td>

<td><input type='text' name='username'></td>

</tr>

<tr>

<td>Password:</td>

<td><input type='password' name='password' /></td>

</tr>

<tr>

<td colspan='2'><input name="submit" type="submit" value="login" /></td>

</tr>

</table>

<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />




</form>

</body>

</html>

Đường link

https://www.facebook.com/dialog/oauth?client_id=180439422588509&redirect_uri=https://localhost:8443/login-facebookdùng để gọi hộp thoại Đăng nhập và cài đặt URL chuyển hướng.

  • user.jsp
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<html>

<head>

<title>User Page</title>

</head>

<body>

<h1>User Page</h1>

<h2>Welcome: ${pageContext.request.userPrincipal.name}</h2>

<a href="<c:url value="/admin" />">Admin Page</a>

<br/><br/>




<form action="<c:url value="/j_spring_security_logout" />" method="post">

<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />

<input type="submit" value="Logout" />

</form>




</body>

</html>
  • admin.jsp
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<html>

<head>

<title>Admin Page</title>

</head>

<body>

<h1>Admin Page</h1>

<h2>Welcome: ${pageContext.request.userPrincipal.name}</h2>

<a href="<c:url value="/user" />">User Page</a>

<br/><br/>

<form action="<c:url value="/j_spring_security_logout" />" method="post">

<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />

<input type="submit" value="Logout" />

</form>




</body>

</html>

403.jsp

<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>

<html>

<head>

<title>403</title>

</head>

<body>

<h1>403</h1>

<span>Hi: ${pageContext.request.userPrincipal.name} you do not have permission to access this page</span>

<a href="<c:url value="/user" />">User Page</a>

<br/><br/>

<form action="<c:url value="/j_spring_security_logout" />" method="post">

<input type="hidden" name="${_csrf.parameterName}" 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

Code ví dụ Spring MVC Security, login bằng Facebook

Code ví dụ spring mvc sercurity đăng nhập bằng facebook

ví dụ spring mvc login bằng facebook

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

hướng dẫn spring mvc đăng nhập bằng facebook

Trường hợp không đồng ý đăng nhập bằng facebook

hướng dẫn spring security đăng nhập bằng facebook

đăng nhập bằng facebook theo cách thủ công

Trường hợp cho phép đăng nhập bằng facebook (sau khi cho phép, lần sau đăng nhập tiếp facebook sẽ không hỏi lại nữa)

ví dụng spring mvc đăng nhập bằng facebook không sử dụng sdk

Code ví dụ Spring MVC Security, login bằng Facebook

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

Code ví dụ Spring MVC Security, login bằng Facebook

Code ví dụ Spring MVC Security, login bằng Facebook stackjava.com

Okay, Done!

Trả lời

Email của bạn sẽ không được hiển thị công khai.