Spring Security 예제 튜토리얼

Spring Security는 웹 애플리케이션에서 인증 및 권한 부여를 수행하는 방법을 제공합니다. 우리는 어떤 서블릿 기반 웹 애플리케이션에서도 스프링 시큐리티를 사용할 수 있습니다.

Spring Security

Spring Security를 사용하는 몇 가지 이점은 다음과 같습니다:

  1. 입증된 기술, 휠을 다시 발명하는 것보다 이것을 사용하는 것이 좋습니다. 보안은 우리가 추가로 주의를 기울여야 하는 사항이며, 그렇지 않으면 우리의 애플리케이션이 공격자에게 취약해집니다.
  2. CSRF와 세션 조작 공격과 같은 일반적인 공격 중 일부를 방지합니다.
  3. 어떤 웹 애플리케이션에도 쉽게 통합할 수 있습니다. 웹 애플리케이션 구성을 수정할 필요가 없으며, 스프링이 자동으로 보안 필터를 웹 애플리케이션에 삽입합니다.
  4. 다양한 방법으로 인증을 지원합니다 – 인메모리, DAO, JDBC, LDAP 등.
  5. 특정 URL 패턴을 무시하는 옵션을 제공하므로 정적 HTML, 이미지 파일을 제공하는 데 유용합니다.
  6. 그룹 및 역할을 지원합니다.

Spring Security 예제

우리는 웹 애플리케이션을 생성하고 Spring Security와 통합할 것입니다. Eclipse에서 “Dynamic Web Project” 옵션을 사용하여 웹 애플리케이션을 만들어 기본 구조를 준비하십시오. 빌드 및 배포에 Maven을 사용하므로 Maven 프로젝트로 변환해야 합니다. 이러한 단계에 익숙하지 않은 경우 Java 웹 애플리케이션 튜토리얼을 참조하십시오. 애플리케이션을 보안하는 방법에 대해 알아보겠습니다.

  1. 인메모리
  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 예제를 읽어보십시오.

Spring Security Maven 종속성

다음은 최종 pom.xml 파일입니다.

<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>

스프링 프레임워크와 관련된 다음 종속성이 있습니다.

  1. spring-jdbc: JDBC 인증 방법에서 JDBC 작업에 사용됩니다. JNDI로 DataSource 설정이 필요합니다. 사용 예제의 전체 내용은 스프링 DataSource JNDI 예제를 참조하세요.
  2. spring-security-taglibs: 스프링 시큐리티 태그 라이브러리로, JSP 페이지에서 사용자 역할을 표시하는 데 사용합니다. 대부분의 경우에는 필요하지 않을 수도 있습니다.
  3. spring-security-config: 인증 프로바이더를 구성하는 데 사용됩니다. JDBC, DAO, LDAP 등을 사용할지 여부를 설정합니다.
  4. spring-security-web: 이 컴포넌트는 Spring Security를 Servlet API에 통합합니다. 웹 애플리케이션에서 보안 구성을 플러그인하기 위해 필요합니다.

또한, 프로그래밍 방식으로 리스너와 필터를 추가하기 위해 Servlet API 3.0 기능을 사용할 예정이므로 종속성의 서블릿 API 버전은 3.0 이상이어야 합니다.

스프링 시큐리티 예제 뷰 페이지

애플리케이션에는 JSP 및 HTML 페이지가 있습니다. HTML 페이지 이외의 모든 페이지에 인증을 적용하려고 합니다. health.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Health Check</title>
</head>
<body>
    <h3>Service is up and running!!</h3>
</body>
</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 예제 UserDetailsService DAO 구현

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;
			}
		};
	}

}

익명 내부 클래스를 만들고 반환하는 것에 유의하십시오. 구현 클래스를 만들고 그것을 인스턴스화하고 반환 할 수 있습니다. 일반적으로 실제 응용 프로그램에서 진행하는 방법입니다.Spring Security 예제 WebSecurityConfigurer 구현

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는 .html로 끝나는 URL을 완전히 무시해야 합니다.
                .antMatchers("/*.html");
    }

}

주의할 점은 configure(WebSecurity web) 메서드를 재정의하여 모든 HTML 파일을 무시하도록 설정했다는 것입니다. 이 코드는 JDBC 인증을 플러그인하는 방법을 보여줍니다. DataSource를 제공하여 구성해야 합니다. 사용자 정의 테이블을 사용하므로 사용자 세부 정보와 역할을 가져오기 위한 select 쿼리도 제공해야 합니다. 인메모리 및 DAO 기반 인증을 구성하는 것은 간단합니다. 위의 코드에서 주석 처리되어 있으므로 주석 처리를 해제하여 사용하면 됩니다. 한 번에 한 가지 구성만 있도록 주의하십시오. @Configuration@EnableWebSecurity 주석이 필요합니다. 이렇게 하면 스프링 프레임워크가 이 클래스를 스프링 보안 구성에 사용할 것임을 알 수 있습니다. 스프링 보안 구성은 빌더 패턴을 사용하며 인증 메서드를 기반으로 나중에는 일부 메서드를 사용할 수 없습니다. 예를 들어, auth.userDetailsService()UserDetailsService의 인스턴스를 반환하고 그 후에는 DataSource와 같은 다른 옵션을 설정할 수 없습니다.

Spring Security Web를 Servlet API와 통합하기

마지막 단계는 Spring Security 구성 클래스를 Servlet API와 통합하는 것입니다. 이는 AbstractSecurityWebApplicationInitializer 클래스를 확장하고 슈퍼 클래스 생성자에 Security 구성 클래스를 전달하여 쉽게 수행할 수 있습니다. 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 Filter로 등록합니다. 이는 Servlet-3 호환 서블릿 컨테이너에서만 작동합니다. 따라서 Apache Tomcat을 사용하는 경우 버전이 7.0 이상인지 확인하십시오. 프로젝트가 준비되었으므로 좋아하는 서블릿 컨테이너에 배포하십시오. 이 애플리케이션을 실행하기 위해 Apache Tomcat-7을 사용하고 있습니다. 아래 이미지는 다양한 시나리오에서의 응답을 보여줍니다.

보안 없이 HTML 페이지에 접근

잘못된 자격 증명으로 인한 인증 실패

Spring Security JDBC 인증이 적용된 홈 페이지

Spring Security UserDetailsService DAO 인증을 사용하는 홈페이지

Spring Security In-Memory 인증을 사용하는 홈페이지

로그아웃 페이지

만약 Servlet Specs 3을 지원하지 않는 Servlet 컨테이너를 사용하려면, 배포 서술자를 통해 DispatcherServlet을 등록해야 합니다. 자세한 내용은 WebApplicationInitializer의 JavaDoc을 참조하세요. 이것이 Spring Security 예제 튜토리얼과 서블릿 기반 웹 애플리케이션에서의 통합에 대해 알아본 것입니다. 더 배우기 위해 아래 링크에서 샘플 프로젝트를 다운로드하고 실험해보세요.

Spring Servlet Security 프로젝트 다운로드

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