Spring 4 Security MVC Login Logout Example

اليوم سنتعلم حول مثال تسجيل الدخول في Spring Security. قبل قراءة هذه المقالة، يرجى قراءة المقالة السابقة الخاصة بي في “مقدمة لأمان Spring 4” للحصول على بعض الأساسيات.

مثال على تسجيل الدخول وتسجيل الخروج في Spring Security

في هذه المقالة، سنقوم بتطوير تطبيق ويب باستخدام Spring 4 MVC Security لتوفير ميزات تسجيل الدخول وتسجيل الخروج باستخدام الخيار المؤقت. يستخدم هذا المثال تكوين Spring Java مع تعليمات برمجية Spring، وهذا يعني عدم استخدام web.xml وتكوين Spring XML (النمط القديم). إذا لم تكن على دراية بوحدة Spring 3.x Security، يرجى الاطلاع على المقالات التالية أولاً لتجربة وصفة أمان Spring.

  1. مثال أمان Spring MVC باستخدام in-memory، UserDetailsService و JDBC Authentication
  2. أمان Spring في تطبيق ويب Servlet باستخدام DAO، JDBC، مصادقة in-memory

يدعم وحدة Spring 4 Security Module الخيارات التالية لتخزين وإدارة بيانات اعتماد المستخدم:

  1. التخزين المؤقت
  2. قواعد البيانات العلاقية (RDBMS)
  3. مخازن البيانات غير العلاقية
  4. LDAP

سنستخدم خيار “In-Memory Store” في هذا المثال. سنناقش خيارات أخرى في مشاركاتي القادمة. سنستخدم Spring 4.0.2.RELEASE، Spring STS 3.7 Suite IDE، Spring TC Server 3.1 مع Java 1.8، وأداة Maven لبناء هذا المثال.

مثال على تسجيل الدخول باستخدام Spring Security

سنقوم بتطوير منطق تسجيل الدخول وتسجيل الخروج باستخدام ميزات الأمان في Spring 4. الهدف الرئيسي لهذا التطبيق هو تطوير تطبيق دون استخدام “web.xml” ودون كتابة سطر واحد من تكوين Spring XML Beans. وهذا يعني أننا سنستخدم ميزة Spring Java Config بتحديد الموارد بواسطة تعليمات Spring. سنقوم بتطوير هذا التطبيق مع الميزات التالية:

  1. صفحة الترحيب
  2. صفحة تسجيل الدخول
  3. صفحة الرئيسية
  4. ميزة تسجيل الخروج

يرجى اتباع الخطوات التالية لتطوير واستكشاف مثال تسجيل الدخول البسيط باستخدام Spring 4 Security.

  • أنشئ مشروعًا “Simple Spring Web Maven” في Spring STS Suite باستخدام التفاصيل التالية
   Project Name : SpringMVCSecruityMavenApp
  • قم بتحديث ملف pom.xml بالمحتوى التالي
