Code ví dụ Spring Boot Security login bằng Google (Gmail)
(Xem lại: Code ví dụ JSP Servlet login bằng Google (Gmail/Google+))
(Xem thêm: Code ví dụ Spring Boot Security login bằng Facebook)
Các công nghệ sử dụng:
- Spring Boot 2.0.1 + Spring Security
- Maven
- JDK 1.8
- Eclipse + Spring Tool Suite
- Thymeleaf
Tạo Spring Boot Project
Cấu trúc Project
Thư viện sử dụng:
Mình sử dụng thêm thư viện httpcomponents để gửi request bên trong code java.
<!-- org.apache.httpcomponents --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpcore</artifactId> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>fluent-hc</artifactId> </dependency>
Tạo ứng dụng/project trên google để sử dụng chức năng login
Ở đây mình tạo ứng dụng “stackajva-demo-login” với:
- Client ID = 80724656105-fg2ndheoujm7c7dd4ob1i9mq3ebdbjhb.apps.googleusercontent.com
- Client Secret = PrmoOSPhKsilVqpzxrzjOoEU
(Xem lại: Tạo ứng dụng google+ để đăng nhập thay tài khoản)
File application.properties
google.app.id=80724656105-fg2ndheoujm7c7dd4ob1i9mq3ebdbjhb.apps.googleusercontent.com google.app.secret=PrmoOSPhKsilVqpzxrzjOoEU google.redirect.uri=http://localhost:8080/login-google google.link.get.token=https://accounts.google.com/o/oauth2/token google.link.get.user_info=https://www.googleapis.com/oauth2/v1/userinfo?access_token=
File cấu hình security
package stackjava.com.sbgoogle.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public BCryptPasswordEncoder passwordEncoder() { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); return bCryptPasswordEncoder; } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().passwordEncoder(passwordEncoder()). withUser("kai").password("$2a$04$Q2Cq0k57zf2Vs/n3JXwzmerql9RzElr.J7aQd3/Sq0fw/BdDFPAj.").roles("ADMIN"); auth.inMemoryAuthentication().passwordEncoder(passwordEncoder()). withUser("sena").password("$2a$04$Q2Cq0k57zf2Vs/n3JXwzmerql9RzElr.J7aQd3/Sq0fw/BdDFPAj.").roles("USER"); // auth.inMemoryAuthentication().passwordEncoder(NoOpPasswordEncoder.getInstance()).withUser("sena").password("123456").roles("USER"); } @Override protected void configure(HttpSecurity http) throws Exception { // Chỉ cho phép user có quyền ADMIN truy cập đường dẫn /admin/** http.authorizeRequests().antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')"); // Chỉ cho phép user có quyền ADMIN hoặc USER truy cập đường dẫn /user/** http.authorizeRequests().antMatchers("/user/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')"); // Khi người dùng đã login, với vai trò USER, Nhưng truy cập vào trang yêu cầu vai trò ADMIN, sẽ chuyển hướng tới trang /403 http.authorizeRequests().and().exceptionHandling().accessDeniedPage("/403"); // Cấu hình cho Login Form. http.authorizeRequests().and().formLogin()// .loginProcessingUrl("/j_spring_security_login")// .loginPage("/login")// .defaultSuccessUrl("/user")// .failureUrl("/login?message=error")// .usernameParameter("username")// .passwordParameter("password") // Cấu hình cho Logout Page. .and().logout().logoutUrl("/j_spring_security_logout").logoutSuccessUrl("/login?message=logout"); } }
File Controller.
package stackjava.com.sbgoogle.controller; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import org.apache.http.client.ClientProtocolException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import stackjava.com.sbgoogle.common.GooglePojo; import stackjava.com.sbgoogle.common.GoogleUtils; @Controller public class BaseController { @Autowired private GoogleUtils googleUtils; @RequestMapping(value = { "/", "/login" }) public String login() { return "login"; } @RequestMapping("/login-google") public String loginGoogle(HttpServletRequest request) throws ClientProtocolException, IOException { String code = request.getParameter("code"); if (code == null || code.isEmpty()) { return "redirect:/login?google=error"; } String accessToken = googleUtils.getToken(code); GooglePojo googlePojo = googleUtils.getUserInfo(accessToken); UserDetails userDetail = googleUtils.buildUser(googlePojo); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetail, null, userDetail.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); return "redirect:/user"; } @RequestMapping("/user") public String user() { return "user"; } @RequestMapping("/admin") public String admin() { return "admin"; } @RequestMapping("/403") public String accessDenied() { return "403"; } }
Method loginGoogle xử lý kết quả trả về từ google
- Lấy code mà google 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 google:
package stackjava.com.sbgoogle.common; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.fluent.Form; import org.apache.http.client.fluent.Request; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @Component public class GoogleUtils { @Autowired private Environment env; public String getToken(final String code) throws ClientProtocolException, IOException { String link = env.getProperty("google.link.get.token"); String response = Request.Post(link) .bodyForm(Form.form().add("client_id", env.getProperty("google.app.id")) .add("client_secret", env.getProperty("google.app.secret")) .add("redirect_uri", env.getProperty("google.redirect.uri")).add("code", code) .add("grant_type", "authorization_code").build()) .execute().returnContent().asString(); ObjectMapper mapper = new ObjectMapper(); JsonNode node = mapper.readTree(response).get("access_token"); return node.textValue(); } public GooglePojo getUserInfo(final String accessToken) throws ClientProtocolException, IOException { String link = env.getProperty("google.link.get.user_info") + accessToken; String response = Request.Get(link).execute().returnContent().asString(); ObjectMapper mapper = new ObjectMapper(); GooglePojo googlePojo = mapper.readValue(response, GooglePojo.class); System.out.println(googlePojo); return googlePojo; } public UserDetails buildUser(GooglePojo googlePojo) { boolean enabled = true; boolean accountNonExpired = true; boolean credentialsNonExpired = true; boolean accountNonLocked = true; List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); authorities.add(new SimpleGrantedAuthority("ROLE_USER")); UserDetails userDetail = new User(googlePojo.getEmail(), "", enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities); return userDetail; } }
File GooglePojo.java
Dùng để chứa các thông tin tài khoản (email, name…) gửi về từ google.
package stackjava.com.sbgoogle.common; public class GooglePojo { private String id; private String email; private boolean verified_email; private String name; private String given_name; private String family_name; private String link; private String picture; // getter-setter }
Các file view:
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Spring Boot Security Hello</title> </head> <body> <h2>Spring Boot Security Login with Google+</h2> <a href="https://accounts.google.com/o/oauth2/auth?scope=email&redirect_uri=http://localhost:8080/login-google&response_type=code &client_id=80724656105-fg2ndheoujm7c7dd4ob1i9mq3ebdbjhb.apps.googleusercontent.com&approval_prompt=force">Login With Gmail</a> <br/> <form name='login-form' th:action="@{/j_spring_security_login}" method='POST'> <table> <tr> <td>Username:</td> <td><input type='text' name='username' value=''></td> </tr> <tr> <td>Password:</td> <td><input type='password' name='password' /></td> </tr> <tr> <td><input name="submit" type="submit" value="submit" /></td> </tr> </table> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> </form> </body> </html>
Đường link https://accounts.google.com/o/oauth2/...force
dùng để gọi hộp thoại đăng nhập và cài đặt URL chuyển hướng
<!DOCTYPE html> <html> <head> <title>Spring Boot Security Hello</title> </head> <body> <h2>User Page</h2> <h3> Welcome : <span th:utext="${#request.userPrincipal.name}"></span> </h3> <a th:href="@{/admin}">Admin Page</a> <br/><br/> <form th:action="@{/j_spring_security_logout}" method="post"> <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" /> <input type="submit" value="Logout" /> </form> </body> </html>
<!DOCTYPE html> <html> <head> <title>Spring Boot Security Hello</title> </head> <body> <h2>Admin Page</h2> <h3> Hello : <span th:utext="${#request.userPrincipal.name}"></span> </h3> <a th:href="@{/user}">User Page</a> <br/><br/> <form th:action="@{/j_spring_security_logout}" method="post"> <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" /> <input type="submit" value="Logout" /> </form> </body> </html>
<!DOCTYPE html> <html> <head> <title>Spring Boot Security Hello</title> </head> <body> <h2>Access Denied Page - 403</h2> <h3> Hi : <span th:utext="${#request.userPrincipal.name} + ' - you do not have permission to access this page'"></span> </h3> <a th:href="@{/user}">User Page</a> <br/><br/> <form th:action="@{/j_spring_security_logout}" method="post"> <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" /> <input type="submit" value="Logout" /> </form> </body> </html>
Demo:
Đăng nhập bình thường bằng tài khoản kai/123456
Đăng nhập bằng tài khoản google
Truy cập trang admin.html bị lỗi vì tài khoản đăng nhập bằng google chỉ có role user.
Code ví dụ Spring Boot Security login bằng Google (Gmail) stackjava.com
Okay, Done!
Download code ví dụ trên tại đây.
References: