הדרכת דוגמה של Spring Security

Spring Security מספק דרכים לביצוע אימות והרשאה ביישום רשת. אנו יכולים להשתמש ב-Spring Security בכל יישום רשת שמבוסס סרבלט.

Spring Security

חלק מהיתרונות של שימוש ב-Spring Security הם:

  1. טכנולוגיה מוכחת, כדאי להשתמש בזה במקום להמציא את הגלגל שוב. אבטחה היא משהו שאנחנו צריכים לטפל בו בתשומת לב נוספת, אחרת היישום שלנו יהיה חשוף לתוקפים.
  2. מונע חלק מהתקפים הנפוצים כגון CSRF, התקפות ייצוב הסשן.
  3. קל לשלב בכל יישום רשת. אנחנו איננו צריכים לשנות את תצורות היישום הרשת, Spring מבצע אוטומטית הכנסה של מסנני אבטחה ליישום הרשת.
  4. מספק תמיכה באימות באמצעות דרכים שונות – בזיכרון, DAO, JDBC, LDAP ועוד רבים.
  5. מספק אפשרות להתעלמות מתבניות כתובת URL מסוימות, מתאים לספק קבצים סטטיים של HTML, תמונות.
  6. תמיכה בקבוצות ותפקידים.

דוגמה ל-Spring Security

ניצור אפליקציית אינטרנט ונשתלב אותה עם Spring Security. ניצור אפליקציית אינטרנט באמצעות האפשרות "Dynamic Web Project" ב־Eclipse, כך שהאפליקציה שלנו תהיה מוכנה. נקפיד להמיר אותה לפרויקט Maven מאחר ואנו משתמשים ב־Maven לבנייה ואיחסון. אם אינך מכיר את השלבים הללו, אנא הסתכל על הדרכה לאפליקציות אינטרנט ב־Java. לאחר שנקבע את אבטחת האפליקציה שלנו, מבנה הפרויקט הסופי יראה כמו בתמונה למטה. נבחן שלושה שיטות אימות ב־spring security.

  1. in-memory
  2. DAO
  3. JDBC

עבור JDBC, אני משתמש במסד נתונים של MySQL ויש לי את התסריט הבא שהורץ כדי ליצור את טבלאות פרטי המשתמשים.

