Skip to content

Spring security

Sayapin Alexander edited this page Feb 21, 2013 · 24 revisions

Общие сведения о Spring Security

TODO! Вводная часть

SecurityContextHolder

Краеугольным камнем Spring Security являются такие понятия как SecurityContext и SecurityContextHolder. SecurityContextHolder - это класс с помощью, которого можно получить текущий контекст безопасности (SecurityContext). Текущий контекст безопасноти хранит данные об аутентификации (Authentication) доверителя (Principal).

SecurityContextHolder по умолчанию использует ThreadLocal, что делает возможным его получение из любого места приложения, в случае работы в многопотоной среде есть соответствующие настройки для передачи SecurityContext между потоками.

Получение данных об аутентификации и доверителе (Principal) будет выглядеть так:

SecurityContext context = SecurityContextHolder.getContext();

Authentication authentication = context.getAuthentication();

Object user = authentication.getPrincipal();

В переменной user могуть быть сохранены 2 типа значений:

  • Объект реализующий UserDetails
  • Строка с представлением пользователя

Получение имени пользователя может выглядеть так:

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
String username;
if (principal instanceof UserDetails) {
	username = ((UserDetails)principal).getUsername();
} else {
	username = principal.toString();
}

Основными методами в SecurityContextHolder являются методы получение и установки контекста безопасности.

SecurityContext getContext(); // получить контекст безопасности
void setContext(SecurityContext context); // установить контекст безопасности

Authentication

Интерфейс, который должны реализовывать классы, которые представляют маркет для запроса проверки безопасности или авторизованного доверителя (Principal).

Класс, который реализует интерфейс Authentication, возвращается в ответ на попытку авторизации:

// Authentication authToken;
// AuthenticationManager am;
Authentication auth = am.authenticate(authToken);

Таким образом, в объект реализующий Authentication c заполненными полями username и credentials передаётся объекту AuthenticationManager, который проверяет возможность аутентификации и устанавливает флаг isAuthenticated() данного объекта.

Другими словами Authentication представляет данные пользователя для аутентификации или аутентифицированного пользователя.

Основными методами являются

Collection<? extends GrantedAuthority> getAuthorities(); // Получение списка полномочий
Object getCredentials(); // Получение пароля
Object getDetails(); // Получение дополнительных данных по пользователю
Object getPrincipal(); // Получение объекта или идентификатора авторизованного пользователя
boolean isAuthenticated(); // Получить статус аутентификации

Полномочия GrantedAuthority

Интерфейс представляет собой описание полномочий, который выданы объекту аутентификации.

String getAuthority();

Полномочия представлены как строки, если возможно, иначе этот данный метод возвращает null. Базовой реализацией GrantedAuthority является класс SimpleGrantedAuthority, который представляет полномочие как строку.

Полномочие - широкое понятие, оно может представлять как право, так и, например, роль. В таком случае данное полномочие будет представлено как:

GrantedAuthority roleAuthority = new SimpleGrantedAuthority("ROLE_ADMIN");

При проверке прав доступа с полномочиями работает AccessDecisionManager, который на основании полномочий выбирает предоставлять доступ или нет.

AuthenticationManager

AuthenticationManager - интерфес, который должны реализовывать классы, которые производят аутентификацию и заполнение по переданному объекту Authentication (включая список полномочий, если аутентификация прошла успешно).

Единственный метод, который должен реализовывать класс реализующий этот интерфейс - это метод аутентификации.

Authentication authenticate(Authentication authentication) throws AuthenticationException;

ProviderManager

В состав Spring security входит реализация данного интерфеса - ProviderManager, который реализует аутентификацию как делегирование вызова authenticate() провайдерам аутентификации, которые реализуют интерфейс AuthenticationProvider.

AuthenticationProvider

Интерфесй AuthenticationProvider содержит 2 метода:

  • Authentication authenticate(Authentication authentication) throws AuthenticationException - собственно аутентификация
  • boolean supports(Class<?> authentication); - проверка возможно применения данного провайдера аутентификации к данному объекту аутентификации

