В последних версиях произошли некоторые изменения в методе записи конфигурации Spring Security. Многие распространенные методы были заброшены и будут удалены в будущем Spring Security7. Поэтому, основываясь на старой прошлогодней статье, Сонгге добавил некоторый новый контент. рекомендации друзей, которые используют Spring Security.
Далее я вместе с друзьями разберу все известные изменения, начиная с Spring Security 5.7 (соответствует Spring Boot 2.7).
Прежде всего, первый момент заключается в том, что срок действия WebSecurityConfigurerAdapter, который легче всего обнаружить друзьям, истек. В последней версии Spring Security6.1 этот класс полностью удален, и обойтись им больше невозможно.
Если быть точным, срок действия Spring Security истек. WebSecurityConfigurerAdapter в версии 5.7.0-M2. Причина истечения срока действия заключается в том, что чиновник хочет побудить разработчиков использовать конфигурацию безопасности на основе компонентов.
Так что же такое конфигурация безопасности на основе компонентов? Приведем несколько примеров:
Раньше мы настраивали SecurityFilterChain следующим образом:
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) -> authz
.anyRequest().authenticated()
)
.httpBasic(withDefaults());
}
}
Затем оно будет изменено на следующее:
@Configuration
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) -> authz
.anyRequest().authenticated()
)
.httpBasic(withDefaults());
return http.build();
}
}
Если вы понимаете предыдущий метод написания, следующий код на самом деле легко понять, поэтому я не буду его объяснять слишком подробно.
Ранее мы настроили WebSecurity следующим образом:
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/ignore1", "/ignore2");
}
}
В дальнейшем его придется изменить на следующее:
@Configuration
public class SecurityConfiguration {
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers("/ignore1", "/ignore2");
}
}
Другой вопрос касается получения AuthenticationManager. Раньше вы могли получить этот компонент, переопределив метод родительского класса, аналогично следующему:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
В дальнейшем вы сможете создать этот Bean только самостоятельно, аналогично следующему:
@Configuration
public class SecurityConfig {
@Autowired
UserService userService;
@Bean
AuthenticationManager authenticationManager() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userService);
ProviderManager pm = new ProviderManager(daoAuthenticationProvider);
return pm;
}
}
Конечно, AuthenticationManager также можно извлечь из HttpSecurity следующим образом:
@Configuration
public class SpringSecurityConfiguration {
AuthenticationManager authenticationManager;
@Autowired
UserDetailsService userDetailsService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(userDetailsService);
authenticationManager = authenticationManagerBuilder.build();
http.csrf().disable().cors().disable().authorizeHttpRequests().antMatchers("/api/v1/account/register", "/api/v1/account/auth").permitAll()
.anyRequest().authenticated()
.and()
.authenticationManager(authenticationManager)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
}
Это тоже способ.
Давайте рассмотрим конкретный пример.
Сначала мы создаем новый проект Spring Boot, представляем зависимости Web и Spring Security и обращаем внимание на выбор последней версии Spring Boot.
Далее мы предоставляем простой тестовый интерфейс, а именно:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello Небольшой дождь в Цзяннань!";
}
}
Друзья знают, что в Spring Security По умолчанию, пока зависимости добавлены, все интерфейсы нашего проекта защищены. Теперь запустите проект и получите доступ. /hello
интерфейс, вам необходимо войти в систему, прежде чем вы сможете получить к нему доступ. Имя пользователя для входа: user, пароль генерируется случайным образом и находится в журнале запуска проекта.
Теперь наше первое требование — использовать пользовательского пользователя вместо пользователя по умолчанию, предоставленного системой. Нам нужно только зарегистрировать экземпляр UserDetailsService в контейнере Spring следующим образом:
@Configuration
public class SecurityConfig {
@Bean
UserDetailsService userDetailsService() {
InMemoryUserDetailsManager users = new InMemoryUserDetailsManager();
users.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin").build());
users.createUser(User.withUsername("Небольшой дождь в Цзяннань").password("{noop}123").roles("admin").build());
return users;
}
}
Вот и все.
Конечно, мои текущие пользователи хранятся в памяти. Если ваши пользователи хранятся в базе данных, вам нужно только предоставить класс реализации интерфейса UserDetailsService и внедрить его в контейнер Spring. Это много раз упоминалось в vhr. видео раньше (в ответе 666 общедоступной учетной записи Backend есть видео-знакомство), поэтому я не буду здесь вдаваться в подробности.
Но если я скажу, что надеюсь /hello
Доступ к этому интерфейсу можно получить анонимно, и я надеюсь, что этот анонимный доступ не пройдет Spring Security Цепочка фильтров, если она была раньше, можем переписать configure(WebSecurity)
Способ настройки, но теперь вам нужно изменить способ игры:
@Configuration
public class SecurityConfig {
@Bean
UserDetailsService userDetailsService() {
InMemoryUserDetailsManager users = new InMemoryUserDetailsManager();
users.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin").build());
users.createUser(User.withUsername("Небольшой дождь в Цзяннань").password("{noop}123").roles("admin").build());
return users;
}
@Bean
WebSecurityCustomizer webSecurityCustomizer() {
return new WebSecurityCustomizer() {
@Override
public void customize(WebSecurity web) {
web.ignoring().antMatchers("/hello");
}
};
}
}
ранее располагавшийся в configure(WebSecurity)
Содержимое метода теперь находится в WebSecurityCustomizer Bean , просто напишите здесь настройки.
Что делать, если я также хочу настроить страницу входа, параметры и т. д.? Читайте дальше:
@Configuration
public class SecurityConfig {
@Bean
UserDetailsService userDetailsService() {
InMemoryUserDetailsManager users = new InMemoryUserDetailsManager();
users.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin").build());
users.createUser(User.withUsername("Небольшой дождь в Цзяннань").password("{noop}123").roles("admin").build());
return users;
}
@Bean
SecurityFilterChain securityFilterChain() {
List<Filter> filters = new ArrayList<>();
return new DefaultSecurityFilterChain(new AntPathRequestMatcher("/**"), filters);
}
}
Нижний уровень Spring Security на самом деле представляет собой набор фильтров, поэтому наша предыдущая конфигурация в методе configure(HttpSecurity) фактически настраивает цепочку фильтров. Теперь что касается настройки цепочки фильтров, мы настраиваем цепочку фильтров, предоставляя компонент SecurityFilterChain. SecurityFilterChain — это интерфейс. Этот интерфейс имеет только один класс реализации, DefaultSecurityFilterChain. Первый параметр для построения DefaultSecurityFilterChain — это правило перехвата. пути нужно перехватывать. Второй параметр — цепочка фильтров. Здесь я дал пустую коллекцию, которая является нашей Spring Security. Все запросы будут перехвачены, а затем завершатся после прохождения пустой коллекции, что эквивалентно отказу от перехвата каких-либо запросов.
Перезапустите проект в это время, и вы обнаружите /hello
Доступ к нему также возможен напрямую, поскольку этот путь не проходит через какой-либо фильтр.
На самом деле, я думаю, что новый способ написания более интуитивен, чем старый способ написания, и каждому легче понять основной рабочий механизм цепочки фильтров Spring Security.
Некоторые друзья скажут, что этот способ письма отличается от того, что я писал раньше! При такой конфигурации я не знаю, какие фильтры есть в Spring Security. На самом деле, если мы изменим метод записи, мы сможем настроить это как раньше:
@Configuration
public class SecurityConfig {
@Bean
UserDetailsService userDetailsService() {
InMemoryUserDetailsManager users = new InMemoryUserDetailsManager();
users.createUser(User.withUsername("javaboy").password("{noop}123").roles("admin").build());
users.createUser(User.withUsername("Небольшой дождь в Цзяннань").password("{noop}123").roles("admin").build());
return users;
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.and()
.csrf().disable();
return http.build();
}
}
Написание этого способа на самом деле мало чем отличается от предыдущего способа написания.
В последней версии друзья обнаружили, что от многих распространенных методов отказались, как показано ниже:
В том числе привычный метод and(), используемый для соединения различных элементов конфигурации, теперь заброшен, и согласно официальному заявлению, этот метод будет полностью удален в Spring Security7.
Другими словами, вы больше не увидите такие конфигурации, как следующие:
@Override
protected void configure(HttpSecurity http) throws Exception {
InMemoryUserDetailsManager users = new InMemoryUserDetailsManager();
users.createUser(User.withUsername("javagirl").password("{noop}123").roles("admin").build());
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.csrf().disable()
.userDetailsService(users);
http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
}
and() будет удален!
На самом деле, брат Сон считает, что удаление метода and — это хорошо. Многим новичкам требуется много времени, чтобы просто понять метод and.
Как вы можете видеть из комментариев к методу and выше, чиновник сейчас продвигает конфигурацию на основе Lambda для замены традиционной конфигурации цепочки, поэтому в будущем наш метод записи придется изменить на следующий:
@Configuration
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth.requestMatchers("/hello").hasAuthority("user").anyRequest().authenticated())
.formLogin(form -> form.loginProcessingUrl("/login").usernameParameter("name").passwordParameter("passwd"))
.csrf(csrf -> csrf.disable())
.sessionManagement(session -> session.maximumSessions(1).maxSessionsPreventsLogin(true));
return http.build();
}
}
На самом деле, методы здесь не являются новыми, но некоторые друзья, возможно, раньше не использовали вышеуказанные методы для настройки и используют их для цепочки конфигураций. Но в дальнейшем к приведенной выше конфигурации по методу Лямбда придется потихоньку привыкать. Содержание конфигурации легко понять, объяснять, думаю, нечего.
Пользовательский вход в формате JSON также отличается от предыдущей версии.
Друзья знают, что формат данных интерфейса входа в систему по умолчанию в Spring Security имеет форму «ключ-значение». Если мы хотим использовать формат JSON для входа в систему, нам необходимо настроить фильтр или интерфейс входа в систему. Далее Brother Song будет сначала. поговорите с ребятами из Сяо, покажите мне эти две разные формы входа.
Spring Security Фильтр по умолчанию для обработки данных входа: UsernamePasswordAuthenticationFilter, в этом фильтре система будет проходить request.getParameter(this.passwordParameter)
Имя пользователя и пароль считываются. Очевидно, что для этого интерфейс должен передать параметры в форму. key-value。
Если вы хотите использовать параметры формата JSON для входа в систему, вам нужно повозиться с этим местом. Наши пользовательские фильтры следующие:
public class JsonLoginFilter extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//Получаем заголовок запроса и на его основе определяем тип параметра запроса
String contentType = request.getContentType();
if (MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(contentType) || MediaType.APPLICATION_JSON_UTF8_VALUE.equalsIgnoreCase(contentType)) {
//Объясняем, что параметры запроса JSON
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = null;
String password = null;
try {
//Разбираем тело запроса JSON параметр
User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
username = user.getUsername();
username = (username != null) ? username.trim() : "";
password = user.getPassword();
password = (password != null) ? password : "";
} catch (IOException e) {
throw new RuntimeException(e);
}
//Создаем токен Авторизации
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
//Выполняем настоящую операцию из Авторизоваться
Authentication auth = this.getAuthenticationManager().authenticate(authRequest);
return auth;
} else {
return super.attemptAuthentication(request, response);
}
}
}
Друзья, которые читали предыдущую серию статей Сонг Гэ по Spring Security, должны быть хорошо знакомы с этим кодом.
Наконец, нам нужно настроить этот фильтр:
@Configuration
public class SecurityConfig {
@Autowired
UserService userService;
@Bean
JsonLoginFilter jsonLoginFilter() {
JsonLoginFilter filter = new JsonLoginFilter();
filter.setAuthenticationSuccessHandler((req,resp,auth)->{
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
//Получить текущий объект Авторизация успешно изпользователя
User user = (User) auth.getPrincipal();
user.setPassword(null);
RespBean respBean = RespBean.ok("Авторизоватьсяуспех", user);
out.write(new ObjectMapper().writeValueAsString(respBean));
});
filter.setAuthenticationFailureHandler((req,resp,e)->{
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
RespBean respBean = RespBean.error("Авторизоватьсянеудача"); if (e instanceof BadCredentialsException) {
respBean.setMessage("Имя пользователя или пароль были введены неверно, авторизоваться не удалось");
} else if (e instanceof DisabledException) {
respBean.setMessage("Аккаунт отключен, авторизация не удалась");
} else if (e instanceof CredentialsExpiredException) {
respBean.setMessage("Срок действия пароля истек, авторизоваться не удалось");
} else if (e instanceof AccountExpiredException) {
respBean.setMessage("Срок действия учетной записи истек, авторизоваться не удалось");
} else if (e instanceof LockedException) {
respBean.setMessage("Аккаунт заблокирован, авторизоваться не удалось");
}
out.write(new ObjectMapper().writeValueAsString(respBean));
});
filter.setAuthenticationManager(authenticationManager());
filter.setFilterProcessesUrl("/login");
return filter;
}
@Bean
AuthenticationManager authenticationManager() {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userService);
ProviderManager pm = new ProviderManager(daoAuthenticationProvider);
return pm;
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//Открываем фильтр из Конфигурация
http.authorizeHttpRequests()
//Любой запрос должен быть аутентифицирован перед доступом
.anyRequest().authenticated()
.and()
//Открываем форму Авторизоваться. После открытия автоматически отобразится страница Конфигурация Авторизоваться, Авторизоватьсяинтерфейс и другая информация.
.formLogin()
//и Авторизоваться Связанныйиз URL Все адреса разрешены
.permitAll()
.and()
//закрытие csrf Механизм защиты по существу от Spring Security Удален из цепочки фильтров CsrfFilter
.csrf().disable();
http.addFilterBefore(jsonLoginFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
Здесь нужно настроить компонент JsonLoginFilter и добавить его в цепочку фильтров Spring Security.
До Spring Boot3 (до Spring Security6) приведенный выше код мог реализовать вход в формате JSON.
Однако, начиная с Spring Boot 3, этот код имеет некоторые недостатки, и больше невозможно войти в систему напрямую с помощью JSON. Конкретные причины анализируются ниже Сонгом.
Другой способ настроить вход в формате JSON — напрямую настроить интерфейс входа, как показано ниже:
@RestController
public class LoginController {
@Autowired
AuthenticationManager authenticationManager;
@PostMapping("/doLogin")
public String doLogin(@RequestBody User user) {
UsernamePasswordAuthenticationToken unauthenticated = UsernamePasswordAuthenticationToken.unauthenticated(user.getUsername(), user.getPassword());
try {
Authentication authenticate = authenticationManager.authenticate(unauthenticated);
SecurityContextHolder.getContext().setAuthentication(authenticate);
return "success";
} catch (AuthenticationException e) {
return "error:" + e.getMessage();
}
}
}
Здесь напрямую настраивается интерфейс входа в систему, а параметры запроса передаются в виде JSON. После получения имени пользователя и пароля вызовите метод AuthenticationManager#authenticate для аутентификации. После успешной аутентификации информация о аутентифицированном пользователе сохраняется в SecurityContextHolder.
Наконец, просто настройте интерфейс входа в систему:
@Configuration
public class SecurityConfig {
@Autowired
UserService userService;
@Bean
AuthenticationManager authenticationManager() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userService);
ProviderManager pm = new ProviderManager(provider);
return pm;
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests()
//выражать /doLogin Доступ к этому адресу можно получить напрямую, не используя Авторизоваться.
.requestMatchers("/doLogin").permitAll()
.anyRequest().authenticated().and()
.formLogin()
.permitAll()
.and()
.csrf().disable();
return http.build();
}
}
Это также можно рассматривать как решение с использованием параметров формата JSON. До Spring Boot3 (до Spring Security6) с вышеуказанным решением проблем не было.
Начиная с Spring Boot3 (Spring Security6), оба вышеперечисленных решения имеют некоторые недостатки.
Конкретная производительность:При звонке Авторизоваться Авторизоваться успешно,Затем посетите другие страницы в системе,Вы вернетесь на страницу Авторизоваться.,Инструкция по посещению Авторизоваться из другого интерфейса,Система не знает, что вы уже прошли Авторизацию.
Причина вышеуказанной проблемы в основном заключается в том, что изменился один из фильтров в цепочке фильтров Spring Security:
До Spring Boot3 в цепочке фильтров Spring Security был фильтр SecurityContextPersistenceFilter. Этот фильтр был заброшен в Spring Boot2.7.x, но все еще используется. В Spring Boot3 он был удален из фильтра безопасности Spring. цепочку и заменен фильтром SecurityContextHolderFilter.
Два решения для входа в систему JSON, представленные моими друзьями в первом разделе, могут работать в Spring Boot 2.x, но не могут работать в Spring Boot 3.x, что вызвано изменением этого фильтра.
Итак, далее давайте проанализируем различия между этими двумя фильтрами.
Давайте сначала посмотрим на основную логику SecurityContextPersistenceFilter:
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
try {
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(), holder.getResponse());
}
finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
SecurityContextHolder.clearContext();
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
}
}
Я разместил здесь только некоторый ключевой код ядра:
this.repo.saveContext
Метод держать текущий Авторизоваться пользовательский объект (фактически держать HttpSession середина).Это общий процесс сертификации Spring Security.
Однако после Spring Boot3 этот фильтр был заменен на SecurityContextHolderFilter. Давайте посмотрим на ключевую логику фильтра SecurityContextHolderFilter:
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
Supplier<SecurityContext> deferredContext = this.securityContextRepository.loadDeferredContext(request);
try {
this.securityContextHolderStrategy.setDeferredContext(deferredContext);
chain.doFilter(request, response);
}
finally {
this.securityContextHolderStrategy.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
Друзья, вы можете видеть, что предыдущая логика в основном такая же, разница в коде, наконец. Наконец, для сохранения SecurityContext в HttpSession стало на один шаг меньше.
Теперь становится ясно, что после успешного входа пользователя информация о пользователе не сохраняется в HttpSession. В результате при поступлении следующего запроса SecurityContext не может быть прочитан из HttpSession и сохранен в SecurityContextHolder. Spring Security будет Предполагается, что текущий пользователь не вошел в систему.
Вот в чем причина проблемы!
Если вы найдете причину, проблема будет решена.
Прежде всего, проблема заключается в фильтре. Невозможно изменить фильтр напрямую. Однако, поскольку Spring Security отказалась от старого решения в процессе обновления, мы изо всех сил пытались записать старое решение. не возможно.
Фактически Spring Security предоставляет еще одну точку входа для модификации. В методе org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#successfulAuthentication исходный код выглядит следующим образом:
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
context.setAuthentication(authResult);
this.securityContextHolderStrategy.setContext(context);
this.securityContextRepository.saveContext(context, request, response);
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
Этот метод является методом обратного вызова после успешного входа текущего пользователя. Друзья, вы можете видеть, что в этом методе обратного вызова есть предложение this.securityContextRepository.saveContext(context, request, response);
,Это означает, что текущий Авторизация успешно из информации пользователя вносит депозит в HttpSession середина.
В текущем фильтре тип SecurityContextRepository — RequestAttributeSecurityContextRepository, что означает, что SecurityContext сохраняется в атрибутах текущего запроса. Очевидно, эти данные исчезают после завершения текущего запроса. В классе автоматической конфигурации Spring Security атрибут securityContextRepository указывает на DelegatingSecurityContextRepository, который является прокси-хранилищем. Прокси-объектами являются RequestAttributeSecurityContextRepository и HttpSessionSecurityContextRepository, поэтому по умолчанию после успешного входа пользователя в систему сохраняются здесь. HttpSessionSecurityContextRepository.
Когда мы настраиваем фильтр входа, решение в автоматической конфигурации уничтожается. Используемый здесь объект SecurityContextRepository на самом деле является RequestAttributeSecurityContextRepository, поэтому система будет думать, что пользователь не вошел в систему, когда пользователь посетит его позже.
Тогда решение простое, нам нужно только указать значение атрибута securityContextRepository для пользовательского фильтра следующим образом:
@Bean
JsonLoginFilter jsonLoginFilter() {
JsonLoginFilter filter = new JsonLoginFilter();
filter.setAuthenticationSuccessHandler((req,resp,auth)->{
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
//Получить текущий объект Авторизация успешно изпользователя
User user = (User) auth.getPrincipal();
user.setPassword(null);
RespBean respBean = RespBean.ok("Авторизоватьсяуспех", user);
out.write(new ObjectMapper().writeValueAsString(respBean));
});
filter.setAuthenticationFailureHandler((req,resp,e)->{
resp.setContentType("application/json;charset=utf-8");
PrintWriter out = resp.getWriter();
RespBean respBean = RespBean.error("Авторизоватьсянеудача"); if (e instanceof BadCredentialsException) {
respBean.setMessage("Имя пользователя или пароль были введены неверно, авторизоваться не удалось");
} else if (e instanceof DisabledException) {
respBean.setMessage("Аккаунт отключен, авторизация не удалась");
} else if (e instanceof CredentialsExpiredException) {
respBean.setMessage("Срок действия пароля истек, авторизоваться не удалось");
} else if (e instanceof AccountExpiredException) {
respBean.setMessage("Срок действия учетной записи истек, авторизоваться не удалось");
} else if (e instanceof LockedException) {
respBean.setMessage("Аккаунт заблокирован, авторизоваться не удалось");
}
out.write(new ObjectMapper().writeValueAsString(respBean));
});
filter.setAuthenticationManager(authenticationManager());
filter.setFilterProcessesUrl("/login");
filter.setSecurityContextRepository(new HttpSessionSecurityContextRepository());
return filter;
}
Друзья, как видите, для настройки вам достаточно вызвать метод setSecurityContextRepository.
Причина, по которой это свойство не нужно было устанавливать до Spring Boot3.x, заключалась в том, что, хотя оно не было сохранено здесь, оно было сохранено в фильтре SecurityContextPersistenceFilter.
Итак, для проблемы пользовательского интерфейса входа решение аналогично:
@RestController
public class LoginController {
@Autowired
AuthenticationManager authenticationManager;
@PostMapping("/doLogin")
public String doLogin(@RequestBody User user, HttpSession session) {
UsernamePasswordAuthenticationToken unauthenticated = UsernamePasswordAuthenticationToken.unauthenticated(user.getUsername(), user.getPassword());
try {
Authentication authenticate = authenticationManager.authenticate(unauthenticated);
SecurityContextHolder.getContext().setAuthentication(authenticate);
session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
return "success";
} catch (AuthenticationException e) {
return "error:" + e.getMessage();
}
}
}
Друзья видели, что после успешного входа в систему разработчик вручную сохраняет данные в HttpSession. Это гарантирует, что при поступлении следующего запроса действительные данные могут быть прочитаны из HttpSession и сохранены в SecurityContextHolder.
Ладно, есть небольшая проблема в замене старых и новых версий Spring Boot, надеюсь, каждый сможет чему-то научиться.