25.04.2015

Spring Security и Mysql - практический пример.

UPDATE: Возможно вам будет интересен пример использования Spring Security 4 и AngularJS.

Я уже упоминал про JavaEE фреймворк Spring Security и приводил простой пример его использования. Этот пример включает в себя создание веб приложения с двумя страницами - страница логина и закрытая страница. Для авторизации, как ясно, там используется Spring Security. Однако имя пользователя и пароль (точнее SHA-1 хэш) в этом примере хранятся в XML файле настройки контекста Spring Security. Понятно, что в реальных задачах данные пользователя, как и его роли, хранятся в базе данных. 

В этом посте я хочу привести усовершенствованный пример, в котором имя пользователя, хэш пароля и роль пользователя в системе будут храниться в БД Mysql. Для основы возьму код предыдущего примера, а полный код для этого поста можно найти ниже. 

Итак, как можно связать авторизацию с помощью Spring и Mysql. Сделать это можно немного по разному, посмотрим на один из способов. Первым делом установим Mysql и создадим тестовую БД, в которой будет одна таблица. В таблице содержится имя пользователя, название его роли в системе, и хэш пароля. Обычно в БД хранят и другую информацию - например дату, до которой аккаунт активен и пр. Но в этом примере этого не будет. 

После создания БД создадим пользователя, имя и пароль которого будут прописаны в настройках приложения. Даем ему права и устанавливаем кодировку UTF-8 для базы. Теперь создаем таблицу и добавляем в него пользователя:

Готово, теперь в базе есть 1 пользователь с ролью ROLE_ADMIN. Настройка базы завершена, переходим к приложению. Первым делом нужно добавить в pom.xml нужные зависимости:

     
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.25</version>
	</dependency>


	<dependency>
		<groupId>org.apache.commons</groupId>
		<artifactId>commons-io</artifactId>
		<version>1.3.2</version>
	</dependency>

 В файл root-context.xml добавим настройки для подключения к Mysql:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
     http://www.springframework.org/schema/beans 
     http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/tx
     http://www.springframework.org/schema/tx/spring-tx.xsd
     http://www.springframework.org/schema/aop 
     http://www.springframework.org/schema/aop/spring-aop.xsd
     http://www.springframework.org/schema/context 
     http://www.springframework.org/schema/context/spring-context.xsd">
	
	<!-- MySQL config -->
	<bean id="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver" />
		<property name="url"
			value="jdbc:mysql://localhost:3306/test_spring?useUnicode=true&amp;characterEncoding=utf8" />
		<property name="username" value="spring_app" />
		<property name="password" value="123456" />
	</bean>
	
	<!-- В этом контектсе загружается все, кроме контроллеров Spring MVC-->
	<context:component-scan base-package="org.develnotes.examples.service">
		<context:exclude-filter expression="org.springframework.stereotype.Controller"
			type="annotation" />
	</context:component-scan>
		
</beans>

Теперь добавим два класса - MysqlContentDAO и MysqlUserService.

MysqlContentDAO.java:

package org.develnotes.examples.service;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * Используется для получения данных из БД
 * Пример для develnotes.org
 * @author Alex Dl.
 *
 */
@Service
public class MysqlContentDAO {
	
	private final Logger log = LoggerFactory.getLogger(MysqlContentDAO.class);
	@Autowired
	private DataSource dataSource;

	/**
	 * Получить хэш SHA-1 пароля пользователя
	 * @param username имя пользователя
	 * @return
	 */
	public String getUserPasswordHash(String username) {
		
		String sql = "select pwdHash from users where userName=?;";
		Connection conn = null;
 
		try {
			conn = dataSource.getConnection();
			PreparedStatement ps = conn.prepareStatement(sql);
			ps.setString(1, username);
			ResultSet rs = ps.executeQuery();
			
			if (rs.next()) {
				return rs.getString("pwdHash");
			}
			rs.close();
			ps.close();
			
		} catch (Exception e){
			log.error("Error  : " + e.getMessage());
		} finally {
			if (conn != null) {
				try {
					conn.close();
				} catch (Exception e) {}
			}
		}
			
		return null;
	}

