-
Notifications
You must be signed in to change notification settings - Fork 56
Spring security
TODO! Вводная часть
Краеугольным камнем 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); // установить контекст безопасности
Интерфейс, который должны реализовывать классы, которые представляют маркет для запроса проверки безопасности или авторизованного доверителя (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(); // Получить статус аутентификации
Интерфейс представляет собой описание полномочий, который выданы объекту аутентификации.
String getAuthority();
Полномочия представлены как строки, если возможно, иначе этот данный метод возвращает null
.
Базовой реализацией GrantedAuthority
является класс SimpleGrantedAuthority
, который представляет полномочие как строку.
Полномочие - широкое понятие, оно может представлять как право, так и, например, роль. В таком случае данное полномочие будет представлено как:
GrantedAuthority roleAuthority = new SimpleGrantedAuthority("ROLE_ADMIN");
При проверке прав доступа с полномочиями работает AccessDecisionManager
, который на основании полномочий выбирает предоставлять доступ или нет.
AuthenticationManager
- интерфес, который должны реализовывать классы, которые производят аутентификацию и заполнение по переданному объекту Authentication
(включая список полномочий, если аутентификация прошла успешно).
Единственный метод, который должен реализовывать класс реализующий этот интерфейс - это метод аутентификации.
Authentication authenticate(Authentication authentication) throws AuthenticationException;
В состав Spring security входит реализация данного интерфеса - ProviderManager
, который реализует аутентификацию как делегирование вызова authenticate()
провайдерам аутентификации, которые реализуют интерфейс AuthenticationProvider
.
Интерфесй AuthenticationProvider
содержит 2 метода:
-
Authentication authenticate(Authentication authentication) throws AuthenticationException
- собственно аутентификация -
boolean supports(Class<?> authentication);
- проверка возможно применения данного провайдера аутентификации к данному объекту аутентификации
ProviderManager
таким образом проходит по списку провайдеров и вызывает метод supports
. Если провайдер поддерживает данный объект аутентификации, то вызывается метод authenticate()
. Провайдеры проверяются по порядку до первой успешной аутентификации. Данное свойство позволяет реализовывать гибкие правила аутентификации, например, сотрудники компании аутентифицируются через LDAP, а остальные пользователи аутентифицируются через доступ к БД.
В состав Spring Security входит несколько провайдеров аутентификации, которые можно использовать рассмотрим наиболее интерсеные и важные:
-
DaoAuthenticationProvider
- провайдер аутентификации через DAO -
TestingAuthenticationProvider
- тестовый провайдер аутентифкации (возвращает неизменный объект аутентифкации для Unit тестирования) -
LdapAuthenticationProvider
- провайдер аутентификации через LDAP
Рассмотрим DaoAuthenticationProvider
и LdapAuthenticationProvider
.
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
:
-
Collection<? extends GrantedAuthority> getAuthorities();
- получение списка полномочий -
String getPassword();
- получение пароля, если это возможно (реализацииUserDetails
обычно "затирают" пароль после аутентификации) -
String getUsername();
- получение имени пользователя -
boolean isAccountNonExpired();
- является ли аккаунт просроченным -
boolean isAccountNonLocked();
- является ли аккаунт заблокированным -
boolean isCredentialsNonExpired();
- не истекло ли время действия пароля -
boolean isEnabled();
- явлется ли пользователь активным
Кроме интерфейса UserDetails
существуют интерфейсы, которые расширяют данный интерфейс специализированными полями, например, LdapUserDetails
.
Так же существуют и реализации обоих интерфейсов, например, User
(для UserDetails
) или InetOrgPerson
(для LdapUserDetails
).
Интерфейс 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);
- проверка существования пользователя
Менеджер пользователей, который хранит данные пользователей в памяти. Данный менеджер удобно использовать для unit-тестирования.
Данный менеджер позволяет получать пользователей из БД используя JDBC из таблиц:
-
users
- таблица пользователей -
authorities
- таблица полномочий -
groups
- таблица групп -
group_members
- таблица соответствия между группами и пользователями -
group_authorities
- таблица групповый полномочий
Данный менеджер получает данные пользователя из 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;
}
}
}