<?xml version="1.0" encoding="UTF-8"?>
<project
   xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 
   https://maven.apache.org/xsd/maven-4.0.0.xsd"
   xmlns="https://maven.apache.org/POM/4.0.0" 
   xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance">

	<modelVersion>4.0.0</modelVersion>
	<groupId>com.journaldev</groupId>
	<artifactId>SpringMVCSecruityMavenApp</artifactId>
	<packaging>war</packaging>
	<version>1.0</version>

	<properties>
	    <java.version>1.8</java.version>
	    <spring.version>4.0.2.RELEASE</spring.version>
	    <spring.security.version>4.0.2.RELEASE</spring.security.version>
	    <servlet.api.version>3.1.0</servlet.api.version>
	    <jsp.api.version>2.2</jsp.api.version>
	    <jstl.version>1.2</jstl.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-web</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-web</artifactId>
			<version>${spring.security.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-config</artifactId>
			<version>${spring.security.version}</version>
		</dependency>
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>${servlet.api.version}</version>
		</dependency>		
		<dependency>
			<groupId>javax.servlet.jsp</groupId>
			<artifactId>jsp-api</artifactId>
			<version>${jsp.api.version}</version>
		</dependency>
		<dependency>
			<groupId>jstl</groupId>
			<artifactId>jstl</artifactId>
			<version>${jstl.version}</version>
		</dependency>
	</dependencies>	

	<build>
	    <finalName>SpringMVCSecruityMavenApp</finalName>
	    <plugins>
		<plugin>
		     <groupId>org.apache.maven.plugins</groupId>
		     <artifactId>maven-compiler-plugin</artifactId>
		     <version>3.1</version>
		     <configuration>
			<source>${java.version}</source>
			<target>${java.version}</target>
		     </configuration>
		</plugin>
		<plugin>
	           <groupId>org.apache.maven.plugins</groupId>
	           <artifactId>maven-war-plugin</artifactId>
	           <configuration>
	              <failOnMissingWebXml>false</failOnMissingWebXml>
	           </configuration>           
        	</plugin>
	    </plugins>
	</build>
</project>

ملحوظة:- إذا كنت غير عارف بعلامة “<failOnMissingWebXml>”، يرجى قراءة نهاية هذه المشاركة لفهم جيد لاستخدام هذا العنصر.- أولاً، قم بتطوير متحكم تسجيل الدخول باستخدام تعليمة @Controller من Spring.
LoginController.java

package com.journaldev.spring.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class LoginController {

	@RequestMapping(value = { "/"}, method = RequestMethod.GET)
	public ModelAndView welcomePage() {
		ModelAndView model = new ModelAndView();
		model.setViewName("welcomePage");
		return model;
	}

	@RequestMapping(value = { "/homePage"}, method = RequestMethod.GET)
	public ModelAndView homePage() {
		ModelAndView model = new ModelAndView();
		model.setViewName("homePage");
		return model;
	}
	
	@RequestMapping(value = "/loginPage", method = RequestMethod.GET)
	public ModelAndView loginPage(@RequestParam(value = "error",required = false) String error,
	@RequestParam(value = "logout",	required = false) String logout) {
		
		ModelAndView model = new ModelAndView();
		if (error != null) {
			model.addObject("error", "Invalid Credentials provided.");
		}

		if (logout != null) {
			model.addObject("message", "Logged out from JournalDEV successfully.");
		}

		model.setViewName("loginPage");
		return model;
	}

}

تفسير الكود:- قمنا بتعريف ثلاثة أساليب في “LoginController” للتعامل مع ثلاثة أنواع مختلفة من طلبات العميل

  1. welcomePage() ستتعامل مع جميع طلبات العميل التي تستخدم مسار URI “/”.
  2. homePage() ستتعامل مع جميع طلبات العميل التي تستخدم مسار URI “/homePage”.
  3. loginPage() ستتعامل مع جميع طلبات العميل التي تستخدم مسار URI “/loginPage”.
  4. في loginPage()، قمنا بالاهتمام بمعالجة الأخطاء ورسائل تسجيل الخروج.
  • ثم قم بتطوير فئة “LoginSecurityConfig” لتوفير ميزات الأمان في تسجيل الدخول وتسجيل الخروج باستخدام واجهة برمجة تطبيقات Spring 4 Security.
    LoginSecurityConfig.java
package com.journaldev.spring.secuity.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 LoginSecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder authenticationMgr) throws Exception {
		authenticationMgr.inMemoryAuthentication()
			.withUser("journaldev")
			.password("jd@123")
			.authorities("ROLE_USER");
	}
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
			.antMatchers("/homePage").access("hasRole('ROLE_USER')")
			.and()
				.formLogin().loginPage("/loginPage")
				.defaultSuccessUrl("/homePage")
				.failureUrl("/loginPage?error")
				.usernameParameter("username").passwordParameter("password")				
			.and()
				.logout().logoutSuccessUrl("/loginPage?logout"); 
		
	}
}