	/**
	 * Получить роль пользователя по имени пользователя
	 * @param username имя пользователя
	 * @return роль пользователя
	 */
	public String getUserRole(String username) {

		String roleName = "";

		String sql = "select userRole from users where userName=?;";
		Connection conn = null;

		try {
			conn = dataSource.getConnection();
			PreparedStatement ps = conn.prepareStatement(sql);
			ps.setString(1, username);
			ResultSet rs = ps.executeQuery();

			while (rs.next()) {
				roleName = rs.getString("userRole");
			}
			rs.close();
			ps.close();

		} catch (Exception e) {
			log.error("Error  : " + e.getMessage());
		} finally {
			if (conn != null) {
				try {
					conn.close();
				} catch (Exception e) {
				}
			}
		}

		return roleName;
	}

}

Этот класс используется для получения роли пользователя по имени и для получения хэша пароля. Класс MysqlUserService используется для предоставления данных Spring Security и реализует интерфейс org.springframework.security.core.userdetails.UserDetailsService:

package org.develnotes.examples.service;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.stereotype.Service;

/**
 * Сервис для получения роли пользователя/проверки пароля при входе в систему.
 * Пример для develnotes.org
 * @author Alex Dl.
 */
@Service
public class MysqlUserService implements UserDetailsService, Serializable {

	private static final long serialVersionUID = -7858305254948955717L; 
	@Autowired
	private MysqlContentDAO contentService;
	private static final Logger logger = LoggerFactory.getLogger(MysqlUserService.class);

	@Override
	public UserDetails loadUserByUsername(final String username) {

		//создаем простой анонимный класс, реализующий интерфейс UserDetails
		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() {

				try {
					
					//получаем хэш пароля по имени пользователя
					return contentService.getUserPasswordHash(username);
					
				} catch (Exception e) {
					logger.error("Error while access MYSQL database to get pwdHash for user = "
							+ username);
				}
				return null;
			}

			@Override
			public Collection<? extends GrantedAuthority> getAuthorities() {
				List<SimpleGrantedAuthority> auths = new java.util.ArrayList<SimpleGrantedAuthority>();

				try {
					//получаем роль пользователя по имени пользователя
					auths.add(new SimpleGrantedAuthority(contentService.getUserRole(username)));
					
				} catch (Exception e) {
					logger.error("Error while access MYSQL database "
							+ "to get details for user = " + username);
				}

				return auths;
			}
		};
	}

	public MysqlContentDAO getContentService() {
		return contentService;
	}
	
	public void setContentService(MysqlContentDAO contentService) {
		this.contentService = contentService;
	}
}

В классе выше необходимо определить один метод - loadUserByUsername (final String username), который возвращает данные учетной записи для Spring. Для упрощения примера - создаем анонимный класс, в котором некоторые поля не используются, устанавливаем туда значения по умолчанию. Теперь необходимо указать этот класс в контексте Spring Security:

<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-3.0.xsd
	http://www.springframework.org/schema/security
	http://www.springframework.org/schema/security/spring-security-3.1.xsd">

	<http auto-config="true">
	
		<intercept-url pattern="/admin" access="ROLE_ADMIN" />
		<intercept-url pattern="/admin/*" access="ROLE_ADMIN" />
		<intercept-url pattern="/admin*" access="ROLE_ADMIN" />
		
		<form-login login-page="/login" default-target-url="/admin"
			authentication-failure-url="/loginfailed" />
		<logout logout-success-url="/logout" />
		
	</http>

	<authentication-manager alias="authenticationManager">
		<authentication-provider user-service-ref="mysqlUserService">
	    <password-encoder hash="sha"/>
		</authentication-provider>
	</authentication-manager>

</beans:beans>

Все, наше приложение готово! Запустив его на сервере приложений/контейнере сервлетов, можно проверить работоспособность. 

Для пользователя admin правильным паролем является самый секьюрный - "qwerty" =):

Скачать пример

GIT



Теги: javaEE programming Spring

comments powered by Disqus