春のセキュリティの例のチュートリアル

Spring Securityは、ウェブアプリケーションでの認証と認可を行う方法を提供します。Spring Securityは、サーブレットベースのウェブアプリケーションで使用することができます。

春のセキュリティ

Spring Securityを使用することのいくつかの利点は次のとおりです:

    1. 証明された技術であるため、車輪の再発明よりもこれを使用する方が良いです。セキュリティは注意が必要な要素であり、そうしないとアプリケーションは攻撃者に対して脆弱になります。

 

    1. CSRFやセッション固定攻撃など、一部の一般的な攻撃を防ぎます。

 

    1. どんなウェブアプリケーションにも簡単に統合できます。ウェブアプリケーションの構成を変更する必要はありません。Springは自動的にセキュリティフィルターをウェブアプリケーションに注入します。

 

    1. インメモリ、DAO、JDBC、LDAPなど、さまざまな方法での認証をサポートしています。

 

    1. 特定のURLパターンを無視するオプションがあり、静的HTMLや画像ファイルを提供するのに便利です。

 

    グループや役割のサポートがあります。

スプリングセキュリティの例

私たちはウェブアプリケーションを作成し、それをSpring Securityと統合します。Eclipseの「Dynamic Web Project」オプションを使用してウェブアプリケーションを作成し、スケルトンのウェブアプリケーションを準備します。ビルドと展開にMavenを使用しているため、それをMavenプロジェクトに変換することを確認してください。これらの手順に慣れていない場合は、Java Web Application Tutorialを参照してください。アプリケーションをセキュリティで保護した後、最終的なプロジェクト構造は以下の画像のようになります。私たちは3つのSpring Security認証メソッドを調べます。

    1. インメモリー

 

    1. データアクセスオブジェクト

 

    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
	('scdev', 'scdev123', 1);

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

commit;

わたしたちは、サーブレットコンテナ内でJDBCデータソースをJNDIとして設定する必要があります。これについては、「Tomcat JNDI DataSource Example」を読んで学ぶことが必要です。

春セキュリティのメイヴン依存関係

以下は私たちの最終的な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>

以下は、Spring Frameworkに関連する依存関係があります。

    1. spring-jdbc: これはJDBC認証方法によるJDBC操作に使用されます。JNDIとしてDataSourceのセットアップが必要です。使用例の完全版については、Spring DataSource JNDI Exampleを参照してください。

 

    1. spring-security-taglibs: Spring Securityのタグライブラリで、JSPページでユーザーの役割を表示するために使用しました。ただし、ほとんどの場合は必要ありません。

 

    1. spring-security-config: 認証プロバイダーの設定に使用されます。JDBC、DAO、LDAPなどの使用方法を指定します。

 

    spring-security-web: このコンポーネントはSpring SecurityをServlet APIに統合します。Webアプリケーションにセキュリティ設定をプラグインするために必要です。

また、プログラムによってリスナーやフィルターを追加するためにServlet API 3.0 の機能を使用しますので、依存関係においてServlet 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>

インデックス.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>

アプリケーションのデプロイ記述子にはindex.jspをwelcome-fileとして含めました。Spring SecurityがCSRF攻撃に対処するため、ログアウトのフォームを送信する際には、CSRFトークンをサーバーに送信して削除します。Spring Securityコンポーネントによって設定されたCSRFオブジェクトは「_csrf」であり、そのプロパティ名とトークン値をログアウトリクエストに渡すために使用しています。では、今度はSpring Securityの設定を見てみましょう。

Spring Securityの例としてUserDetailsService DAOの実装

DAOベースの認証を使用するため、UserDetailsServiceインターフェースを実装してloadUserByUsername()メソッドの実装を提供する必要があります。理想的には、ユーザーを検証するためのリソースを使用するべきですが、簡単のために基本的な検証のみ行っています。AppUserDetailsServiceDAO.java

package com.scdev.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("scdev")){
			throw new UsernameNotFoundException(username + " not found");
		}
		
		//creating dummy user details, should do JDBC operations
		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 "scdev123";
			}
			
			@Override
			public Collection<? extends GrantedAuthority> getAuthorities() {
				List<SimpleGrantedAuthority> auths = new java.util.ArrayList<SimpleGrantedAuthority>();
				auths.add(new SimpleGrantedAuthority("admin"));
				return auths;
			}
		};
	}

}

注意点ですが、UserDetailsの匿名内部クラスを作成して返していることに気づいてください。それに対して、実装クラスを作成してそれをインスタンス化して返すこともできます。通常、実際のアプリケーションではその方法がよく使われます。

Spring Securityの例として、WebSecurityConfigurerの実装をご紹介します。

私たちは、WebSecurityConfigurerインターフェースを実装するか、基本的な実装クラスであるWebSecurityConfigurerAdapterを拡張し、メソッドをオーバーライドすることができます。SecurityConfig.java

package com.scdev.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.scdev.webapp.spring.dao.AppUserDetailsServiceDAO;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

		// in-memory authentication
		// auth.inMemoryAuthentication().withUser("scdev").password("scdev123").roles("USER");

		// using custom UserDetailsService DAO
		// auth.userDetailsService(new AppUserDetailsServiceDAO());

		// using 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 should completely ignore URLs ending with .html
                .antMatchers("/*.html");
    }

}

configure(WebSecurity web)メソッドをオーバーライドすることで、すべてのHTMLファイルを無視していることに注意してください。このコードは、JDBC認証をプラグインする方法を示しています。私たちはDataSourceを提供することでそれを設定する必要があります。カスタムテーブルを使用しているため、ユーザーの詳細と役割を取得するためのSELECTクエリも提供する必要があります。インメモリベースとDAOベースの認証を設定するのは簡単ですが、上記のコードではコメントアウトされています。使用するためにそれらのコメントを解除できますが、一度に1つの設定のみを持つようにしてください。@Configurationと@EnableWebSecurityのアノテーションが必要です。これにより、このクラスがSpring Securityの設定に使用されることがSpringフレームワークに認識されます。Spring Securityの設定はBuilderパターンを使用しており、authenticateメソッドに基づいて、一部のメソッドは後で使用できなくなります。たとえば、auth.userDetailsService()はUserDetailsServiceのインスタンスを返し、それ以降にDataSourceを設定するなどの他のオプションは使用できません。

Servlet APIとSpring Security Webの統合

最後のパートは、Spring Securityの設定クラスをサーブレットAPIに統合することです。これは、AbstractSecurityWebApplicationInitializerクラスを拡張し、スーパークラスのコンストラクターにセキュリティ設定クラスを渡すことで簡単に行うことができます。SecurityWebApplicationInitializer.java

package com.scdev.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ページにアクセスする

認証に失敗しました。認証情報が間違っています。

スプリングセキュリティJDBC認証を使用したホームページ

スプリング セキュリティのUserDetailsService DAO認証を備えたホームページ

スプリングセキュリティを使用したホームページのメモリ認証機能を持ったページ

ログアウトページ

もしServlet Specs 3をサポートしていないServletコンテナを使用したい場合は、デプロイメント記述子を通じてDispatcherServletを登録する必要があります。詳細については、WebApplicationInitializerのJavaDocを参照してください。それがSpring Securityの例のチュートリアルとServletベースのWebアプリケーションでの統合のすべてです。詳しく学ぶために、以下のリンクからサンプルプロジェクトをダウンロードして試してみてください。

「Spring Servlet Security プロジェクトをダウンロードします。」

コメントを残す 0

Your email address will not be published. Required fields are marked *