CREATE TABLE `Employees` (
  `username` varchar(20) NOT NULL DEFAULT '',
  `password` varchar(20) NOT NULL DEFAULT '',
  `enabled` tinyint(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `Roles` (
  `username` varchar(20) NOT NULL DEFAULT '',
  `role` varchar(20) NOT NULL DEFAULT '',
  PRIMARY KEY (`username`,`role`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `Employees` (`username`, `password`, `enabled`)
VALUES
	('pankaj', 'pankaj123', 1);

INSERT INTO `Roles` (`username`, `role`)
VALUES
	('pankaj', 'Admin'),
	('pankaj', 'CEO');

commit;

נצטרך גם להגדיר את JDBC DataSource כ־JNDI במשדר סרבלטים שלנו. כדי ללמוד על זה, אנא קרא דוגמת Tomcat JNDI DataSource.

תלויות Maven של Spring Security

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>WebappSpringSecurity</groupId>
	<artifactId>WebappSpringSecurity</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<dependencies>
		<!-- Spring Security Artifacts - START -->
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-web</artifactId>
			<version>3.2.3.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-config</artifactId>
			<version>3.2.3.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-taglibs</artifactId>
			<version>3.0.5.RELEASE</version>
		</dependency>
		<!-- Spring Security Artifacts - END -->

		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet.jsp</groupId>
			<artifactId>jsp-api</artifactId>
			<version>2.1</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>3.0.1</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>1.1.1</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>4.0.2.RELEASE</version>
		</dependency>
	</dependencies>
	<build>
		<sourceDirectory>src</sourceDirectory>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.1</version>
				<configuration>
					<source>1.7</source>
					<target>1.7</target>
				</configuration>
			</plugin>
			<plugin>
				<artifactId>maven-war-plugin</artifactId>
				<version>2.3</version>
				<configuration>
					<warSourceDirectory>WebContent</warSourceDirectory>
					<failOnMissingWebXml>false</failOnMissingWebXml>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>





org.springframework
spring-jdbc






org.springframework.security
spring-security-taglibs




org.springframework.security
spring-security-config




org.springframework.security
spring-security-web


  1. spring-jdbc: משמש לפעולות JDBC על ידי שיטת אימות JDBC. דורש הגדרת DataSource כ-JNDI. לדוגמה מלאה על השימוש בו זמינה ב Spring DataSource JNDI Example
  2. spring-security-taglibs: ספריית תגי Spring Security. השתמשתי בה להצגת תפקידי משתמש בעמוד JSP. ברוב הפעמים, לא תזדקקו לזה גם כן.

  3. spring-security-config: נמצא בשימוש להגדרת ספקי אימות, האם להשתמש ב-JDBC, DAO, LDAP וכו'.

  4. spring-security-web: משרכז את ה-Spring Security עם API של Servlet. אנו זקוקים אליו כדי להכניס את הגדרת האבטחה שלנו ליישום האינטרנט.

Spring Security Example View Pages

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Health Check</title>
</head>
<body>
    <h3>Service is up and running!!</h3>
</body>
</html>

health.html
index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="https://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="https://www.springframework.org/security/tags" prefix="sec" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "https://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Home Page</title>
</head>
<body>
<h3>Home Page</h3>

	<p>
      Hello <b><c:out value="${pageContext.request.remoteUser}"/></b><br>
      Roles: <b><sec:authentication property="principal.authorities" /></b>
    </p>
    
    <form action="logout" method="post">
      <input type="submit" value="Logout" />
      <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
    </form>
</body>
</html>

I have included index.jsp as welcome-file in the application deployment descriptor. Spring Security takes care of CSRF attack, so when we are submitting form for logout, we are sending the CSRF token back to server to delete it. The CSRF object set by Spring Security component is _csrf and we are using it’s property name and token value to pass along in the logout request. Let’s look at the Spring Security configurations now.

דוגמה לשימוש ב-Spring Security במימוש DAO של UserDetailsService

מכיוון שנעשה שימוש באימות מבוסס DAO, אנו צריכים לממש את ממשק UserDetailsService ולספק את המימוש עבור שיטת loadUserByUsername(). מומלץ להשתמש במשאב מסוים לאימות המשתמש, אך לצורך פשטות אני מבצע רק אימות בסיסי. AppUserDetailsServiceDAO.java

package com.journaldev.webapp.spring.dao;

import java.util.Collection;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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;

public class AppUserDetailsServiceDAO implements UserDetailsService {

	protected final Log logger = LogFactory.getLog(getClass());
	
	@Override
	public UserDetails loadUserByUsername(final String username)
			throws UsernameNotFoundException {
		
		logger.info("loadUserByUsername username="+username);
		
		if(!username.equals("pankaj")){
			throw new UsernameNotFoundException(username + " not found");
		}
		
		// יצירת פרטי משתמש דמו, צריך לבצע פעולות JDBC
		return new UserDetails() {
			
			private static final long serialVersionUID = 2059202961588104658L;

			@Override
			public boolean isEnabled() {
				return true;
			}
			
			@Override
			public boolean isCredentialsNonExpired() {
				return true;
			}
			
			@Override
			public boolean isAccountNonLocked() {
				return true;
			}
			
			@Override
			public boolean isAccountNonExpired() {
				return true;
			}
			
			@Override
			public String getUsername() {
				return username;
			}
			
			@Override
			public String getPassword() {
				return "pankaj123";
			}
			
			@Override
			public Collection getAuthorities() {
				List auths = new java.util.ArrayList();
				auths.add(new SimpleGrantedAuthority("admin"));
				return auths;
			}
		};
	}

}

שימו לב שאני יוצר מחלקה פנימית אנונימית של UserDetails ומחזיר אותה. ניתן ליצור מחלקת מימוש עבור זאת ולאחר מכן ליצור ולהחזיר אותה. כללית זו הדרך הנכונה לפעול ביישומונים אמיתיים.

דוגמה למימוש של WebSecurityConfigurer ב-Spring Security

ניתן לממש את ממשק WebSecurityConfigurer או להרחיב את מחלקת המימוש הבסיסית WebSecurityConfigurerAdapter ולדרוס את השיטות. SecurityConfig.java

package com.journaldev.webapp.spring.security;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import com.journaldev.webapp.spring.dao.AppUserDetailsServiceDAO;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Override
	public void configure(AuthenticationManagerBuilder auth)
			throws Exception {

		// אימות בזיכרון
		// auth.inMemoryAuthentication().withUser("pankaj").password("pankaj123").roles("USER");

		// בשימוש במותאם אישית UserDetailsService DAO
		// auth.userDetailsService(new AppUserDetailsServiceDAO());

		// בשימוש ב-JDBC
		Context ctx = new InitialContext();
		DataSource ds = (DataSource) ctx
				.lookup("java:/comp/env/jdbc/MyLocalDB");

		final String findUserQuery = "select username,password,enabled "
				+ "from Employees " + "where username = ?";
		final String findRoles = "select username,role " + "from Roles "
				+ "where username = ?";
		
		auth.jdbcAuthentication().dataSource(ds)
				.usersByUsernameQuery(findUserQuery)
				.authoritiesByUsernameQuery(findRoles);
	}
	
	@Override
    public void configure(WebSecurity web) throws Exception {
        web
            .ignoring()
                // Spring Security צריך להתעלם לחלוטין מ-URLs שמסתיימים ב-.html
                .antMatchers("/*.html");
    }

}

שימו לב שאנו מתעלמים מכל הקבצים HTML על ידי דריסה של שיטת configure(WebSecurity web). הקוד מראה כיצד להכניס אימות JDBC. אנו צריכים להגדיר אותו על ידי מתן DataSource. מאחר שאנו משתמשים בטבלאות מותאמות אישית, אנו חייבים גם לספק את שאילתות הבחירה כדי לקבל את פרטי המשתמש והתפקידים שלו. הגדרת האימות בזיכרון ובשימוש DAO נכונים, הם מפורטים בקוד למעלה. ניתן להסיר את ההערות כדי להשתמש בהם, ולוודא כי יש רק הגדרה אחת בכל פעם. הערות @Configuration ו-@EnableWebSecurity דרושות, כך שהקוד מסגירה יודע שכיתה זו תשמש להגדרת אבטחת Spring. הגדרת אבטחת Spring משתמשת ב-פטרן בנייה ובהתבסס על שיטת האימות, חלק מהשיטות לא יהיו זמינים מאוחר יותר. לדוגמה, auth.userDetailsService() מחזיר את מופע ה-UserDetailsService ואז לא נוכל להגדיר אפשרויות אחרות, כמו שלא נוכל להגדיר DataSource אחרי זה.

שילוב Spring Security Web עם Servlet API

החלק האחרון הוא לשלב את מחלקת התצורה של Spring Security עם ממשק ה-Servlet API. זה נעשה בקלות על ידי הרחבת מחלקת AbstractSecurityWebApplicationInitializer והעברת מחלקת התצורה האבטחתית בבנאי של המחלקה האב. SecurityWebApplicationInitializer.java

package com.journaldev.webapp.spring.security;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class SecurityWebApplicationInitializer extends
		AbstractSecurityWebApplicationInitializer {

	public SecurityWebApplicationInitializer() {
        super(SecurityConfig.class);
    }
}

כאשר ההקשר שלנו מתחיל, הוא משתמש ב-ServletContext כדי להוסיף את המאזין ContextLoaderListener ולרשום את מחלקת התצורה שלנו כ-מסנן Servlet. שימו לב שזה יעבוד רק במקרים של מפענחי Servlet התואמים לגרסה 3. לכן, אם אתם משתמשים ב-Apache Tomcat, וודאו שהגרסה שלכם היא 7.0 או גרסה גבוהה יותר. הפרויקט שלנו מוכן, פשוט הפיצו אותו במפענח Servlet האהוב עליכם. אני משתמש ב-Apache Tomcat-7 להפעלת היישום הזה. בתמונות למטה מוצג התגובה בסקנריואים שונים.

גישה לדף HTML בלעדי אבטחה

אימות נכשל עבור פרטי הכניסה שגויים

דף הבית עם אימות JDBC של Spring Security

עמוד הבית עם אימות DAO של Spring Security UserDetailsService

עמוד הבית עם אימות In-Memory של Spring Security

עמוד התנתקות

אם ברצונך להשתמש במגדירי Servlet שאינם תומכים ב-Servlet Specs 3, יהיה עליך לרשום את DispatcherServlet דרך מחולל ההטבעה. ראה את JavaDoc של WebApplicationInitializer לפרטים נוספים. זהו הכל לדוגמה ולאינטגרציה של Spring Security באפליקציה ווב בהתבסס על Servlet. ניתן להוריד את הפרויקט הדוגמא מהקישור למטה ולהתנסות בו למעמד נוסף.

הורד פרויקט אבטחת Servlet של Spring

Source:
https://www.digitalocean.com/community/tutorials/spring-security-example-tutorial