Hello Spring Security Java Config

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 :

  1. Spring 4.1.3.RELEASE
  2. Spring Security 4.1.3.RELEASE
  3. Eclipse Neon.1 Release (4.6.1)
  4. JDK 1.8
  5. Maven 3
  6. Tomcat 7

Cấu trúc thư mục và file sẽ như thế này.

Screenshot from 2016-10-02 15:17:34.png

Spring Security Dependencies

Để sử dụng được Spring security, bạn cần cài đặt spring-security-webspring-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 :

  1. Nếu URL = /welcome or / thì trả về hello page.
  2. 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

Screenshot from 2016-10-02 15:34:24.png

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.

Screenshot from 2016-10-02 15:35:21.png

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

Screenshot from 2016-10-02 15:36:03.png

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

Screenshot from 2016-10-02 15:36:47.png

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 :

  1. Spring 4.3.3.RELEASE
  2. Spring Security 4.1.3.RELEASE
  3. Eclipse Neon.1 Release (4.6.1)
  4. JDK 1.8
  5. Maven 3
  6. Tomcat 7

Cấu trúc thư mục và file sẽ như thế này.

Screenshot from 2016-10-02 15:50:14.png

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

Screenshot from 2016-10-02 16:38:10.png

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

Screenshot from 2016-10-02 16:38:44.png

Ở đây nếu bạn sử dụng user role là USER để login. Spring sẽ trả về bạn text như sau.

Screenshot from 2016-10-02 16:39:38.png

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.

Screenshot from 2016-10-02 16:40:41.png

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.

Screenshot from 2016-10-02 16:41:29.png

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

http://www.mkyong.com/tutorials/spring-security-tutorials/

http://websystique.com/spring-security-tutorial/


All Rights Reserved