Ví dụ OAuth2 Social Login trong Spring Boot
1. Mục tiêu của bài học
Cách truyền thống mà người dùng phải trải qua để truy cập vào một website được bảo hộ đó là họ cần phải tạo một tài khoản, sau đó vào email của họ kích hoạt tài khoản, ghi nhớ tên đăng nhập và mật khẩu, đa số người dùng thường quên các thông tin này sau một thời gian. Các thông tin mà người dùng phải nhập để tạo một tài khoản đôi khi cũng làm người dùng nản trí.

Đăng nhập vào một website thông qua một mạng xã hội là một hình thức khá phổ biến hiện nay, nó được rất nhiều website sử dụng. Bằng cách này người dùng có thể nhanh chóng tạo một tài khoản trên website của bạn, ứng dụng của bạn cũng không cần gửi email kích hoạt tài khoản cho người dùng.

Trong lần đầu tiên mà người dùng sử dụng tài khoản mạng xã hội của họ để đăng nhập vào website của bạn, bạn có 3 lựa chọn khác nhau để xử lý.
- Lựa chọn 1: Tài khoản mạng xã hội của họ sẽ được chấp nhận, và website của bạn không thực hiện bất kỳ một hành động nào khác.
- Lựa chọn 2: Dựa trên các thông tin công khai trên tài khoản mạng xã hội của người dùng, website của bạn tự động taọ ra một tài khoản tương ứng.
- Lựa chọn 3: Website của bạn chuyển hướng người dùng tới trang đăng ký với các thông tin mặc định lấy được từ tài khoản mạng xã hội của họ, điều này giúp người dùng không cần nhập tất cả các thông tin.
Trong bài học này, chúng ta sẽ tạo một ứng dụng với Spring Boot + JPA + Spring Social Security. Dưới đây là hình ảnh xem trước của ứng dụng.
LỰA CHỌN 2:
Trường hợp lần đầu tiên người dùng đăng nhập với mang xã hội, một bản ghi APP_USER được tạo ra tự động, với User_Name tự động được tạo ra bởi hệ thống.

LỰA CHỌN 3:
Trường hợp người dùng lần đầu tiên đăng nhập bằng mạng xã hội, hệ thống chuyển hướng người dùng tới trang đăng ký với các thông tin mặc định có được từ tài khoản mạng xã hội của người dùng. Người dùng có thể thay đổi thông tin User_Name, First Name, Last Name, ... Sau đó người dùng nhấn "Submit" một bản ghi APP_USER sẽ được tạo ra.

2. Tạo dự án Spring Boot
Trên Eclipse, tạo một dự án Spring Boot.

Cơ sở dữ liệu sử dụng trong ứng dụng này là MySQL, SQL Server, PostGres hoặc Oracle hoặc bất kỳ một cơ sở dữ liệu nào khác mà bạn quen thuộc.

Khai báo các thư viện Spring Social vào project của bạn:
- spring-security-oauth2
- spring-social-core
- spring-social-config
- spring-social-security
- spring-social-facebook
- spring-social-twitter
- spring-social-github
- spring-social-linkedin
- spring-social-google
Nội dung đầy đủ của tập tin pom.xml:
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>org.o7planning</groupId>
<artifactId>SpringBootSocialJPA</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SpringBootSocialJPA</name>
<description>Spring Boot + Oauth2 Social + JPA</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.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-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</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>
<!-- http://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.social/spring-social-core -->
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-core</artifactId>
<version>1.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.social/spring-social-config -->
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-config</artifactId>
<version>1.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.social/spring-social-web -->
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-web</artifactId>
<version>1.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.social/spring-social-security -->
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-security</artifactId>
<version>1.1.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.social/spring-social-facebook -->
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-facebook</artifactId>
<version>2.0.3.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.social/spring-social-twitter -->
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-twitter</artifactId>
<version>1.1.2.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.social/spring-social-github -->
<!-- <dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-github</artifactId>
<version>1.0.0.M4</version>
</dependency> -->
<!-- https://mvnrepository.com/artifact/org.springframework.social/spring-social-linkedin -->
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-linkedin</artifactId>
<version>1.0.2.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.social/spring-social-google -->
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-google</artifactId>
<version>1.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.3</version>
</dependency>
<!-- SQL Server Mssql-Jdbc Driver -->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<scope>runtime</scope>
</dependency>
<!-- SQL Server JTDS Driver -->
<!-- https://mvnrepository.com/artifact/net.sourceforge.jtds/jtds -->
<dependency>
<groupId>net.sourceforge.jtds</groupId>
<artifactId>jtds</artifactId>
<version>1.3.1</version>
</dependency>
<!-- Email validator,... -->
<!-- http://mvnrepository.com/artifact/commons-validator/commons-validator%20 -->
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<repositories>
<!-- Repository for ORACLE JDBC Driver -->
<repository>
<id>codelds</id>
<url>https://code.lds.org/nexus/content/groups/main-repo</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
SpringBootSocialJpaApplication.java
package org.o7planning.sbsocial;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringBootSocialJpaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootSocialJpaApplication.class, args);
}
}
3. Cấu hình DataSource
Các thông tin về cơ sở dữ liệu cần được cấu hình trong tập tin application.properties:
application.properties (MySQL)
spring.thymeleaf.cache=false
# ===============================
# DATABASE
# ===============================
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/social
spring.datasource.username=root
spring.datasource.password=12345
# ===============================
# JPA / HIBERNATE
# ===============================
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
application.properties (Mssql-Jdbc Driver)
spring.thymeleaf.cache=false
# ===============================
# DATABASE
# ===============================
spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource.url=jdbc:sqlserver://tran-vmware-pc\\SQLEXPRESS:1433;databaseName=social
spring.datasource.username=sa
spring.datasource.password=12345
# ===============================
# JPA / HIBERNATE
# ===============================
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.SQLServer2012Dialect
application.properties (PostGres)
spring.thymeleaf.cache=false
# ===============================
# DATABASE CONNECTION
# ===============================
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://tran-vmware-pc:5432/social
spring.datasource.username=postgres
spring.datasource.password=12345
# ===============================
# JPA / HIBERNATE
# ===============================
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
# Fix Postgres JPA Error:
# Method org.postgresql.jdbc.PgConnection.createClob() is not yet implemented.
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
application.properties (Oracle)
spring.thymeleaf.cache=false
# ===============================
# DATABASE
# ===============================
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:db12c
spring.datasource.username=social
spring.datasource.password=12345
# ===============================
# JPA / HIBERNATE
# ===============================
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
4. Cấu hình Security & Spring Social

