Начало работы с Spring Security

задняя часть Архитектура Spring Безопасность

До весенней безопасности


я использовалInterceptorБыли реализованы перехват входа в систему и обработка сеанса простого веб-сайта Demo.Хотя соответствующие функции могут быть достигнуты, нет сомнений в том, что метод настройки, предоставляемый Spring Security, проще и понятнее и может лучше защитить веб-приложение.

Соответствующая структура Spring Security


Здесь вы можете обратиться к официальному вводному документу Spring Security:spring-security-architecture
Проще говоря:

  • Spring Security является единственнымFilter, чей конкретный типFilterChainProxy, то есть как@BeanсуществуетApplicationContextнастроен в.
  • С точки зрения контейнера, Spring Security является одним фильтром, но в нем много дополнительных фильтров, каждый из которых играет свою роль, как показано на следующем рисунке:
    An AuthenticationManager hierarchy using ProviderManager
  • Аутентификация Spring Security, в основномAuthenticationManagerЭтот интерфейс завершен, и основным методом его проверки являетсяauthenticate()
public interface AuthenticationManager {   
    
  Authentication authenticate(Authentication authentication)   
    throws AuthenticationException;   
   
}
  • Этот метод делает три вещи:
    • Если он может проверить, что ввод представляет собой действительного принципала, вернитеAuthentication(обычно содержитauthenticated=true)
    • Бросьте a, если он может подтвердить, что ввод представляет недопустимый принципалAuthenticationException
    • Если он не может решить, вернутьсяnull
  • чаще всего используетсяAuthicationManagerРеализацияProviderManager, который делегирует егоAuthticationProviderЭтот экземпляр,AuthenticationProviderиAuthenticationManagerАналогично, но с некоторыми дополнительными методами, позволяющими вызывающей стороне запрашивать,Authenticaionформа.
public interface AuthenticationProvider {   
   
	Authentication authenticate(Authentication authentication)   
			throws AuthenticationException;   
   
	boolean supports(Class<?> authentication);   
   
}
	

supports()в методеClass<?>параметрClass<? extends Authentication>, он только спросит, поддерживает ли он переход кauthenticate()метод.

  • В этой же программе А.ProviderManagerпоручив сериюAuthenticaitonProvidersЧтобы поддерживать несколько различных механизмов аутентификации, еслиProviderManagerНевозможно определить конкретныйAuthenticationтип экземпляра, он пропускается.

  • Часто программа содержит несколько логических групп защиты ресурсов, каждая группа имеет свою уникальнуюAuthenticationManager, обычно они имеют общего родителя, тогда родитель становится"global"资源, это всеproviderвернулся.

    An AuthenticationManager hierarchy using ProviderManager

  • Spring Security предоставляет некоторую конфигурацию, чтобы помочь нам быстро включить функцию проверки, наиболее часто используемыйAuthenticationManagerBuiler, это in-memory (в памяти), JDBC, LDAP или пользовательскийUserDetailServiceЭти районы очень хорошие.


Доступ и контроль разрешений с помощью Spring Security

Примечание. Этот последующий код реализован с использованием SpringBoot в качестве фреймворка и его DEMO Git:Spring-Security-Demo

  • В основном путем перегрузки метода configure WebSecurityConfigurerAdapter для управления доступом и разрешениями.
метод описывать
configure(WebSecurity) Настройте цепочку фильтров Spring Security, перегрузив
configure(HttpSecurity) Путем перегрузки настройте, как перехватчик защищает запрос
configure(AuthenticationManagerBuilder) Настройте службу сведений о пользователе, перегрузив
  • Перепишем метод следующим образом:
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
		.authorizeRequests()
		.antMatchers("/index").hasAnyAuthority("ROLE_USER","ROLE_ADMIN")
		.antMatchers("/oss").hasAuthority("ROLE_ADMIN")
		.antMatchers(HttpMethod.GET, "/login").permitAll()
		.anyRequest().authenticated()
		.and()
		.formLogin()
		.loginPage("/login")
		.permitAll()//.successHandler(successHandler)
		.and()
		.logout()
		.logoutSuccessUrl("/")
		.permitAll();
	}

	
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
				.withUser("root").password(new BCryptPasswordEncoder().encode("root")).roles("USER","ADMIN").and()
				.withUser("normal").password(new BCryptPasswordEncoder().encode("normal")).roles("USER");
		//auth.authenticationProvider(userProvider);
		//auth.authenticationProvider(afterProvider);
		
	}