ProviderManager таким образом проходит по списку провайдеров и вызывает метод supports. Если провайдер поддерживает данный объект аутентификации, то вызывается метод authenticate(). Провайдеры проверяются по порядку до первой успешной аутентификации. Данное свойство позволяет реализовывать гибкие правила аутентификации, например, сотрудники компании аутентифицируются через LDAP, а остальные пользователи аутентифицируются через доступ к БД.

Реализации AuthenticationProvider

В состав Spring Security входит несколько провайдеров аутентификации, которые можно использовать рассмотрим наиболее интерсеные и важные:

  • DaoAuthenticationProvider - провайдер аутентификации через DAO
  • TestingAuthenticationProvider - тестовый провайдер аутентифкации (возвращает неизменный объект аутентифкации для Unit тестирования)
  • LdapAuthenticationProvider - провайдер аутентификации через LDAP

Рассмотрим DaoAuthenticationProvider и LdapAuthenticationProvider.

DaoAuthenticationProvider

DaoAuthenticationProvider - это провайдер, который использует DAO (точнее UserDetailsService) для аутентификации пользователей. Данный класс использует дополнительные классы для аутентификации:

  • void setPasswordEncoder(Object passwordEncoder); - метод устанаваливает шифровальщик паролей для шифрования и проверки паролей
  • void setSaltSource(SaltSource saltSource); - устанавливает поставщик "соли" для декодирования паролей
  • void setUserDetailsService(UserDetailsService userDetailsService); - устанавливает UserDetailsService для получения данных пользователя

DaoAuthenticationProvider - использует UserDetailsService для получения пользователя по имени пользователя и сравнивает пароли используя PasswordEncoder (если установлен SaltSource, то он используется для добавления к паролю "соли").

Spring Security поставляется с несколькими реализациями UserDetailsService.

  • InMemoryUserDetailsManager - хранилище данных пользователей в памяти
  • JdbcUserDetailsManager - хранилище данных пользователей в БД
  • LdapUserDetailsManager - хранилище данных пользователей в LDAP

UserDetails

Класс реализующий интерфейс UserDetails представляет данные пользователя и список полномочий. UserDetails расширенные свойства для данных пользователя.

Методы интерфейса UserDetails:

  • Collection<? extends GrantedAuthority> getAuthorities(); - получение списка полномочий
  • String getPassword(); - получение пароля, если это возможно (реализации UserDetails обычно "затирают" пароль после аутентификации)
  • String getUsername(); - получение имени пользователя
  • boolean isAccountNonExpired(); - является ли аккаунт просроченным
  • boolean isAccountNonLocked(); - является ли аккаунт заблокированным
  • boolean isCredentialsNonExpired(); - не истекло ли время действия пароля
  • boolean isEnabled(); - явлется ли пользователь активным

Кроме интерфейса UserDetails существуют интерфейсы, которые расширяют данный интерфейс специализированными полями, например, LdapUserDetails. Так же существуют и реализации обоих интерфейсов, например, User (для UserDetails) или InetOrgPerson (для LdapUserDetails).

UserDetailsService

Интерфейс UserDetailsService реализует сервис получения данных пользователя по имени пользователя.

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

Spring security поставляется с интерфейсом UserDetailsManager, который расширяет UserDetailsService дополнительными методами:

  • void createUser(UserDetails user); - создание пользователя
  • void updateUser(UserDetails user); - обновление данных пользователя
  • void deleteUser(String username); - удаление пользователя
  • void changePassword(String oldPassword, String newPassword); - изменение пароля пользователя
  • boolean userExists(String username); - проверка существования пользователя

InMemoryUserDetailsManager

Менеджер пользователей, который хранит данные пользователей в памяти. Данный менеджер удобно использовать для unit-тестирования.

JdbcUserDetailsManager

Данный менеджер позволяет получать пользователей из БД используя JDBC из таблиц:

  • users - таблица пользователей
  • authorities - таблица полномочий
  • groups - таблица групп
  • group_members - таблица соответствия между группами и пользователями
  • group_authorities - таблица групповый полномочий