Cấu hình Spring Security:
WebSecurityConfig.java
package org.o7planning.sbsocial.config;
import org.o7planning.sbsocial.entity.AppRole;
import org.springframework.beans.factory.annotation.Autowired;
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.core.userdetails.UserDetailsService;
import org.springframework.social.security.SpringSocialConfigurer;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
// Pages do not require login
http.authorizeRequests().antMatchers("/", "/signup", "/login", "/logout").permitAll();
http.authorizeRequests().antMatchers("/userInfo").access("hasRole('" + AppRole.ROLE_USER + "')");
// For ADMIN only.
http.authorizeRequests().antMatchers("/admin").access("hasRole('" + AppRole.ROLE_ADMIN + "')");
// When the user has logged in as XX.
// But access a page that requires role YY,
// AccessDeniedException will be thrown.
http.authorizeRequests().and().exceptionHandling().accessDeniedPage("/403");
// Form Login config
http.authorizeRequests().and().formLogin()//
// Submit URL of login page.
.loginProcessingUrl("/j_spring_security_check") // Submit URL
.loginPage("/login")//
.defaultSuccessUrl("/userInfo")//
.failureUrl("/login?error=true")//
.usernameParameter("username")//
.passwordParameter("password");
// Logout Config
http.authorizeRequests().and().logout().logoutUrl("/logout").logoutSuccessUrl("/");
// Spring Social Config.
http.apply(new SpringSocialConfigurer())
//
.signupUrl("/signup");
}
// This bean is load the user specific data when form login is used.
@Override
public UserDetailsService userDetailsService() {
return userDetailsService;
}
}
Để người dùng có thể đăng ký thông qua mạng xã hội vào website của bạn, trên mỗi mạng xã hội bạn cần phải đăng ký các chứng chỉ (certificate) OAuth2, sau khi đăng ký bạn sẽ có một cặp "Khóa + Mật khẩu". Hãy khai báo các cặp khóa và mật khẩu này vào tập tin social-cfg.properties:

Ở dưới đây tôi đã đăng ký các chứng chỉ (certificate) OAuth2 để ứng dụng này có đăng nhập với một vài mạng xã hội thông dụng. Nhưng nó chỉ có tác dụng nếu ứng dụng này chạy trên http://localhost:8080, trong trường hợp bạn chạy ứng dụng trên các tên miền (domain) hoặc cổng (port) khác bạn phải đăng ký các chứng chỉ (certificate) của riêng bạn.
social-cfg.properties
social.auto-signup=false
# Google
# http://localhost:8080/auth/google
google.client.id=160790488111-he5fn6rq0foqg05te70dk25gifeoum9s.apps.googleusercontent.com
google.client.secret=VILTmX1UjOnyw2_meYvDQEdl
google.scope=https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile
# Facebook
# http://localhost:8080/auth/facebook
facebook.app.id=1084911261562762
facebook.app.secret=81a324fdbc4cade1ee25523c7bff58b3
facebook.scope=public_profile,email
# Twitter
# http://localhost:8080/auth/twitter
twitter.consumer.key=
twitter.consumer.secret=
twitter.scope=
# Linkedin
# http://localhost:8080/auth/linkedin
linkedin.consumer.key=...
linkedin.consumer.secret=...
linkedin.scope=
Tập tin social-cfg.properties sẽ được đọc trong lớp SocialConfig.
Chú ý thuộc tính social.auto-signup:
- social.auto-signup=true: Có nghĩa là khi người dùng đăng nhập lần đầu tiên bằng mạng xã hội, một bản ghi APP_USER sẽ được tạo ra tự động.
- social.auto-signup=false: Có nghĩa là khi người dùng đăng nhập lần đầu tiên bằng mạng xã hội, ứng dụng sẽ chuyển hướng người dùng tới trang đăng ký, với các thông tin mặc định, người dùng có thể thay đổi các thông tin này, sau đó nhấn "Submit", lúc này một bản ghi APP_USER mới được tạo ra.
SocialConfig.java
package org.o7planning.sbsocial.config;
import javax.sql.DataSource;
import org.o7planning.sbsocial.dao.AppUserDAO;
import org.o7planning.sbsocial.social.ConnectionSignUpImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.social.UserIdSource;
import org.springframework.social.config.annotation.ConnectionFactoryConfigurer;
import org.springframework.social.config.annotation.EnableSocial;
import org.springframework.social.config.annotation.SocialConfigurer;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.ConnectionRepository;
import org.springframework.social.connect.ConnectionSignUp;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository;
import org.springframework.social.connect.web.ConnectController;
import org.springframework.social.facebook.connect.FacebookConnectionFactory;
import org.springframework.social.google.connect.GoogleConnectionFactory;
import org.springframework.social.linkedin.connect.LinkedInConnectionFactory;
import org.springframework.social.security.AuthenticationNameUserIdSource;
import org.springframework.social.twitter.connect.TwitterConnectionFactory;
@Configuration
@EnableSocial
// Load to Environment.
@PropertySource("classpath:social-cfg.properties")
public class SocialConfig implements SocialConfigurer {
private boolean autoSignUp = false;
@Autowired
private DataSource dataSource;
@Autowired
private AppUserDAO appUserDAO;
// @env: read from social-cfg.properties file.
@Override
public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) {
try {
this.autoSignUp = Boolean.parseBoolean(env.getProperty("social.auto-signup"));
} catch (Exception e) {
this.autoSignUp = false;
}
// Twitter
TwitterConnectionFactory tfactory = new TwitterConnectionFactory(//
env.getProperty("twitter.consumer.key"), //
env.getProperty("twitter.consumer.secret"));
// tfactory.setScope(env.getProperty("twitter.scope"));
cfConfig.addConnectionFactory(tfactory);
// Facebook
FacebookConnectionFactory ffactory = new FacebookConnectionFactory(//
env.getProperty("facebook.app.id"), //
env.getProperty("facebook.app.secret"));
ffactory.setScope(env.getProperty("facebook.scope"));
// auth_type=reauthenticate
cfConfig.addConnectionFactory(ffactory);
// Linkedin
LinkedInConnectionFactory lfactory = new LinkedInConnectionFactory(//
env.getProperty("linkedin.consumer.key"), //
env.getProperty("linkedin.consumer.secret"));
lfactory.setScope(env.getProperty("linkedin.scope"));
cfConfig.addConnectionFactory(lfactory);
// Google
GoogleConnectionFactory gfactory = new GoogleConnectionFactory(//
env.getProperty("google.client.id"), //
env.getProperty("google.client.secret"));
gfactory.setScope(env.getProperty("google.scope"));
cfConfig.addConnectionFactory(gfactory);
}
// The UserIdSource determines the userID of the user.
@Override
public UserIdSource getUserIdSource() {
return new AuthenticationNameUserIdSource();
}
// USERCONNECTION.
@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
// org.springframework.social.security.SocialAuthenticationServiceRegistry
JdbcUsersConnectionRepository usersConnectionRepository = new JdbcUsersConnectionRepository(dataSource,
connectionFactoryLocator,
Encryptors.noOpText());
if (autoSignUp) {
// After logging in to social networking.
// Automatically creates corresponding APP_USER if it does not exist.
ConnectionSignUp connectionSignUp = new ConnectionSignUpImpl(appUserDAO);
usersConnectionRepository.setConnectionSignUp(connectionSignUp);
} else {
// After logging in to social networking.
// If the corresponding APP_USER record is not found.
// Navigate to registration page.
usersConnectionRepository.setConnectionSignUp(null);
}
return usersConnectionRepository;
}
// This bean manages the connection flow between the account provider
// and the example application.
@Bean
public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator, //
ConnectionRepository connectionRepository) {
return new ConnectController(connectionFactoryLocator, connectionRepository);
}
}
Xem thêm cách đăng ký chứng chỉ (certificate) OAuth2 với một vài mạng xã hội khác nhau:
5. Entity & DAO
Lần đầu tiên khi bạn chạy ứng dụng này, các bảng sẽ được tạo ra dựa trên các Entity.