- 通过`antMatchers()`进行URL匹配,再进行相应的处理,比如见上代码,我们将**/index**和**/oss**两个链接进行了拦截,并分别要求拥有`ROLE_USER`或`ROLE_ADMIN`、`ROLE_ADMIN`这两个身份才能访问。
- `anyRequest().authenticated()`指其他请求都会需要验证
- `formLogin()`使其有了登录页面,如果没有后面的`loginPage()`,则会默认生成一个Spring Security的页面,而后面注释掉的`successHandler`则是后续会讲到的。
- `permitAll()`则表示当前连接不需要认证。
- `logout()`会拦截所以的**\logout**请求,完成登出操作,`logoutSuccessUrl()`则是登出后的重定向地址。
- `and()`在其中起连接作用。
  • Некоторые распространенные методы настройки пути защиты

    • authentication() : разрешить аутентифицированным пользователям доступ
    • denyAll() : безоговорочно запретить любой доступ
    • fullAuthenticated() : если пользователь полностью аутентифицирован (не через Remeber me)
    • hasIpAdress(String): если бык исходит с данного IP-адреса, к нему можно получить доступ.
    • hasAnyAuthority(String ...): если используется какая-либо из заданных ролей, к ней можно получить доступ
    • hasAnthority(String): если у пользователя есть данная роль, он может получить доступ
    • allowAl() : безоговорочно разрешить метод
    • запомнить меня(): если пользователь аутентифицирован с помощью функции «Запомнить меня», он может получить доступ
    • Кроме того, есть Роль, соответствующая Полномочию. Эти два понятия являются концепцией. Полномочия должны начинаться с "ROLE_", а Роли это не нужно. См. код выше.
  • В настоящее время наша учетная запись root может получить доступ как к индексу, так и к oss, в то время как обычная учетная запись может получить доступ только к index и не может получить доступ к oss.Если вы получите доступ к oss, это появится:
    There was an unexpected error (type=Forbidden, status=403).

  • Выше мы сгенерировали двух пользователей памяти root и normal, перегрузив configure (аутентификация AuthenticationManagerBuilder), и мы также можем добиться этого с помощью jdbc и других методов.


Реализуйте обработку после успешной аутентификации через AuthenticationSuccessHandler.

  • Реализуя интерфейс AuthenticationSuccessHandler, мы можем выполнить соответствующий код после успешной аутентификации, такой какTokenнастройки и так далее, например, я сейчас печатаю данные для входа и перенаправляю запрос на домашнюю страницу
@Component
public class SuccessHandler implements AuthenticationSuccessHandler{

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException {
		System.out.println(authentication.getName()+" is loging , role is"+authentication.getAuthorities());
		response.sendRedirect("/");
		
	}

  • и добавить его вformLogin()после этого, а именно:

.formLogin()
		.loginPage("/login")
		.permitAll().successHandler(successHandler)

  • Войдите снова в учетную запись root, и вы увидите в консоли:root is loging , role is[ROLE_ADMIN, ROLE_USER]

Персонализированная аутентификация через AuthenticationProvider

  • мы строимUserAuthProvider, и сделать этоAuthenticationProviderинтерфейс:
@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		
		System.out.println("-----------------------------------------------------------------------");
		System.out.println("This is UserAuthProvider");
		
		System.out.println("starting authenticate ... ...");
		System.out.println("Credentials:"+authentication.getCredentials());
		System.out.println("Name:"+authentication.getName());
		System.out.println("Class:"+authentication.getClass());
		System.out.println("Details:"+authentication.getDetails());
		System.out.println("Principal:"+authentication.getPrincipal());
		System.out.println("-----------------------------------------------------------------------");
		UsernamePasswordAuthenticationToken auth=new UsernamePasswordAuthenticationToken(authentication.getPrincipal(), authentication.getCredentials());
		return auth;
	}

	@Override
	public boolean supports(Class<?> authentication) {
		System.out.println("This is UserAuthProvider");
		System.out.println("starting supports");
		System.out.println(authentication.getClass());
		return false;
	}
  • В то же время мы прокомментировали предыдущийauth.inMemoryAuthentication(), Присоединяйтесь к UserAuthProviderAuthenticationManagerBuilder, это:
	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//		auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