LdapUserDetailsManager

Данный менеджер получает данные пользователя из LDAP и заполняет соответствующие поля.

Авторизации через LDAP

TODO!

Управления доступом на уровне объектов

TODO!

Ссылки по теме

Подготовительный код:

package com.a1systems.security_test;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.http.UserDetailsServiceFactoryBean;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
 * Hello world!
 *
 */
public class App {

	private static final Logger logger = LoggerFactory.getLogger(App.class);

	public static void main(String[] args) {
		UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken("user1", "user1");

		SampleAuthenticationManager am = new SampleAuthenticationManager();

		Authentication auth = am.authenticate(authToken);

		logger.debug("auth {}", auth);

		SecurityContextHolder.getContext().setAuthentication(auth);

		SecurityContext context = SecurityContextHolder.getContext();

		logger.debug("Context {}", context);

		Authentication authentication = context.getAuthentication();

		logger.debug("authentication {}", authentication);

		Object user = authentication.getPrincipal();

		logger.debug("principal {}", user);


		List<AccessDecisionVoter> voters = new ArrayList<AccessDecisionVoter>();
		voters.add(new RoleVoter());
		voters.add(new MyVoter());

		AffirmativeBased ab = new AffirmativeBased(voters);

		User u1 = new User();
		u1.setLogin("user1");
		u1.setPassword("pswd1");

		User u2 = new User();
		u2.setLogin("user2");
		u2.setPassword("pswd2");




		Book b1 = new Book();
		b1.setAuthor(u1);
		b1.setTitle("Title1");

		Book b2 = new Book();

		logger.debug("principal {}", user);


		List<AccessDecisionVoter> voters = new ArrayList<AccessDecisionVoter>();
		voters.add(new RoleVoter());
		voters.add(new MyVoter());

		AffirmativeBased ab = new AffirmativeBased(voters);

		User u1 = new User();
		u1.setLogin("user1");
		u1.setPassword("pswd1");

		User u2 = new User();
		u2.setLogin("user2");
		u2.setPassword("pswd2");




		Book b1 = new Book();
		b1.setAuthor(u1);
		b1.setTitle("Title1");

		Book b2 = new Book();
		b2.setAuthor(u2);
		b2.setTitle("Title2");

		List<ConfigAttribute> list = new ArrayList<ConfigAttribute>();
		list.add(new ConfigAttribute() {
			@Override
			public String getAttribute() {
				return "view_book";
			}
		});

		try {
			ab.decide(authentication, b1, list);
		} catch (AccessDeniedException e) {
			logger.debug("Acces denied on b1");
		}

		try {
			ab.decide(authentication, b2, list);
		} catch (AccessDeniedException e) {
			logger.debug("Acces denied on b2");
		}
	}

	private static class SampleAuthenticationManager implements AuthenticationManager {

		static final List<GrantedAuthority> AUTHORITIES = new ArrayList<GrantedAuthority>();

		static {
			AUTHORITIES.add(new SimpleGrantedAuthority("ROLE_USER"));
			AUTHORITIES.add(new ViewBookAuthority());
		}

		@Override
		public Authentication authenticate(Authentication auth) throws AuthenticationException {
			if (auth.getName().equals(auth.getCredentials())) {
				return new UsernamePasswordAuthenticationToken(auth.getName(),auth.getCredentials(), AUTHORITIES);
			}
			throw new BadCredentialsException("Bad Credentials");
		}
	}

	private static class ViewBookAuthority implements GrantedAuthority {

		@Override
		public String getAuthority() {
			return "view_book";
		}

	}

	private static class MyVoter implements AccessDecisionVoter {

