Spring Security — это инфраструктура безопасности, которая может предоставлять решения для декларативного контроля доступа к безопасности для систем корпоративных приложений на основе Spring. Он предоставляет набор bean-компонентов, которые можно настроить в контексте приложения Spring, в полной мере используя функции Spring IoC, DI (инверсия управления, DI: внедрение зависимостей) и AOP (аспектно-ориентированное программирование) для предоставления прикладным системам декларативного безопасного доступа. возможности управления уменьшают усилия по написанию большого количества повторяющегося кода для средств управления безопасностью корпоративной системы.
Диаграмма классов
Чтобы облегчить понимание процесса аутентификации Spring Security, специально нарисована следующая диаграмма классов, включая соответствующие основные классы аутентификации.
Обзор
основной валидатор
AuthenticationManager
Этот объект предоставляет запись для метода аутентификации, получаяAuthentiaton
объект как параметр;
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
ProviderManager
этоAuthenticationManager
Класс реализации , который обеспечивает базовую логику и методы аутентификации; он содержитList<AuthenticationProvider>
объект через интерфейс AuthenticationProvider для расширения различных поставщиков аутентификации (когдаSpring Security
Вы можете расширить класс реализации, предоставляемый по умолчанию, если он не соответствует вашим потребностям.AuthenticationProvider
обложкаsupports(Class<?> authentication)
метод);
логика проверки
AuthenticationManager
перениматьAuthentication
объект в качестве параметра и передатьauthenticate(Authentication)
способ проверки;AuthenticationProvider
Класс реализации используется для поддержки парыAuthentication
действие проверки объекта;UsernamePasswordAuthenticationToken
ДостигнутоAuthentication
Он в основном инкапсулирует имя пользователя и пароль, введенные пользователем, и предоставляетAuthenticationManager
Подтвердить; после завершения проверки будет возвращено сообщение об успешной аутентификацииAuthentication
объект;
Authentication
Authentication
основной метод в объекте
public interface Authentication extends Principal, Serializable {
//#1.权限结合,可使用AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_ADMIN")返回字符串权限集合
Collection<? extends GrantedAuthority> getAuthorities();
//#2.用户名密码认证时可以理解为密码
Object getCredentials();
//#3.认证时包含的一些信息。
Object getDetails();
//#4.用户名密码认证时可理解时用户名
Object getPrincipal();
#5.是否被认证,认证为true
boolean isAuthenticated();
#6.设置是否能被认证
void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
ProviderManager
ProviderManager
даAuthenticationManager
Класс реализации обеспечивает базовую логику и процесс реализации аутентификации;
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
//#1.获取当前的Authentication的认证类型
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
//#2.遍历所有的providers使用supports方法判断该provider是否支持当前的认证类型,不支持的话继续遍历
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
#3.支持的话调用provider的authenticat方法认证
result = provider.authenticate(authentication);
if (result != null) {
#4.认证通过的话重新生成Authentication对应的Token
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
}
catch (InternalAuthenticationServiceException e) {
prepareException(e, authentication);
throw e;
}
catch (AuthenticationException e) {
lastException = e;
}
}
if (result == null && parent != null) {
// Allow the parent to try.
try {
#5.如果#1 没有验证通过,则使用父类型AuthenticationManager进行验证
result = parent.authenticate(authentication);
}
catch (ProviderNotFoundException e) {
// ignore as we will throw below if no other exception occurred prior to
// calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already
// handled the request
}
catch (AuthenticationException e) {
lastException = e;
}
}
#6. 是否擦出敏感信息
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
eventPublisher.publishAuthenticationSuccess(result);
return result;
}
// Parent was null, or didn't authenticate (or throw an exception).
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
prepareException(lastException, authentication);
throw lastException;
}
- Пройдите все провайдеры, а затем по очереди выполните метод проверки провайдера.
- Если провайдер успешно проверен, он выйдет из цикла, и последующая проверка не будет выполняться;
- Если проверка прошла успешно, возвращенный результат и объект проверки подлинности будут дополнительно инкапсулированы как токен проверки подлинности; Например, UsernamePasswordAuthenticationToken, RememberMeAuthenticationToken и т. д. Эти токены аутентификации также наследуются от объекта Authentication;
- Если ни один из провайдеров № 1 не прошел аутентификацию успешно, попробуйте пройти аутентификацию с помощью родительского диспетчера аутентификации;
- Необходимо ли стирать конфиденциальную информацию, такую как пароли;
AuthenticationProvider
ProviderManager
пройти черезAuthenticationProvider
Расширяет способ предоставления дополнительной проверки; а такжеAuthenticationProvider
Это сам интерфейс, и мы можем увидеть его класс реализации на диаграмме классов.AbstractUserDetailsAuthenticationProvider
иAbstractUserDetailsAuthenticationProvider
подклассDaoAuthenticationProvider
.DaoAuthenticationProvider
даSpring Security
один из основныхProvider
, предоставляет основные методы и запись для всех баз данных.
DaoAuthenticationProvider
DaoAuthenticationProvider
В основном делать следующее
- Тщательно шифруйте личность пользователя;
#1.可直接返回BCryptPasswordEncoder,也可以自己实现该接口使用自己的加密算法核心方法String encode(CharSequence rawPassword);和boolean matches(CharSequence rawPassword, String encodedPassword);
private PasswordEncoder passwordEncoder;
2. 实现了 `AbstractUserDetailsAuthenticationProvider` 两个抽象方法,
1. 获取用户信息的扩展点
```java
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
UserDetails loadedUser;
try {
loadedUser = this.getUserDetailsService().loadUserByUsername(username);
}
主要是通过注入`UserDetailsService`接口对象,并调用其接口方法 `loadUserByUsername(String username)` 获取得到相关的用户信息。`UserDetailsService`接口非常重要。
2. 实现 additionalAuthenticationChecks 的验证方法(主要验证密码);
AbstractUserDetailsAuthenticationProvider
AbstractUserDetailsAuthenticationProvider
заDaoAuthenticationProvider
Предоставляет основные методы аутентификации;
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
// Determine username
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
#1.获取用户信息由子类实现即DaoAuthenticationProvider
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
logger.debug("User '" + username + "' not found");
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
else {
throw notFound;
}
}
Assert.notNull(user,
"retrieveUser returned null - a violation of the interface contract");
}
try {
#2.前检查由DefaultPreAuthenticationChecks类实现(主要判断当前用户是否锁定,过期,冻结User接口)
preAuthenticationChecks.check(user);
#3.子类实现
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException exception) {
if (cacheWasUsed) {
// There was a problem, so try again after checking
// we're using latest data (i.e. not from the cache)
cacheWasUsed = false;
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
else {
throw exception;
}
}
#4.检测用户密码是否过期对应#2 的User接口
postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return createSuccessAuthentication(principalToReturn, authentication, user);
}
AbstractUserDetailsAuthenticationProvider
в основном реализованоAuthenticationProvider
метод интерфейсаauthenticate
И обеспечивает соответствующую логику проверки;
- вернуть пользователя
UserDetails
AbstractUserDetailsAuthenticationProvider
определяет абстрактный метод
protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException;
2. 三步验证工作
1. preAuthenticationChecks
2. additionalAuthenticationChecks(抽象方法,子类实现)
3. postAuthenticationChecks
3. 将已通过验证的用户信息封装成 UsernamePasswordAuthenticationToken 对象并返回;该对象封装了用户的身份信息,以及相应的权限信息,相关源码如下,
```java
protected Authentication createSuccessAuthentication(Object principal,
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
principal, authentication.getCredentials(),
authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
}
UserDetailsService
UserDetailsService
это интерфейс, который предоставляет метод
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
Вызов метода loadUserByUsername через имя пользователя username возвращает объект интерфейса UserDetails (соответствующийAbstractUserDetailsAuthenticationProvider
3-этапный метод проверки);
public interface UserDetails extends Serializable {
#1.权限集合
Collection<? extends GrantedAuthority> getAuthorities();
#2.密码
String getPassword();
#3.用户民
String getUsername();
#4.用户是否过期
boolean isAccountNonExpired();
#5.是否锁定
boolean isAccountNonLocked();
#6.用户密码是否过期
boolean isCredentialsNonExpired();
#7.账号是否可用(可理解为是否删除)
boolean isEnabled();
}
ВеснаUserDetailsService
Класс реализации org.springframework.security.core.userdetails.jdbc предоставляется по умолчанию.JdbcDaoImpl
JdbcUserDetailsManager
Класс реализации в основном основан наJDBC
Способы добавления, удаления, проверки и изменения Пользователя
public class JdbcUserDetailsManager extends JdbcDaoImpl implements UserDetailsManager,
GroupManager {
// ~ Static fields/initializers
// =====================================================================================
// UserDetailsManager SQL
#1.定义了一些列对数据库操作的语句
public static final String DEF_CREATE_USER_SQL = "insert into users (username, password, enabled) values (?,?,?)";
public static final String DEF_DELETE_USER_SQL = "delete from users where username = ?";
public static final String DEF_UPDATE_USER_SQL = "update users set password = ?, enabled = ? where username = ?";
public static final String DEF_INSERT_AUTHORITY_SQL = "insert into authorities (username, authority) values (?,?)";
public static final String DEF_DELETE_USER_AUTHORITIES_SQL = "delete from authorities where username = ?";
public static final String DEF_USER_EXISTS_SQL = "select username from users where username = ?";
public static final String DEF_CHANGE_PASSWORD_SQL = "update users set password = ? where username = ?";
InMemoryUserDetailsManager
Класс реализации в основном основан на内存
Способы добавления, удаления, проверки и изменения Пользователя
`открытый класс InMemoryUserDetailsManager реализует UserDetailsManager {
защищенный окончательный журнал регистрации = LogFactory.getLog(getClass());
# 1. Магазин с картой
private final Map
private AuthenticationManager authenticationManager;
public InMemoryUserDetailsManager() {
}
public InMemoryUserDetailsManager(Collection<UserDetails> users) {
for (UserDetails user : users) {
createUser(user);
}
}`
Суммировать
UserDetailsService
Интерфейс действует как мост иDaoAuthenticationProvier
где отделение от конкретных источников информации о пользователях,UserDetailsService
Зависит отUserDetails
иUserDetailsManage
р состоит из;UserDetails
иUserDetailsManager
У каждого свои обязанности: один — инкапсулировать основную информацию о пользователе, другой — управлять основной информацией о пользователе;
特别注意
,UserDetailsService
,UserDetails
а такжеUserDetailsManager
Все они являются определяемыми пользователем точками расширения. Мы можем наследовать эти интерфейсы, чтобы предоставлять собственные методы чтения пользовательских источников и управления пользователями. Например, мы можем реализовать структуру ORM, связанную с определенной структурой ORM, такой как Mybatis или Hibernate.UserDetailsService
иUserDetailsManager
;