post-image

Code ví dụ Spring MVC Security đăng nhập bằng LinkedIn

Social

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/app trên linkedin

Ở đây mình tạo ứng dụng “stackajava.com-SpringBoot” với:

  • Client ID = 81xomg6on7p1gw
  • Client Secret = hjdWKlDvKAiJfM9y

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

Tạo ứng dụng/app trên linkedin

Tạo Maven Project

Code ví dụ Spring MVC Security đăng nhập bằng LinkedIn

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>SpringMvcLinkedIn</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>fluent-hc</artifactId>
      <version>4.5.5</version>
    </dependency>
    <!-- Jackson -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.3</version>
    </dependency>
  </dependencies>
</project>Code language: HTML, XML (xml)

Mình sử dụng thêm thư viện httpcomponents để gửi request bên trong code Java và 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.springmvclinkedin" />
  <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>
Code language: HTML, XML (xml)

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>Code language: HTML, XML (xml)

File controller

BaseController.java

package stackjava.com.springmvclinkedin.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.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import stackjava.com.springmvclinkedin.common.LinkedInUser;
import stackjava.com.springmvclinkedin.common.LinkedInUtils;
@Controller
public class BaseController {
  
  @Autowired
  private LinkedInUtils linkedInUtils;
    @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("linkedin_error")) {
          model.addAttribute("message", "Login by LinkedIn Failed!");
        }
      }
      return "login";
    }
  @RequestMapping("/login-linkedin")
  public String loginLinkedIn(HttpServletRequest request) throws ClientProtocolException, IOException {
    String code = request.getParameter("code");
    
    if (code == null || code.isEmpty()) {
      return "redirect:/login?message=linkedin_error";
    }
    String accessToken = linkedInUtils.getToken(code);
    
    LinkedInUser user = linkedInUtils.getUserInfo(accessToken);
    UserDetails userDetail = linkedInUtils.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";
  }
}
Code language: JavaScript (javascript)

Method loginLinkedIn xử lý kết quả trả về từ LinkedIn

  • Lấy code mà LinkedIn 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 LinkedIn:

LinkedInUtils.java

package stackjava.com.springmvclinkedin.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.context.annotation.PropertySource;
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
@PropertySource("classpath:application.properties")
public class LinkedInUtils {
  @Autowired
  private Environment env;
  public String getToken(final String code) throws ClientProtocolException, IOException {
    String link = env.getProperty("linkedin.link.get.token");
    String response = Request.Post(link)
        .bodyForm(Form.form().add("client_id", env.getProperty("linkedin.client.id"))
            .add("client_secret", env.getProperty("linkedin.client.secret"))
            .add("redirect_uri", env.getProperty("linkedin.redirect.uri")).add("code", code)
            .add("grant_type", env.getProperty("linkedin.grant_type")).build())
        .execute().returnContent().asString();
    ObjectMapper mapper = new ObjectMapper();
    JsonNode node = mapper.readTree(response).get("access_token");
    return node.textValue();
  }
  public LinkedInUser getUserInfo(final String accessToken) throws ClientProtocolException, IOException {
    String link = env.getProperty("linkedin.link.get.user_info") + accessToken;
    String response = Request.Get(link).execute().returnContent().asString();
    ObjectMapper mapper = new ObjectMapper();
    JsonNode jsonNode = mapper.readTree(response);
    String id = jsonNode.get("id").textValue();
    String firstName = jsonNode.get("firstName").textValue();
    String lastName = jsonNode.get("lastName").textValue();
    String name = "linkedin - " + firstName + " " + lastName;
    LinkedInUser user = new LinkedInUser(id, name);
    return user;
  }
  
  public UserDetails buildUser(LinkedInUser 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.getName(),
        "", enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
    return userDetail;
  }
}
Code language: JavaScript (javascript)

File LinkedInUser.java

Dùng để chứa thông tin tài khoản gửi về từ LinkedIn

LinkedInUser.java

package stackjava.com.springmvclinkedin.common;
public class LinkedInUser {
  private String id;
  private String name;
  // getter - setter
}
Code language: PHP (php)

 File application.properties

Chứa các thông tin về ứng dụng linkedin

application.properties

linkedin.client.id=81xomg6on7p1gw
linkedin.client.secret=hjdWKlDvKAiJfM9y
linkedin.redirect.uri=http://localhost:8080/SpringMvcLinkedIn/login-linkedin
linkedin.link.get.token=https://www.linkedin.com/oauth/v2/accessToken
linkedin.link.get.user_info=https://api.linkedin.com/v1/people/~?format=json&oauth2_access_token=
linkedin.grant_type=authorization_codeCode language: JavaScript (javascript)

Các file views

login.jsp

<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<title>login</title>
</head>
<body>
  <h2>Spring MVC Security - Login with LinkedIn</h2>
  <span style="color: red;">${message}</span> <br/>
  
  
  <a href="https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=81xomg6on7p1gw
  &redirect_uri=http://localhost:8080/SpringMvcLinkedIn/login-linkedin&scope=r_basicprofile">Login With LinkedIn</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>Code language: HTML, XML (xml)

Đường link https://www.linkedin.com/oauth/v2/authorization?...scope=r_basicprofile dùng để gọi hộp thoại đăng nhập và cài đặt chuyển hướng URL.

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>Code language: HTML, XML (xml)

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>
Code language: HTML, XML (xml)

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>Code language: HTML, XML (xml)

Demo

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

Code ví dụ Spring MVC Security đăng nhập bằng LinkedIn
Code ví dụ Spring MVC Security đăng nhập bằng LinkedIn
Code ví dụ Spring MVC Security đăng nhập bằng LinkedIn

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

Code ví dụ Spring MVC Security đăng nhập bằng LinkedIn

Trường hợp từ chối cho phép ứng dụng truy cập tài khoản

Code ví dụ Spring MVC Security đăng nhập bằng LinkedIn
Code ví dụ Spring MVC Security đăng nhập bằng LinkedIn

Trường hợp đồng cho phép ứng dụng truy cập tài khoản

Code ví dụ Spring MVC Security đăng nhập bằng LinkedIn
Code ví dụ Spring MVC Security đăng nhập bằng LinkedIn

Tài khoản đăng nhập bằng LinkedIn chỉ có role User nên không thể truy cập trang admin.

Code ví dụ Spring MVC Security đăng nhập bằng LinkedIn

Code ví dụ Spring MVC Security đăng nhập bằng LinkedIn stackjava.com

Okay, Done!

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

Nguồn: https://stackjava.com/spring/code-vi-du-spring-mvc-security-dang-nhap-bang-linkedin.html

Leave a Reply

Your email address will not be published. Required fields are marked *