		@Override
		public boolean supports(ConfigAttribute attribute) 
{
package com.a1systems.security_test;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.http.UserDetailsServiceFactoryBean;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
 * Hello world!
 *
 */
public class App {

	private static final Logger logger = LoggerFactory.getLogger(App.class);

	public static void main(String[] args) {
		UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken("user1", "user1");

		List<AuthenticationProvider> plist = new ArrayList<AuthenticationProvider>();

		List<UserDetails> users = new ArrayList<UserDetails>();

		users.add(new UserDetailsImpl("user1","user1"));

		DaoAuthenticationProvider daoProvider = new DaoAuthenticationProvider();
		InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager(users);

		daoProvider.setUserDetailsService(inMemoryUserDetailsManager);

		plist.add(daoProvider);

		ProviderManager am = new ProviderManager(plist);

		Authentication auth = am.authenticate(authToken);

		logger.debug("auth {}", auth);

		SecurityContextHolder.getContext().setAuthentication(auth);

		SecurityContext context = SecurityContextHolder.getContext();

		logger.debug("Context {}", context);

		Authentication authentication = context.getAuthentication();

		logger.debug("authentication {}", authentication);

		Object user = authentication.getPrincipal();

		logger.debug("principal {}", user);

		logger.debug("details {}", authentication.getDetails());
		logger.debug("credentials {}", authentication.getCredentials());

		List<AccessDecisionVoter> voters = new ArrayList<AccessDecisionVoter>();
		voters.add(new RoleVoter());
		voters.add(new MyVoter());

		AffirmativeBased ab = new AffirmativeBased(voters);

		User u1 = new User();
		u1.setLogin("user1");
		u1.setPassword("pswd1");

		User u2 = new User();
		u2.setLogin("user2");
		u2.setPassword("pswd2");




		Book b1 = new Book();
		b1.setAuthor(u1);
		b1.setTitle("Title1");

		Book b2 = new Book();
		b2.setAuthor(u2);
		b2.setTitle("Title2");

		List<ConfigAttribute> list = new ArrayList<ConfigAttribute>();
		list.add(new ConfigAttribute() {
			@Override
			public String getAttribute() {
				return "view_book";
			}
		});

		try {
			ab.decide(authentication, b1, list);
			logger.debug("Acces granted on b1");
		} catch (AccessDeniedException e) {
			logger.debug("Acces denied on b1");
		}

		try {
			ab.decide(authentication, b2, list);
			logger.debug("Acces granted on b1");
		} catch (AccessDeniedException e) {
			logger.debug("Acces denied on b2");
		}
	}

	private static class UserDetailsImpl implements UserDetails {
		private String username;
		private String password;

		private UserDetailsImpl(String username, String password) {
			this.username = username;
			this.password = password;
		}

		@Override
		public Collection<? extends GrantedAuthority> getAuthorities() {
			List<GrantedAuthority> authorityList = new ArrayList<GrantedAuthority>();

			authorityList.add(new ViewBookAuthority());

			return authorityList;
		}

		@Override
		public String getPassword() {
			return this.password;
		}

		@Override
		public String getUsername() {
			return this.username;
		}

		@Override
		public boolean isAccountNonExpired() {
			return true;
		}

		@Override
		public boolean isAccountNonLocked() {
			return true;
		}

		@Override
		public boolean isCredentialsNonExpired() {
			return true;
		}

		@Override
		public boolean isEnabled() {
			return true;
		}
	}


	private static class ViewBookAuthority implements GrantedAuthority {

		@Override
		public String getAuthority() {
			return "view_book";
		}

	}

	private static class MyVoter implements AccessDecisionVoter {

		@Override
		public boolean supports(ConfigAttribute attribute) {
			return "view_book".equals(attribute.getAttribute());
		}

		@Override
		public boolean supports(Class clazz) {
			return true;
		}

		@Override
		public int vote(Authentication authentication, Object object, Collection attributes) {
			Logger logger = LoggerFactory.getLogger(MyVoter.class);

			logger.debug("{} {}", object.getClass(), object);
			Object principal = authentication.getPrincipal();

			logger.debug("{}", principal);

			Book b = (Book)object;

			return b.getAuthor().getLogin() == ((org.springframework.security.core.userdetails.User)principal).getUsername() ? ACCESS_GRANTED : ACCESS_DENIED;
		}

	}
}