تفسير الكود:- قمنا بتعريف طريقتين في “LoginSecurityConfig” لتخزين وإدارة بيانات اعتماد المستخدم والاهتمام بميزات الأمان في تسجيل الدخول وتسجيل الخروج.

  1. @EnableWebSecurity يتم استخدام التعليق @EnableWebSecurity لتمكين أمان الويب في أي تطبيق ويب.
  2. تم استخدام التعليق @EnableWebMVCSecurity لتمكين الأمان على الويب في تطبيق الويب القائم على Spring MVC. ملحوظة:- @EnableWebSecurity = @EnableWebMVCSecurity + ميزات إضافية. لهذا السبب، تم تجاهل التعليق @EnableWebMVCSecurity في إصدار Spring 4.x Framework.4. يجب أن توسع فئة “LoginSecurityConfig” أو أي فئة مخصصة لتكوين Spring Security من فئة “WebSecurityConfigurerAdapter” أو تنفيذ واجهة ذات الصلة.
  3. يتم استخدام طريقة configureGlobal() لتخزين وإدارة بيانات اعتماد المستخدم.
  4. في طريقة configureGlobal()، يمكننا استخدام طريقة authorities() لتعريف أدوار التطبيق مثل “ROLE_USER”. يمكننا أيضًا استخدام طريقة roles() لنفس الغرض.
  5. الفارق بين الطريقتين authorities() و roles():
  6. يحتاج authorities() إلى اسم دور كامل مثل “ROLE_USER”، بينما يحتاج roles() إلى اسم دور مثل “USER”. سيقوم بإضافة قيمة “ROLE_” تلقائيًا إلى اسم الدور “USER” هذا. ملاحظة:- سنقوم بتطوير مثال آخر لعرض الأدوار مثل “USER”، “ADMIN” في مشاركاتي القادمة.
  7. الطريقة المهمة للعناية بأمان تسجيل الدخول وتسجيل الخروج هي configure(HttpSecurity http)
  8. تستخدم قطعة الكود التالية لتجنب الوصول غير المصرح به إلى “/homePage”. إذا حاولت الوصول إلى هذه الصفحة مباشرة، سيتم توجيهك تلقائيًا إلى الصفحة “/loginPage”.
.antMatchers("/homePage").access("hasRole('ROLE_USER')")

إذا قمنا بإزالة استدعاء الطريقة access(“hasRole(‘ROLE_USER’)”)، فإنه يمكننا الوصول إلى هذه الصفحة دون تسجيل الدخول إلى تطبيقنا. 13. لقد قمنا بتكوين ميزات تسجيل الدخول وتسجيل الخروج باستخدام formLogin() و logout() methods.

  • تمكين تكوين Spring MVC
    LoginApplicationConfig.java
package com.journaldev.spring.secuity.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@EnableWebMvc
@Configuration
@ComponentScan({ "com.journaldev.spring.*" })
@Import(value = { LoginSecurityConfig.class })
public class LoginApplicationConfig {
	@Bean
	public InternalResourceViewResolver viewResolver() {
		InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
		viewResolver.setViewClass(JstlView.class);
		viewResolver.setPrefix("/WEB-INF/views/");
		viewResolver.setSuffix(".jsp");
		return viewResolver;
	}
	
}

شرح الكود:- نستخدم فئة “LoginApplicationConfig” لتعريف محللي العرض Spring MVC لتجنب كتابة ملف “web.xml”.

  1. @EnableWebMvc تُستخدم التعليقة لتمكين ميزات تطبيق Spring Web MVC في إطار Spring.
  2. @Import يُستخدم التعليقة لاستيراد فئة تكوين Spring Security إلى هذه الفئة.
  3. @ComponentScan تُستخدم التعليقة لفحص المكونات في الحزمة المحددة. فهو يساوي “context:component-scan” في تكوين Spring XML.
  • تهيئة أمان Spring
package com.journaldev.spring.secuity.config.core;

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

public class SpringSecurityInitializer extends AbstractSecurityWebApplicationInitializer {

}

يتم استخدام “SpringSecurityInitializer” لتسجيل DelegatingFilterProxy لاستخدام سلسلة تصفية springSecurityFilterChain. يتجنب كتابة تكوينات المرشحات في ملف web.xml. – تهيئة تطبيق Spring MVC
يتم استخدام فئة “SpringMVCWebAppInitializer” لتهيئة “DispatcherServlet” بدون ملف web.xml في تكوين مستند إلى التعليقات. SpringMVCWebAppInitializer.java

package com.journaldev.spring.secuity.config.core;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import com.journaldev.spring.secuity.config.LoginApplicationConfig;

public class SpringMVCWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

	@Override
	protected Class<?>[] getRootConfigClasses() {
		return new Class[] { LoginApplicationConfig.class };
	}

	@Override
	protected Class<?>[] getServletConfigClasses() {
		return null;
	}

	@Override
	protected String[] getServletMappings() {
		return new String[] { "/" };
	}
	
}

ملحوظة:-

  1. عندما نصل إلى تطبيقنا، بشكل افتراضي سيسمح SpringMVCWebAppInitializer’s getServletMappings() بالوصول إلى عنوان URL الجذر: “/”. يمكننا التجاوز لتوجيهه إلى عنوان URL مختلف.
  2. فريق Spring أو Pivotal يعمل على حل هذه المشكلة لتجنب هذا الكم الكبير من الشفرة في جافا عن طريق إضافة تعليق. يرجى التحقق من ذلك على https://jira.spring.io/browse/SPR-10359.
  • تطوير ملف welcomePage.jsp