Bảng USERCONNECTION sẽ được tạo ra dựa trên lớp UserConnection, bảng này sẽ được sử dụng tự động bởi Spring Social API, nó lưu trữ các thông tin công khai của người dùng lấy được từ mạng xã hội như ProviderId, ProviderUserId, Displayname, Imageurl, ... Bạn không được thay đổi cấu trúc của bảng này.
UserConnection.java
package org.o7planning.sbsocial.entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "Userconnection")
public class UserConnection implements Serializable {
private static final long serialVersionUID = -6991752510891411572L;
@Id
@Column(name = "Userid", length = 255, nullable = false)
private String userId;
@Id
@Column(name = "Providerid", length = 255, nullable = false)
private String providerId;
@Id
@Column(name = "Provideruserid", length = 255, nullable = false)
private String providerUserId;
@Column(name = "Rank", nullable = false)
private int rank;
@Column(name = "Displayname", length = 255, nullable = true)
private String displayName;
@Column(name = "Profileurl", length = 512, nullable = true)
private String profileUrl;
@Column(name = "Imageurl", length = 512, nullable = true)
private String imageUrl;
@Column(name = "Accesstoken", length = 512, nullable = true)
private String accessToken;
@Column(name = "Secret", length = 512, nullable = true)
private String secret;
@Column(name = "Refreshtoken", length = 512, nullable = true)
private String refreshToken;
@Column(name = "Expiretime", nullable = true)
private Long expireTime;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
public String getProviderUserId() {
return providerUserId;
}
public void setProviderUserId(String providerUserId) {
this.providerUserId = providerUserId;
}
public int getRank() {
return rank;
}
public void setRank(int rank) {
this.rank = rank;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getProfileUrl() {
return profileUrl;
}
public void setProfileUrl(String profileUrl) {
this.profileUrl = profileUrl;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public Long getExpireTime() {
return expireTime;
}
public void setExpireTime(Long expireTime) {
this.expireTime = expireTime;
}
}
Bảng PERSISTENT_LOGINS sẽ được tạo ra dựa trên lớp PersistentLogin, bảng này sẽ được sử dụng tự động bởi Spring Security Remember Me. Bạn không được thay đổi cấu trúc của bảng này.
PersistentLogin.java
package org.o7planning.sbsocial.entity;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity
@Table(name = "persistent_logins")
public class PersistentLogin {
@Id
@Column(name = "Series", length = 64, nullable = false)
private String series;
@Column(name = "Username", length = 64, nullable = false)
private String userName;
@Column(name = "Token", length = 64, nullable = false)
private String token;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "Last_Used", nullable = false)
private Date lastUsed;
public String getSeries() {
return series;
}
public void setSeries(String series) {
this.series = series;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public Date getLastUsed() {
return lastUsed;
}
public void setLastUsed(Date lastUsed) {
this.lastUsed = lastUsed;
}
}
Bảng APP_USER, APP_ROLE, USER_ROLE sẽ được tự động tạo ra dựa trên các lớp AppUser, AppRole, UserRole:
AppUser.java
package org.o7planning.sbsocial.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
@Entity
@Table(name = "App_User", //
uniqueConstraints = { //
@UniqueConstraint(name = "APP_USER_UK", columnNames = "User_Name"),
@UniqueConstraint(name = "APP_USER_UK2", columnNames = "Email") })
public class AppUser {
@Id
@GeneratedValue
@Column(name = "User_Id", nullable = false)
private Long userId;
@Column(name = "User_Name", length = 36, nullable = false)
private String userName;
@Column(name = "Email", length = 128, nullable = false)
private String email;
@Column(name = "First_Name", length = 36, nullable = true)
private String firstName;
@Column(name = "Last_Name", length = 36, nullable = true)
private String lastName;
@Column(name = "Encryted_Password", length = 128, nullable = false)
private String encrytedPassword;
@Column(name = "Enabled", length = 1, nullable = false)
private boolean enabled;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
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 getEncrytedPassword() {
return encrytedPassword;
}
public void setEncrytedPassword(String encrytedPassword) {
this.encrytedPassword = encrytedPassword;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
AppRole.java
package org.o7planning.sbsocial.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
@Entity
@Table(name = "App_Role", //
uniqueConstraints = { //
@UniqueConstraint(name = "APP_ROLE_UK", columnNames = "Role_Name") })
public class AppRole {
public static final String ROLE_USER = "ROLE_USER";
public static final String ROLE_ADMIN = "ROLE_ADMIN";
@Id
@GeneratedValue
@Column(name = "Role_Id", nullable = false)
private Long roleId;
@Column(name = "Role_Name", length = 30, nullable = false)
private String roleName;
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
}
UserRole.java
package org.o7planning.sbsocial.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
@Entity
@Table(name = "User_Role", //
uniqueConstraints = { //
@UniqueConstraint(name = "USER_ROLE_UK", //
columnNames = { "User_Id", "Role_Id" }) })
public class UserRole {
@Id
@GeneratedValue
@Column(name = "Id", nullable = false)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "User_Id", nullable = false)
private AppUser appUser;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "Role_Id", nullable = false)
private AppRole appRole;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public AppUser getAppUser() {
return appUser;
}
public void setAppUser(AppUser appUser) {
this.appUser = appUser;
}
public AppRole getAppRole() {
return appRole;
}
public void setAppRole(AppRole appRole) {
this.appRole = appRole;
}
}

AppUserDAO.java
package org.o7planning.sbsocial.dao;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import org.o7planning.sbsocial.entity.AppRole;
import org.o7planning.sbsocial.entity.AppUser;
import org.o7planning.sbsocial.form.AppUserForm;
import org.o7planning.sbsocial.utils.EncrytedPasswordUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionKey;
import org.springframework.social.connect.UserProfile;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository
@Transactional
public class AppUserDAO {
@Autowired
private EntityManager entityManager;
@Autowired
private AppRoleDAO appRoleDAO;
public AppUser findAppUserByUserId(Long userId) {
try {
String sql = "Select e from " + AppUser.class.getName() + " e " //
+ " Where e.userId = :userId ";
Query query = entityManager.createQuery(sql, AppUser.class);
query.setParameter("userId", userId);
return (AppUser) query.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
public AppUser findAppUserByUserName(String userName) {
try {
String sql = "Select e from " + AppUser.class.getName() + " e " //
+ " Where e.userName = :userName ";
Query query = entityManager.createQuery(sql, AppUser.class);
query.setParameter("userName", userName);
return (AppUser) query.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
public AppUser findByEmail(String email) {
try {
String sql = "Select e from " + AppUser.class.getName() + " e " //
+ " Where e.email = :email ";
Query query = entityManager.createQuery(sql, AppUser.class);
query.setParameter("email", email);
return (AppUser) query.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
private String findAvailableUserName(String userName_prefix) {
AppUser account = this.findAppUserByUserName(userName_prefix);
if (account == null) {
return userName_prefix;
}
int i = 0;
while (true) {
String userName = userName_prefix + "_" + i++;
account = this.findAppUserByUserName(userName);
if (account == null) {
return userName;
}
}
}
// Auto create App User Account.
public AppUser createAppUser(Connection<?> connection) {
ConnectionKey key = connection.getKey();
// (facebook,12345), (google,123) ...
System.out.println("key= (" + key.getProviderId() + "," + key.getProviderUserId() + ")");
UserProfile userProfile = connection.fetchUserProfile();
String email = userProfile.getEmail();
AppUser appUser = this.findByEmail(email);
if (appUser != null) {
return appUser;
}
String userName_prefix = userProfile.getFirstName().trim().toLowerCase()//
+ "_" + userProfile.getLastName().trim().toLowerCase();
String userName = this.findAvailableUserName(userName_prefix);
//
// Random Password! TODO: Need send email to User!
//
String randomPassword = UUID.randomUUID().toString().substring(0, 5);
String encrytedPassword = EncrytedPasswordUtils.encrytePassword(randomPassword);
//
appUser = new AppUser();
appUser.setEnabled(true);
appUser.setEncrytedPassword(encrytedPassword);
appUser.setUserName(userName);
appUser.setEmail(email);
appUser.setFirstName(userProfile.getFirstName());
appUser.setLastName(userProfile.getLastName());
this.entityManager.persist(appUser);
// Create default Role
List<String> roleNames = new ArrayList<String>();
roleNames.add(AppRole.ROLE_USER);
this.appRoleDAO.createRoleFor(appUser, roleNames);
return appUser;
}
public AppUser registerNewUserAccount(AppUserForm appUserForm, List<String> roleNames) {
AppUser appUser = new AppUser();
appUser.setUserName(appUserForm.getUserName());
appUser.setEmail(appUserForm.getEmail());
appUser.setFirstName(appUserForm.getFirstName());
appUser.setLastName(appUserForm.getLastName());
appUser.setEnabled(true);
String encrytedPassword = EncrytedPasswordUtils.encrytePassword(appUserForm.getPassword());
appUser.setEncrytedPassword(encrytedPassword);
this.entityManager.persist(appUser);
this.entityManager.flush();
this.appRoleDAO.createRoleFor(appUser, roleNames);
return appUser;
}
}
AppRoleDAO.java
package org.o7planning.sbsocial.dao;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import org.o7planning.sbsocial.entity.AppRole;
import org.o7planning.sbsocial.entity.AppUser;
import org.o7planning.sbsocial.entity.UserRole;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository
@Transactional
public class AppRoleDAO {
@Autowired
private EntityManager entityManager;
public List<String> getRoleNames(Long userId) {
String sql = "Select ur.appRole.roleName from " + UserRole.class.getName() + " ur " //
+ " where ur.appUser.userId = :userId ";
Query query = this.entityManager.createQuery(sql, String.class);
query.setParameter("userId", userId);
return query.getResultList();
}
public AppRole findAppRoleByName(String roleName) {
try {
String sql = "Select e from " + AppRole.class.getName() + " e " //
+ " where e.roleName = :roleName ";
Query query = this.entityManager.createQuery(sql, AppRole.class);
query.setParameter("roleName", roleName);
return (AppRole) query.getSingleResult();
} catch (NoResultException e) {
return null;
}
}
public void createRoleFor(AppUser appUser, List<String> roleNames) {
//
for (String roleName : roleNames) {
AppRole role = this.findAppRoleByName(roleName);
if (role == null) {
role = new AppRole();
role.setRoleName(AppRole.ROLE_USER);
this.entityManager.persist(role);
this.entityManager.flush();
}
UserRole userRole = new UserRole();
userRole.setAppRole(role);
userRole.setAppUser(appUser);
this.entityManager.persist(userRole);
this.entityManager.flush();
}
}
}
UserConnectionDAO.java
package org.o7planning.sbsocial.dao;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import org.o7planning.sbsocial.entity.UserConnection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository
@Transactional
public class UserConnectionDAO {
@Autowired
private EntityManager entityManager;
public UserConnection findUserConnectionByUserProviderId(String userProviderId) {
try {
String sql = "Select e from " + UserConnection.class.getName() + " e " //
+ " Where e.userProviderId = :userProviderId ";
Query query = entityManager.createQuery(sql, UserConnection.class);
query.setParameter("userProviderId", userProviderId);
List<UserConnection> list = query.getResultList();
return list.isEmpty() ? null : list.get(0);
} catch (NoResultException e) {
return null;
}
}
}
6. Services & Utility

UserDetailsServiceImpl.java
package org.o7planning.sbsocial.service;
import java.util.ArrayList;
import java.util.List;
import org.o7planning.sbsocial.dao.AppRoleDAO;
import org.o7planning.sbsocial.dao.AppUserDAO;
import org.o7planning.sbsocial.entity.AppUser;
import org.o7planning.sbsocial.social.SocialUserDetailsImpl;
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.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;
@Service
@Transactional
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private AppUserDAO appUserDAO;
@Autowired
private AppRoleDAO appRoleDAO;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
System.out.println("UserDetailsServiceImpl.loadUserByUsername=" + userName);
AppUser appUser = this.appUserDAO.findAppUserByUserName(userName);
if (appUser == null) {
System.out.println("User not found! " + userName);
throw new UsernameNotFoundException("User " + userName + " was not found in the database");
}
System.out.println("Found User: " + appUser);
// [ROLE_USER, ROLE_ADMIN,..]
List<String> roleNames = this.appRoleDAO.getRoleNames(appUser.getUserId());
List<GrantedAuthority> grantList = new ArrayList<GrantedAuthority>();
if (roleNames != null) {
for (String role : roleNames) {
// ROLE_USER, ROLE_ADMIN,..
GrantedAuthority authority = new SimpleGrantedAuthority(role);
grantList.add(authority);
}
}
SocialUserDetailsImpl userDetails = new SocialUserDetailsImpl(appUser, roleNames);
return userDetails;
}
}
SocialUserDetailsServiceImpl.java
package org.o7planning.sbsocial.service;
import org.o7planning.sbsocial.social.SocialUserDetailsImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.social.security.SocialUserDetails;
import org.springframework.social.security.SocialUserDetailsService;
import org.springframework.stereotype.Service;
@Service
public class SocialUserDetailsServiceImpl implements SocialUserDetailsService {
@Autowired
private UserDetailsService userDetailService;
// After user created by ConnectionSignUpImpl.execute(Connection<?>)
// This method is called by the Spring Social API.
@Override
public SocialUserDetails loadUserByUserId(String userName) throws UsernameNotFoundException, DataAccessException {
System.out.println("SocialUserDetailsServiceImpl.loadUserByUserId=" + userName);
// See UserDetailServiceImpl.
UserDetails userDetails = ((UserDetailsServiceImpl) userDetailService).loadUserByUsername(userName);
return (SocialUserDetailsImpl) userDetails;
}
}
Trong lần đầu tiên người dùng đăng nhập với tài khoản mạng xã hội của họ, ứng dụng sẽ có được đối tượng Connection, đối tượng này chứa hồ sơ (Profile) của người dùng. Bạn sẽ viết code trong lớp ConnectionSignUpImpl để tạo ra một bản ghi của bảng APP_USER. (Xem thêm code trong lớp SocialConfig).
ConnectionSignUpImpl.java
package org.o7planning.sbsocial.social;
import org.o7planning.sbsocial.dao.AppUserDAO;
import org.o7planning.sbsocial.entity.AppUser;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionSignUp;
public class ConnectionSignUpImpl implements ConnectionSignUp {
private AppUserDAO appUserDAO;
public ConnectionSignUpImpl(AppUserDAO appUserDAO) {
this.appUserDAO = appUserDAO;
}
// After logging in social networking.
// This method will be called to create a corresponding App_User record
// if it does not already exist.
@Override
public String execute(Connection<?> connection) {
AppUser account = appUserDAO.createAppUser(connection);
return account.getUserName();
}
}
SocialUserDetailsImpl.java
package org.o7planning.sbsocial.social;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.o7planning.sbsocial.entity.AppUser;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.social.security.SocialUserDetails;
public class SocialUserDetailsImpl implements SocialUserDetails {
private static final long serialVersionUID = -5246117266247684905L;
private List<GrantedAuthority> list = new ArrayList<GrantedAuthority>();
private AppUser appUser;
public SocialUserDetailsImpl(AppUser appUser, List<String> roleNames) {
this.appUser = appUser;
for (String roleName : roleNames) {
GrantedAuthority grant = new SimpleGrantedAuthority(roleName );
this.list.add(grant);
}
}
@Override
public String getUserId() {
return this.appUser.getUserId() + "";
}
@Override
public String getUsername() {
return appUser.getUserName();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return list;
}
@Override
public String getPassword() {
return appUser.getEncrytedPassword();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
EncrytedPasswordUtils.java
package org.o7planning.sbsocial.utils;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class EncrytedPasswordUtils {
// Encryte Password with BCryptPasswordEncoder
public static String encrytePassword(String password) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder.encode(password);
}
}
Lớp SecurityUtil chứa phương thức tiện ích để tự động đăng nhập một người dùng vào ứng dụng.
SecurityUtil.java
package org.o7planning.sbsocial.utils;
import java.util.List;
import org.o7planning.sbsocial.entity.AppUser;
import org.o7planning.sbsocial.social.SocialUserDetailsImpl;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.social.security.SocialUserDetails;
public class SecurityUtil {
// Auto Login.
public static void logInUser(AppUser user, List<String> roleNames) {
SocialUserDetails userDetails = new SocialUserDetailsImpl(user, roleNames);
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null,
userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
WebUtils.java
package org.o7planning.sbsocial.utils;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
public class WebUtils {
public static String toString(UserDetails user) {
StringBuilder sb = new StringBuilder();
sb.append("UserName:").append(user.getUsername());
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
if (authorities != null && !authorities.isEmpty()) {
sb.append(" (");
boolean first = true;
for (GrantedAuthority a : authorities) {
if (first) {
sb.append(a.getAuthority());
first = false;
} else {
sb.append(", ").append(a.getAuthority());
}
}
sb.append(")");
}
return sb.toString();
}
}
7. Form, Validator, Controller

AppUserForm là một lớp đại diện cho dữ liệu mà người dùng sẽ nhập vào khi đăng ký một tài khoản mới.
AppUserForm.java
package org.o7planning.sbsocial.form;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionKey;
import org.springframework.social.connect.UserProfile;
public class AppUserForm {
private Long userId;
private String email;
private String userName;
private String firstName;
private String lastName;
private String password;
private String role;
private String signInProvider;
private String providerUserId;
public AppUserForm() {
}
public AppUserForm(Connection<?> connection) {
UserProfile socialUserProfile = connection.fetchUserProfile();
this.userId = null;
this.email = socialUserProfile.getEmail();
this.userName = socialUserProfile.getUsername();
this.firstName = socialUserProfile.getFirstName();
this.lastName = socialUserProfile.getLastName();
ConnectionKey key = connection.getKey();
// google, facebook, twitter
this.signInProvider = key.getProviderId();
// ID of User on google, facebook, twitter.
// ID của User trên google, facebook, twitter.
this.providerUserId = key.getProviderUserId();
}
public Long getUserId() {
return userId;
}
public void setUserId(Long id) {
this.userId = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
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 getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getSignInProvider() {
return signInProvider;
}
public void setSignInProvider(String signInProvider) {
this.signInProvider = signInProvider;
}
public String getProviderUserId() {
return providerUserId;
}
public void setProviderUserId(String providerUserId) {
this.providerUserId = providerUserId;
}
}
AppUserFormValidator được sử dụng để kiểm tra dữ liệu mà người dùng nhập vào khi đăng ký tài khoản mới.
AppUserValidator.java
package org.o7planning.sbsocial.validator;
import org.apache.commons.validator.routines.EmailValidator;
import org.o7planning.sbsocial.dao.AppUserDAO;
import org.o7planning.sbsocial.entity.AppUser;
import org.o7planning.sbsocial.form.AppUserForm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
@Component
public class AppUserValidator implements Validator {
// common-validator library.
private EmailValidator emailValidator = EmailValidator.getInstance();
@Autowired
private AppUserDAO appUserDAO;
@Override
public boolean supports(Class<?> clazz) {
return clazz == AppUserForm.class;
}
@Override
public void validate(Object target, Errors errors) {
AppUserForm form = (AppUserForm) target;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email", "", "Email is required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName", "", "User name is required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "", "First name is required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "", "Last name is required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "", "Password is required");
if (errors.hasErrors()) {
return;
}
if (!emailValidator.isValid(form.getEmail())) {
errors.rejectValue("email", "", "Email is not valid");
return;
}
AppUser userAccount = appUserDAO.findAppUserByUserName( form.getUserName());
if (userAccount != null) {
if (form.getUserId() == null) {
errors.rejectValue("userName", "", "User name is not available");
return;
} else if (!form.getUserId().equals(userAccount.getUserId() )) {
errors.rejectValue("userName", "", "User name is not available");
return;
}
}
userAccount = appUserDAO.findByEmail(form.getEmail());
if (userAccount != null) {
if (form.getUserId() == null) {
errors.rejectValue("email", "", "Email is not available");
return;
} else if (!form.getUserId().equals(userAccount.getUserId() )) {
errors.rejectValue("email", "", "Email is not available");
return;
}
}
}
}
MainController.java
package org.o7planning.sbsocial.controller;
import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
import org.o7planning.sbsocial.dao.AppUserDAO;
import org.o7planning.sbsocial.entity.AppRole;
import org.o7planning.sbsocial.entity.AppUser;
import org.o7planning.sbsocial.form.AppUserForm;
import org.o7planning.sbsocial.utils.SecurityUtil;
import org.o7planning.sbsocial.utils.WebUtils;
import org.o7planning.sbsocial.validator.AppUserValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.web.ProviderSignInUtils;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
@Transactional
public class MainController {
@Autowired
private AppUserDAO appUserDAO;
@Autowired
private ConnectionFactoryLocator connectionFactoryLocator;
@Autowired
private UsersConnectionRepository connectionRepository;
@Autowired
private AppUserValidator appUserValidator;
@InitBinder
protected void initBinder(WebDataBinder dataBinder) {
// Form target
Object target = dataBinder.getTarget();
if (target == null) {
return;
}
System.out.println("Target=" + target);
if (target.getClass() == AppUserForm.class) {
dataBinder.setValidator(appUserValidator);
}
// ...
}
@RequestMapping(value = { "/", "/welcome" }, method = RequestMethod.GET)
public String welcomePage(Model model) {
model.addAttribute("title", "Welcome");
model.addAttribute("message", "This is welcome page!");
return "welcomePage";
}
@RequestMapping(value = "/admin", method = RequestMethod.GET)
public String adminPage(Model model, Principal principal) {
// After user login successfully.
String userName = principal.getName();
System.out.println("User Name: " + userName);
UserDetails loginedUser = (UserDetails) ((Authentication) principal).getPrincipal();
String userInfo = WebUtils.toString(loginedUser);
model.addAttribute("userInfo", userInfo);
return "adminPage";
}
@RequestMapping(value = "/logoutSuccessful", method = RequestMethod.GET)
public String logoutSuccessfulPage(Model model) {
model.addAttribute("title", "Logout");
return "logoutSuccessfulPage";
}
@RequestMapping(value = "/userInfo", method = RequestMethod.GET)
public String userInfo(Model model, Principal principal) {
// After user login successfully.
String userName = principal.getName();
System.out.println("User Name: " + userName);
UserDetails loginedUser = (UserDetails) ((Authentication) principal).getPrincipal();
String userInfo = WebUtils.toString(loginedUser);
model.addAttribute("userInfo", userInfo);
return "userInfoPage";
}
@RequestMapping(value = "/403", method = RequestMethod.GET)
public String accessDenied(Model model, Principal principal) {
if (principal != null) {
UserDetails loginedUser = (UserDetails) ((Authentication) principal).getPrincipal();
String userInfo = WebUtils.toString(loginedUser);
model.addAttribute("userInfo", userInfo);
String message = "Hi " + principal.getName() //
+ "<br> You do not have permission to access this page!";
model.addAttribute("message", message);
}
return "403Page";
}
@RequestMapping(value = { "/login" }, method = RequestMethod.GET)
public String login(Model model) {
return "loginPage";
}
// User login with social networking,
// but does not allow the app to view basic information
// application will redirect to page / signin.
@RequestMapping(value = { "/signin" }, method = RequestMethod.GET)
public String signInPage(Model model) {
return "redirect:/login";
}
@RequestMapping(value = { "/signup" }, method = RequestMethod.GET)
public String signupPage(WebRequest request, Model model) {
ProviderSignInUtils providerSignInUtils //
= new ProviderSignInUtils(connectionFactoryLocator, connectionRepository);
// Retrieve social networking information.
Connection<?> connection = providerSignInUtils.getConnectionFromSession(request);
//
AppUserForm myForm = null;
//
if (connection != null) {
myForm = new AppUserForm(connection);
} else {
myForm = new AppUserForm();
}
model.addAttribute("myForm", myForm);
return "signupPage";
}
@RequestMapping(value = { "/signup" }, method = RequestMethod.POST)
public String signupSave(WebRequest request, //
Model model, //
@ModelAttribute("myForm") @Validated AppUserForm appUserForm, //
BindingResult result, //
final RedirectAttributes redirectAttributes) {
// Validation error.
if (result.hasErrors()) {
return "signupPage";
}
List<String> roleNames = new ArrayList<String>();
roleNames.add(AppRole.ROLE_USER);
AppUser registered = null;
try {
registered = appUserDAO.registerNewUserAccount(appUserForm, roleNames);
} catch (Exception ex) {
ex.printStackTrace();
model.addAttribute("errorMessage", "Error " + ex.getMessage());
return "signupPage";
}
if (appUserForm.getSignInProvider() != null) {
ProviderSignInUtils providerSignInUtils //
= new ProviderSignInUtils(connectionFactoryLocator, connectionRepository);
// (Spring Social API):
// If user login by social networking.
// This method saves social networking information to the UserConnection table.
providerSignInUtils.doPostSignUp(registered.getUserName(), request);
}
// After registration is complete, automatic login.
SecurityUtil.logInUser(registered, roleNames);
return "redirect:/userInfo";
}
}
8. View (Thymeleaf)

_menu.html
<div xmlns:th="http://www.thymeleaf.org"
style="border: 1px solid #ccc;padding:5px;margin-bottom:20px;">
<a th:href="@{/}">Home</a>
|
<a th:href="@{/userInfo}">User Info</a>
|
<a th:href="@{/admin}">Admin</a>
|
<a th:if="${#request.userPrincipal != null}" th:href="@{/logout}">Logout</a>
</div>
403Page.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Access Denied</title>
</head>
<body>
<!-- Include _menu.html -->
<th:block th:include="/_menu"></th:block>
<h3 th:if="${message != null}" th:utext="${message}" style="color: red;"></h3>
<div th:if="${userInfo != null}" th:utext="${userInfo}"></div>
</body>
</html>
adminPage.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:utext="${title}"></title>
</head>
<body>
<!-- Include _menu.html -->
<th:block th:include="/_menu"></th:block>
<h2>Admin Page</h2>
<h3>Welcome :
<span th:utext="${#request.userPrincipal.name}"></span>
</h3>
<b>This is protected page!</b>
<br/><br/>
<div th:if="${userInfo != null}" th:utext="${userInfo}"></div>
</body>
</html>
loginPage.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login</title>
</head>
<body>
<!-- Include _menu.html -->
<th:block th:include="/_menu"></th:block>
<h1>Login</h1>
<h2>Social Login</h2>
<a th:href="@{/auth/facebook}">Face Book</a>
<br />
<a th:href="@{/auth/google}">Google</a>
<br />
<!-- /login?error=true -->
<div th:if="${#request.getParameter('error') == 'true'}"
style="color:red;margin:10px 0px;">
Login Failed!!!<br />
Reason :
<span th:if="${#session!= null and #session.getAttribute('SPRING_SECURITY_LAST_EXCEPTION') != null}"
th:utext="${#session.getAttribute('SPRING_SECURITY_LAST_EXCEPTION').message}">
Static summary
</span>
</div>
<h3>Enter user name and password:</h3>
<form name='f' th:action="@{/j_spring_security_check}" method='POST'>
<table>
<tr>
<td>User:</td>
<td><input type='text' name='username' value=''></td>
</tr>
<tr>
<td>Password:</td>
<td><input type='password' name='password' /></td>
</tr>
<tr>
<td>Remember Me?</td>
<td><input type="checkbox" name="remember-me" /></td>
</tr>
<tr>
<td><input name="submit" type="submit" value="submit" /></td>
</tr>
</table>
</form>
<br>
Username/pass:
<ul>
<li>dbuser1/123</li>
<li>dbadmin1/123</li>
</ul>
</body>
</html>
loginSuccessfulPage.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Logout</title>
</head>
<body>
<!-- Include _menu.html -->
<th:block th:include="/_menu"></th:block>
<h1>Logout Successful!</h1>
</body>
</html>
signupPage.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Sign Up</title>
<style>
.error-message {
font-size:90%;
font-style:italic;
color:red;
}
</style>
</head>
<body>
<!-- Include _menu.html -->
<th:block th:include="/_menu"></th:block>
<h1>Register</h1>
<h2 style="color:blue;"
th:if="${myForm.signInProvider != null}">Signup with <span th:utex="${myForm.signInProvider}"></span></h2>
<form th:object="${myForm}" method="POST">
<input type="hidden" th:field="*{userId}" />
<input type="hidden" th:field="*{signInProvider}" />
<table border="0">
<tr>
<td>User Name</td>
<td><input th:field="*{userName}" /></td>
<td>
<span class="error-message"
th:if="${#fields.hasErrors('userName')}" th:errors="*{userName}">..</span>
</td>
</tr>
<tr>
<td>Email</td>
<td><input th:field="*{email}" /></td>
<td>
<span class="error-message"
th:if="${#fields.hasErrors('email')}" th:errors="*{email}">..</span>
</td>
</tr>
<tr>
<td>First Name</td>
<td><input th:field="*{firstName}" /></td>
<td>
<span class="error-message"
th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}">..</span>
</td>
</tr>
<tr>
<td>Last Name</td>
<td><input th:field="*{lastName}" /></td>
<td>
<span class="error-message"
th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}">..</span>
</td>
</tr>
<tr>
<td>Password</td>
<td><input th:field="*{password}" /></td>
<td>
<span class="error-message"
th:if="${#fields.hasErrors('password')}" th:errors="*{password}">..</span>
</td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Submit" /></td>
<td></td>
</tr>
</table>
</form>
<div class="error-message" th:utext="${errorMessage}"></div>
</body>
</html>
userInfoPage.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>User Info</title>
</head>
<body>
<!-- Include _menu.html -->
<th:block th:include="/_menu"></th:block>
<h2>User Info Page</h2>
<h3>Welcome : <span th:utext="${#request.userPrincipal.name}"></span></h3>
<b>This is protected page!</b>
<br/><br/>
<div th:if="${userInfo != null}" th:utext="${userInfo}"></div>
</body>
</html>
welcomePage.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:utext="${title}"></title>
</head>
<body>
<!-- Include _menu.html -->
<th:block th:include="/_menu"></th:block>
<h2>Message : <span th:utext="${message}"></span></h2>
</body>
</html>
Các hướng dẫn Spring Boot
- Cài đặt Spring Tool Suite cho Eclipse
- Hướng dẫn lập trình Spring cho người mới bắt đầu
- Hướng dẫn lập trình Spring Boot cho người mới bắt đầu
- Các thuộc tính thông dụng của Spring Boot
- Hướng dẫn sử dụng Spring Boot và Thymeleaf
- Hướng dẫn sử dụng Spring Boot và FreeMarker
- Hướng dẫn sử dụng Spring Boot và Groovy
- Hướng dẫn sử dụng Spring Boot và Mustache
- Hướng dẫn sử dụng Spring Boot và JSP
- Hướng dẫn sử dụng Spring Boot, Apache Tiles, JSP
- Sử dụng Logging trong Spring Boot
- Giám sát ứng dụng với Spring Boot Actuator
- Tạo ứng dụng web đa ngôn ngữ với Spring Boot
- Sử dụng nhiều ViewResolver trong Spring Boot
- Sử dụng Twitter Bootstrap trong Spring Boot
- Hướng dẫn và ví dụ Spring Boot Interceptor
- Hướng dẫn sử dụng Spring Boot, Spring JDBC và Spring Transaction
- Hướng dẫn và ví dụ Spring JDBC
- Hướng dẫn sử dụng Spring Boot, JPA và Spring Transaction
- Hướng dẫn sử dụng Spring Boot và Spring Data JPA
- Hướng dẫn sử dụng Spring Boot, Hibernate và Spring Transaction
- Tương tác Spring Boot, JPA và cơ sở dữ liệu H2
- Hướng dẫn sử dụng Spring Boot và MongoDB
- Sử dụng nhiều DataSource với Spring Boot và JPA
- Sử dụng nhiều DataSource với Spring Boot và RoutingDataSource
- Tạo ứng dụng Login với Spring Boot, Spring Security, Spring JDBC
- Tạo ứng dụng Login với Spring Boot, Spring Security, JPA
- Tạo ứng dụng đăng ký tài khoản với Spring Boot, Spring Form Validation
- Ví dụ OAuth2 Social Login trong Spring Boot
- Chạy các nhiệm vụ nền theo lịch trình trong Spring
- Ví dụ CRUD Restful Web Service với Spring Boot
- Ví dụ Spring Boot Restful Client với RestTemplate
- Ví dụ CRUD với Spring Boot, REST và AngularJS
- Bảo mật Spring Boot RESTful Service sử dụng Basic Authentication
- Bảo mật Spring Boot RESTful Service sử dụng Auth0 JWT
- Ví dụ Upload file với Spring Boot
- Ví dụ Download file với Spring Boot
- Ví dụ Upload file với Spring Boot và jQuery Ajax
- Ví dụ Upload file với Spring Boot và AngularJS
- Tạo ứng dụng Web bán hàng với Spring Boot, Hibernate
- Hướng dẫn và ví dụ Spring Email
- Tạo ứng dụng Chat đơn giản với Spring Boot và Websocket
- Triển khai ứng dụng Spring Boot trên Tomcat Server
- Triển khai ứng dụng Spring Boot trên Oracle WebLogic Server
- Cài đặt chứng chỉ SSL miễn phí Let's Encrypt cho Spring Boot
- Cấu hình Spring Boot chuyển hướng HTTP sang HTTPS
- Tìm nạp dữ liệu với Spring Data JPA DTO Projections
Show More