Hello Spring Security Java Config
Bài đăng này đã không được cập nhật trong 3 năm
Spring framework đã phát triển Spring Security để hỗ trợ cho việc thực hiện cơ chế Authentication và Authorization trên ứng dụng.
Bài viết này sẽ hướng dẫn các bạn config Spring Security với demo chức năng login đơn giản.
Có 2 cách để config/implement Spring security. Cách thứ nhất là config bean thông qua file .xml và cách thứ 2 là sử dụng Annotations. Cách thứ 2 - sử dụng Annotations được sử dụng nếu bạn có ý định phát triển project web trong khoảng time dài thì nên chọn cách thứ 2. Cách này còn có ưu điêm là rõ ràng hơn. Ngoài ra cách thứ 2 sử dụng thuần Java.
Nếu bạn đọc nào có ý định tìm hiểu sâu hơn về ưu nhược điêm của 2 cách này thì tham khảo ở preview dưới đây : https://spring.io/blog/2013/07/03/spring-security-java-config-preview-web-security/
Trong phạm vi bài viết này mình sẽ mô tả cả 2 cách config.
Spring Security hello world example
Để build project này mình đã config như sau :
- Spring 4.1.3.RELEASE
- Spring Security 4.1.3.RELEASE
- Eclipse Neon.1 Release (4.6.1)
- JDK 1.8
- Maven 3
- Tomcat 7
Cấu trúc thư mục và file sẽ như thế này.
Spring Security Dependencies
Để sử dụng được Spring security, bạn cần cài đặt spring-security-web
và spring-security-config
trong file pom.xml
.
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- Servlet Library -->
<!-- http://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Spring dependencies -->
<!-- http://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.1.RELEASE</version>
</dependency>
<!-- http://mvnrepository.com/artifact/org.springframework/spring-web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.1.RELEASE</version>
</dependency>
<!-- http://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.1.RELEASE</version>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-config -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/jstl/jstl -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
Spring MVC Web Application
Sau đó là 1 controller đơn giản như dưới :
- Nếu URL = /welcome or / thì trả về hello page.
- Nếu URL = /admin thì trả về admin page.
package org.o7planning.hellospringmvc.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class HelloController {
@RequestMapping(value = { "/", "/welcome**" }, method = RequestMethod.GET)
public ModelAndView welcomePage() {
ModelAndView model = new ModelAndView();
model.addObject("title", "Spring Security Hello World");
model.addObject("message", "This is welcome page!");
model.setViewName("hello");
return model;
}
@RequestMapping(value = "/admin**", method = RequestMethod.GET)
public ModelAndView adminPage() {
ModelAndView model = new ModelAndView();
model.addObject("title", "Spring Security Hello World");
model.addObject("message", "This is protected page!");
model.setViewName("admin");
return model;
}
}
Tiếp đến là các file jsp
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@page session="true"%>
<html>
<body>
<h1>Title : ${title}</h1>
<h1>Message : ${message}</h1>
<c:if test="${pageContext.request.userPrincipal.name != null}">
<h2>Welcome : ${pageContext.request.userPrincipal.name}
| <a href="<c:url value="/j_spring_security_logout" />" > Logout</a></h2>
</c:if>
</body>
</html>
<%@page session="false"%>
<html>
<body>
<h1>Title : ${title}</h1>
<h1>Message : ${message}</h1>
</body>
</html>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd">
<context:component-scan base-package="org.o7planning.*" />
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/pages/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
Spring Security : User Authentication
Ở đây mình sẽ setting Spring Security ở file XML.
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-4.1.xsd">
<http auto-config="true">
<intercept-url pattern="/admin**" access="hasRole ('ROLE_USER')" />
</http>
<authentication-manager>
<authentication-provider>
<user-service>
<user name="duong" password="123456" authorities="hasRole ('ROLE_USER')" />
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
Ở đây, chỉ mỗi user "duong" có thể access vào /admin URL.
Integrate Spring Security
Bạn phải khai báo DelegatingFilterProxy
như là một filter servlet trong file web.xml để chặn bất kì request nào đến.
<!-- Spring Security Filter -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
DEMO
http://localhost:8080/HelloSpringMVC/welcome
Khi bạn vào trang admin http://localhost:8080/HelloSpringMVC/admin . Spring Security sẽ chuyển bạn về trnag http://localhost:8080/HelloSpringMVC/login và hiển thị form login.
Nếu username or pwd sai, sẽ có error messages thông báo và Spring sẽ chuyển bạn về đường dẫn http://localhost:8080/HelloSpringMVC/login?error
Nếu login thành công. Spring sẽ chuyển bạn về request ban đầu của bạn (là request vào trang admin) và hiển thị như sau
Sample code
https://github.com/duongichi/HelloSpringMVC/tree/SecurityHARDLOGIN
Spring Security Hello World Annotation Example
Phần trước mình đã sử dụng XML để config Spring security. Phần này mình sẽ chuyển các XML đó thành pure Spring annotation.
Để build project này mình đã config như sau :
- Spring 4.3.3.RELEASE
- Spring Security 4.1.3.RELEASE
- Eclipse Neon.1 Release (4.6.1)
- JDK 1.8
- Maven 3
- Tomcat 7
Cấu trúc thư mục và file sẽ như thế này.
Update pom.xml
Config file pom.xml như dưới đây.
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.o7planning</groupId>
<artifactId>HelloSpringMVC</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>HelloSpringMVC Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- Servlet Library -->
<!-- http://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Spring dependencies -->
<!-- http://mvnrepository.com/artifact/org.springframework/spring-core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<!-- http://mvnrepository.com/artifact/org.springframework/spring-web -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<!-- http://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/jstl/jstl -->
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<build>
<finalName>HelloSpringMVC</finalName>
<plugins>
<!-- Config: Maven Tomcat Plugin -->
<!-- http://mvnrepository.com/artifact/org.apache.tomcat.maven/tomcat7-maven-plugin -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<!-- Config: contextPath and Port (Default: /HelloSpringMVC : 8080) -->
<!--
<configuration>
<path>/</path>
<port>8899</port>
</configuration>
-->
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
Bạn chú ý là vì mình sẽ không sử dụng web.xml trong lần này nên cần phải khai báo maven-war-plugin
. Nếu bạn không khai báo cái này maven build sẽ FAIL.
Spring Security Configuration Class
Bước đầu tiên là add spring security vào application để tạo Spring Security Java Configuration
. Config này sẽ tạo nên Servlet Filter - springSecurityFilterChain
- chịu trách nhiệm cho việc security (chặng URLs, validation, login form v.v...)
package org.o7planning.hellospringmvc.config;
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;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("duong").password("abc123").roles("USER");
auth.inMemoryAuthentication().withUser("admin").password("root123").roles("ADMIN");
auth.inMemoryAuthentication().withUser("dba").password("root123").roles("ADMIN","DBA");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.antMatchers("/admin/**").access("hasRole('ADMIN')")
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.and().formLogin()
.and().exceptionHandling().accessDeniedPage("/Access_Denied");
}
}
Phương thức configureGlobalSecurity sẽ config AuthenticationManagerBuilder
với user credentials và các roles được cho phép. Với AuthenticationManagerBuilder sẽ tạo AuthenticationManager
chịu trách nhiệm xử lý bất kì request nào về authentication.
Phương thức Configure
config HttpSecurity
- cho phép config security cho một http cụ thể nào đó. Default sẽ là tất cả mọi request.
Ở config phía trên. Tất cả mọi user có thể access được vào URL '/' hoặc '/home'. URL '/admin/' chỉ có thể access bởi role ADMIN. URL '/db/' chỉ có thể access bởi roles ADMIN và DBA.
Phương thức 'formLogin' sẽ hỗ trợ form dựa trên authentication và sẽ sinh ra 1 form default (bạn có thể customize form này) bằng cách request user nhập username và pwd.
exceptionHandling().accessDeniedPage()
sẽ cho phép catch tất cả 403 [http access denied] exceptions và hiển thị page tương ứng với lỗi này. Default sẽ là HTTP 403 page.
Config ở trên tương đương với config trong file XML như dưới.
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd">
<http auto-config="true" >
<intercept-url pattern="/" access="permitAll" />
<intercept-url pattern="/home" access="permitAll" />
<intercept-url pattern="/admin**" access="hasRole('ADMIN')" />
<intercept-url pattern="/dba**" access="hasRole('ADMIN') and hasRole('DBA')" />
<form-login authentication-failure-url="/Access_Denied" />
</http>
<authentication-manager >
<authentication-provider>
<user-service>
<user name="duong" password="abc123" authorities="ROLE_USER" />
<user name="admin" password="root123" authorities="ROLE_ADMIN" />
<user name="dba" password="root123" authorities="ROLE_ADMIN,ROLE_DBA" />
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
Register the springSecurityFilter with war
Class dưới đây sẽ đăng ký springSecurityFilter
với application war.
package org.o7planning.hellospringmvc.config;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
}
Config ở trên tương đương với config trong file XML như dưới.
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Add Controller
package org.o7planning.hellospringmvc.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
public class HelloController {
@RequestMapping(value = { "/", "/home" }, method = RequestMethod.GET)
public String homePage(ModelMap model) {
model.addAttribute("greeting", "Hi, Welcome to mysite. ");
return "welcome";
}
@RequestMapping(value = "/admin", method = RequestMethod.GET)
public String adminPage(ModelMap model) {
model.addAttribute("user", getPrincipal());
return "admin";
}
@RequestMapping(value = "/db", method = RequestMethod.GET)
public String dbaPage(ModelMap model) {
model.addAttribute("user", getPrincipal());
return "dba";
}
@RequestMapping(value="/logout", method = RequestMethod.GET)
public String logoutPage (HttpServletRequest request, HttpServletResponse response) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null){
new SecurityContextLogoutHandler().logout(request, response, auth);
}
return "welcome";
}
@RequestMapping(value = "/Access_Denied", method = RequestMethod.GET)
public String accessDeniedPage(ModelMap model) {
model.addAttribute("user", getPrincipal());
return "accessDenied";
}
private String getPrincipal(){
String userName = null;
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
userName = ((UserDetails)principal).getUsername();
} else {
userName = principal.toString();
}
return userName;
}
}
Method getPrincipal
là một function trả lại logged với user name từ SecurityContext
. Method logoutPage
sẽ xử lý action log out đến SecurityContextLogoutHandler().logout(request, response, auth);
.
Bạn sẽ để ý là ko hề có '/login'. Nguyên nhân là nó được sinh ra tự đọng bời Spring Security.
Add SpringMVC Configuration Class
package org.o7planning.hellospringmvc.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "org.o7planning.hellospringmvc")
public class HelloConfig {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/pages/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
Add Initializer class
package org.o7planning.hellospringmvc.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { HelloConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
Add Views
welcome.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>HelloWorld page</title>
</head>
<body>
Greeting : ${greeting}
This is a welcome page.
</body>
</html>
admin.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>HelloWorld Admin page</title>
</head>
<body>
Dear <strong>${user}</strong>, Welcome to Admin Page.
<a href="<c:url value="/logout" />">Logout</a>
</body>
</html>
dba.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>DBA page</title>
</head>
<body>
Dear <strong>${user}</strong>, Welcome to DBA Page.
<a href="<c:url value="/logout" />">Logout</a>
</body>
</html>
accessDenied.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>AccessDenied page</title>
</head>
<body>
Dear <strong>${user}</strong>, You are not authorized to access this page
<a href="<c:url value="/logout" />">Logout</a>
</body>
</html>
DEMO
Bạn truy cập vào đường dẫn mặc định http://localhost:8080/HelloSpringMVC/ . Spring sẽ trả về trang welcome
Khi bạn truy cập vào http://localhost:8080/HelloSpringMVC/admin . Spring security sẽ chuyển hướng bạn đến trang login http://localhost:8080/HelloSpringMVC/login
Ở đây nếu bạn sử dụng user role là USER để login. Spring sẽ trả về bạn text như sau.
hãy logout và thử login lại với user role ADMIN. bạn sẽ nhận được thông báo như dưới.
Nếu bạn vẫn giữ session admin như hiện tại và thử login vào trang db http://localhost:8080/HelloSpringMVC/db . Bạn sẽ nhận đc thông báo như sau.
Sample Code
https://github.com/duongichi/HelloSpringMVC/tree/anotherSecurity
Tham khảo
http://o7planning.org/vi/10129/huong-dan-lap-trinh-spring-mvc-cho-nguoi-moi-bat-dau
All rights reserved