//				.withUser("root").password(new BCryptPasswordEncoder().encode("root")).roles("USER","ADMIN").and()
//				.withUser("normal").password(new BCryptPasswordEncoder().encode("normal")).roles("USER");
		auth.authenticationProvider(userProvider);
		auth.authenticationProvider(afterProvider);
		
	}


  • В этот момент, когда мы снова войдем в систему, мы обнаружим, что консоль выводит
	This is UserAuthProvider   
	starting supports  
	 java.lang.  Class 
  • Причина этого в том, что мы переписываемsupports()метод, всегда возвращает false, и когда он возвращает false, он не будет вызываться сноваauthenticate()Чтобы выполнить операцию аутентификации (как описано выше), мы будемsupports()Возвращаемое значение становится истинным, снова войдите в систему (имя пользователя: пароль root: 1234), консоль выдаст
This is UserAuthProvider
starting supports
class java.lang.Class
-----------------------------------------------------------------------
This is UserAuthProvider
starting authenticate ... ...
Credentials:1234
Name:root
Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken
Details:org.springframework.security.web.authentication.WebAuthenticationDetails@166c8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node04v47liue6knt1oghnzgiqb9dx0
Principal:root
-----------------------------------------------------------------------
root is loging , role is[]

  • успешно вошел в систему, потому что мы находимся вauthenticate()Метод напрямую объявляетAuthenticationэкземплярUsernamePasswordAuthenticationToken, и возвращается, как сказано выше, при возвратеAuthenticationНапример, по умолчанию авторизация прошла успешно, и если мы вернемnull, это означает, что это не может быть оценено, и вход в систему не будет успешным.

  • В этот момент мы создаем еще один объектUserAfterProvider, который также реализуетAuthenticationProviderинтерфейс иUserAfterProviderиUserAuthProviderизauthenticate()возвращаемые значения установлены наnull, мы используем вышеуказанные данные для повторного входа в систему, вывод консоли выглядит следующим образом:

This is UserAuthProvider
starting supports
class java.lang.Class
-----------------------------------------------------------------------
This is UserAuthProvider
starting authenticate ... ...
Credentials:1234
Name:root
Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken
Details:org.springframework.security.web.authentication.WebAuthenticationDetails@43458: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node01m47f3t6xq5a470fu07jaipzb0
Principal:root
-----------------------------------------------------------------------
This is UserAfterProvider
starting supports
class java.lang.Class
-----------------------------------------------------------------------
This is UserAfterProvider
starting authenticate ... ...
Credentials:1234
Name:root
Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken
Details:org.springframework.security.web.authentication.WebAuthenticationDetails@43458: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node01m47f3t6xq5a470fu07jaipzb0
Principal:root
-----------------------------------------------------------------------

  • То есть оба Porvider были проверены, и ни один из них не прошел (возвращает null), что указывает на то, что все соединенияAuthenticationManagerBuilderПроверка будет выполнена снова, тогда, если мы поставь один из провайдераauthenticate()Возвращаемое значение возвращается кAuthenticationНапример, войдите снова, консоль выдаст следующие результаты:
This is UserAuthProvider
starting supports
class java.lang.Class
-----------------------------------------------------------------------
This is UserAuthProvider
starting authenticate ... ...
Credentials:1234
Name:root
Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken
Details:org.springframework.security.web.authentication.WebAuthenticationDetails@166c8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node04v47liue6knt1oghnzgiqb9dx0
Principal:root
-----------------------------------------------------------------------
root is loging , role is[]
This is UserAuthProvider
starting supports
class java.lang.Class
-----------------------------------------------------------------------
This is UserAuthProvider
starting authenticate ... ...
Credentials:null
Name:root
Class:class org.springframework.security.authentication.UsernamePasswordAuthenticationToken
Details:org.springframework.security.web.authentication.WebAuthenticationDetails@166c8: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: node04v47liue6knt1oghnzgiqb9dx0
Principal:root
-----------------------------------------------------------------------
  • потому что мы переписываемAuthenticationSuccessHandler, так что проверка прошла успешно, жаль перенаправить на **/, и мой контроллер прав/сделал еще один перенаправить/Индекс **, значит было две проверки, но в этот раз мы выяснили, что т.к.UserAuthProviderпрошло, значитUserAfterProviderПроверка отсутствует, поэтому мы можем знать, что если один провайдер проходит проверку, мы можем считать, что он прошел проверку.

  • Поэтому мы можем добитьсяAuthenticationProviderЧтобы написать свою собственную логику аутентификации, вы можете даже использовать службы, связанные с @Autowire, чтобы помочь в реализации.