<h3>Welcome to JournalDEV Tutorials</h3>
<a href="${pageContext.request.contextPath}/loginPage">Login to Journal</a>
  • تطوير ملف loginPage.jsp
<%@ taglib prefix="c" uri="https://java.sun.com/jsp/jstl/core"%>
<html>
<body onload='document.loginForm.username.focus();'>
	<h3>JournalDEV Tutorials</h3>

	<c:if test="${not empty error}"><div>${error}</div></c:if>
	<c:if test="${not empty message}"><div>${message}</div></c:if>

	<form name='login' action="<c:url value='/loginPage' />" 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 colspan='2'><input name="submit" type="submit" value="submit" /></td>
			</tr>
		</table>
		<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
	</form>
</body>
</html>
  • تطوير ملف homepage.jsp
<%@taglib prefix="c" uri="https://java.sun.com/jsp/jstl/core"%>
<h3>Welcome to JournalDEV Tutorials</h3>
<ul>
	<li>Java 8 tutorial</li>
	<li>Spring tutorial</li>
	<li>Gradle tutorial</li>
	<li>BigData tutorial</li>
</ul>

<c:url value="/logout" var="logoutUrl" />
<form id="logout" action="${logoutUrl}" method="post" >
  <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
</form>
<c:if test="${pageContext.request.userPrincipal.name != null}">
	<a href="javascript:document.getElementById('logout').submit()">Logout</a>
</c:if>
  • يبدو هيكل المشروع النهائي بهذا الشكل:

تشغيل مثال تسجيل الدخول وتسجيل الخروج في Spring Security MVC

لتشغيل هذا تطبيق ويب Spring، نحتاج إلى أي حاوية ويب تدعم Spring 4 وبيئة Java 8 مع حاوية Servlet 3.1.0.

  • نشر وتشغيل على خادم TC Spring في مجموعة STS Suite Spring
  • يتم الوصول تلقائيًا إلى عنوان URL لصفحة ترحيب التطبيق لدينا كما هو موضح أدناه.
    – انقر فوق رابط “تسجيل الدخول إلى JournalDEV” للوصول إلى صفحة تسجيل الدخول.
    – الآن، قم بتقديم تفاصيل تسجيل دخول غير صحيحة وانقر على زر “تسجيل الدخول”.
    هنا يمكننا ملاحظة رسالة الخطأ هذه: “تم توفير بيانات اعتماد غير صالحة.”- الآن، قم بتقديم تفاصيل تسجيل دخول صحيحة مكونة في فئة “LoginSecurityConfig”.
    بعد تسجيل الدخول بنجاح إلى تطبيقنا، يمكننا رؤية صفحة البداية لتطبيقنا مع رابط “تسجيل الخروج”.- انقر فوق رابط “تسجيل الخروج” لتسجيل الخروج من التطبيق.
    هنا يمكننا ملاحظة أننا قد سجلنا خروجنا من التطبيق بنجاح وتم توجيهنا إلى صفحة تسجيل الدخول مرة أخرى. يمكننا ملاحظة بعض رسائل تسجيل الخروج الناجحة في هذه الصفحة.

ملحوظة:- إذا لاحظنا هذا المثال، فإننا لا نستخدم ملف web.xml بالطريقة الصحيحة. حيث أن Maven، كونها تطبيق ويب، تبحث عن ملف web.xml وتثير بعض الأخطاء إذا لم تجده في التطبيق. لتجنب مشاكل Maven ذات الصلة، نحتاج إلى تكوين علم “failOnMissingWebXml” في ملف pom.xml. هذا كل شيء عن مثال بسيط عن وحدة Spring 4 Security. سنقوم بتطوير مزيد من الأمثلة الفعلية والمفيدة في الوقت الحقيقي في مشاركاتي القادمة مثل إدارة الأدوار، ميزة تذكرني، أمان WebSocket، وأكثر من ذلك. يرجى ترك تعليق إذا أعجبتك مشاركتي أو كانت لديك أي مشاكل / اقتراحات.

Source:
https://www.digitalocean.com/community/tutorials/spring-4-security-mvc-login-logout-example