Почему Spring Security запрещает определенный ресурс независимо от его роли? - PullRequest
0 голосов
/ 21 декабря 2018

Я занимаюсь разработкой веб-приложения с использованием архитектуры Spring MVC и защищаю его с помощью Spring Security.Я использую репозитории JPA для своего уровня персистентности.У меня проблема в том, что когда я пытаюсь отправить POST-запрос с определенной страницы приложения (страница «Добавить реализацию»), я получаю страницу с сообщением об ошибке:

There was an unexpected error (type=Forbidden, status=403). Forbidden

Thisпроисходит независимо от роли моего пользователя (есть две роли: admin и vendor).Кроме того, это даже происходит, когда я явно разрешаю рассматриваемый URL в моей функции configure(HttpSecurity http), используя antMatchers и permitAll().Итак, вопрос в том, почему мой запрос POST не авторизован?

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

Ниже приведена моя функция настройки: URL /vendor/{id:[0-9]+}/addimpl - это проблема, которая доставляет мне неприятности.Я явно разрешил это здесь только для того, чтобы посмотреть, что произойдет, но я все еще получаю ошибку 403 при публикации (но запрос GET работает нормально).

@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Autowired
private AcvpUserDetailsService userDetailsService;

@Override
protected void configure(HttpSecurity http) throws Exception { 
    http.authorizeRequests()
            .antMatchers("/register", "/webjars/**", "/css/**",
                    "/images/**").permitAll()
            .antMatchers("/vendor/{id:[0-9]+}/addimpl").permitAll()
            .anyRequest().authenticated().and().formLogin()
            .loginPage("/login").loginProcessingUrl("/login").successHandler(myAuthenticationSuccessHandler()).permitAll().and()
            .logout().permitAll();
}

Вот мой UserDetailsService:

@Service
@Transactional
public class AcvpUserDetailsService implements UserDetailsService {

    @Autowired
    private AcvpUserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) {
        AcvpUser user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException(username);
        }
        return new AcvpUserPrincipal(user);
    }
}

И класс UserDetails ...

@Transactional
public class AcvpUserPrincipal implements UserDetails {

/**
 * this is necessary for posterity to know whether they can serialize this
 * class safely
 */
private static final long serialVersionUID = 3771770649711489402L;
private AcvpUser user;

public AcvpUserPrincipal(AcvpUser user) {
    this.user = user;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {   
    return Collections.singletonList(new 
SimpleGrantedAuthority(user.getRole()));
}

@Override
public String getPassword() {
    return user.getPassword(); // this is now the encrypted password
}

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

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

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

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

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

А вот мой класс AuthenticationSuccessHandler.Экземпляр этого возвращается из myAuthenticationSuccessHandler() в функции конфигурации.

public class AcvpAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

protected Log logger = LogFactory.getLog(this.getClass());

private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

@Autowired
private AcvpUserRepository userRepository;

@Override
public void onAuthenticationSuccess(HttpServletRequest request, 
  HttpServletResponse response, Authentication authentication)
  throws IOException {

    handle(request, response, authentication);
    clearAuthenticationAttributes(request);
}

protected void handle(HttpServletRequest request, 
  HttpServletResponse response, Authentication authentication)
  throws IOException {

    String targetUrl = determineTargetUrl(authentication);

    if (response.isCommitted()) {
        logger.debug(
          "Response has already been committed. Unable to redirect to "
          + targetUrl);
        return;
    }

    redirectStrategy.sendRedirect(request, response, targetUrl);
}

protected String determineTargetUrl(Authentication authentication) {
    boolean isUser = false;
    boolean isAdmin = false;
    Collection<? extends GrantedAuthority> authorities
     = authentication.getAuthorities();
    for (GrantedAuthority grantedAuthority : authorities) {
        if (grantedAuthority.getAuthority().equals(AcvpRoles.VENDOR_ROLE)) {
            isUser = true;
            break;
        } else if (grantedAuthority.getAuthority().equals(AcvpRoles.ADMIN_ROLE)) {
            isAdmin = true;
            break;
        }
    }

    if (isUser) {        
        String username = authentication.getName();
        AcvpUser user = userRepository.findByUsername(username);     
        return "/vendor/" + user.getVendor().getId();
    } else if (isAdmin) {
        return "/";
    } else {
        throw new IllegalStateException();
    }
}

protected void clearAuthenticationAttributes(HttpServletRequest request) {
    HttpSession session = request.getSession(false);
    if (session == null) {
        return;
    }
    session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}

public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
    this.redirectStrategy = redirectStrategy;
}
protected RedirectStrategy getRedirectStrategy() {
    return redirectStrategy;
}
}

Вот зависимости Spring Security из моего файла pom:

<!-- Spring Security -->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-core</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-springsecurity4</artifactId>
        <version>3.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
        <scope>runtime</scope>
    </dependency>

Вот функция контроллера.Я включаю и GET, и POST, но учтите, что GET работает нормально, в то время как POST выдает ошибку.Я укажу, однако, что программа НЕ входит в функцию POST.Я установил точку останова и попытался отладить, но она не сработала, прежде чем войти в эту функцию.

@RequestMapping(value = "/vendor/{id:[0-9]+}/addimpl", method = RequestMethod.GET)
public String getAddImplementation(Model model, @PathVariable("id") Long id)
        throws VendorNotFoundException {
    Vendor vendor = vendorRepository.findById(id)
            .orElseThrow(VendorNotFoundException::new);
    model.addAttribute("vendor", vendor);
    model.addAttribute("edit", false);
    model.addAttribute("moduleTypes", ModuleType.values());
    ImplementationAddForm backingObject = new ImplementationAddForm();
    model.addAttribute("form", backingObject);
    return "implementation-add-edit";
}

@RequestMapping(value = "/vendor/{id:[0-9]+}/addimpl", method = RequestMethod.POST)
public String saveImplementation(@PathVariable("id") Long id,
        @ModelAttribute("implementation") @Valid ImplementationAddForm form,
        BindingResult bindingResult, Model model, RedirectAttributes ra)
        throws VendorNotFoundException {
    Vendor vendor = vendorRepository.findById(id)
            .orElseThrow(VendorNotFoundException::new);

    if (bindingResult.hasErrors()) {
        model.addAttribute("vendor", vendor);
        model.addAttribute("edit", false);
        model.addAttribute("moduleTypes", ModuleType.values());
        model.addAttribute("form", form);
        return "implementation-add-edit";
    } else {
        Implementation i = form.buildEntity();
        i.setVendor(vendor);
        implementationRepository.save(i);
        return "redirect:/vendor/" + id;
    }

}

Наконец, я включаю вывод настройки «debug» в классе SecurityConfiguration.Это могло бы помочь, но я ничего не смог получить от него.

Request received for POST '/vendor/33/addimpl':

org.apache.catalina.connector.RequestFacade@3b981cfd

servletPath:/vendor/33/addimpl
pathInfo:null
headers: 
host: localhost:8080
connection: keep-alive
content-length: 402
cache-control: max-age=0
origin: http://localhost:8080
upgrade-insecure-requests: 1
content-type: application/x-www-form-urlencoded
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 
(KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
referer: http://localhost:8080/vendor/33/addimpl
accept-encoding: gzip, deflate, br
accept-language: en-US,en;q=0.9
cookie: JSESSIONID=1121ADD15A2E23786464649647B62356


Security filter chain: [
  WebAsyncManagerIntegrationFilter
  SecurityContextPersistenceFilter
  HeaderWriterFilter
  CsrfFilter
  LogoutFilter
  UsernamePasswordAuthenticationFilter
  RequestCacheAwareFilter
  SecurityContextHolderAwareRequestFilter
  AnonymousAuthenticationFilter
  SessionManagementFilter
  ExceptionTranslationFilter
  FilterSecurityInterceptor
]


 ************************************************************


2018-12-21 09:49:32.570  INFO 4392 --- [nio-8080-exec-3] Spring Security 
Debugger                 : 

 ************************************************************

Request received for POST '/error':

org.apache.catalina.core.ApplicationHttpRequest@73210c23

servletPath:/error
pathInfo:null
headers: 
host: localhost:8080
connection: keep-alive
content-length: 402
cache-control: max-age=0
origin: http://localhost:8080
upgrade-insecure-requests: 1
content-type: application/x-www-form-urlencoded
user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 
(KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
referer: http://localhost:8080/vendor/33/addimpl
accept-encoding: gzip, deflate, br
accept-language: en-US,en;q=0.9
cookie: JSESSIONID=1121ADD15A2E23786464649647B62356


Security filter chain: [
  WebAsyncManagerIntegrationFilter
  SecurityContextPersistenceFilter
  HeaderWriterFilter
  CsrfFilter
  LogoutFilter
  UsernamePasswordAuthenticationFilter
  RequestCacheAwareFilter
  SecurityContextHolderAwareRequestFilter
  AnonymousAuthenticationFilter
  SessionManagementFilter
  ExceptionTranslationFilter
  FilterSecurityInterceptor
]

После отправки запроса POST на /vendor/33/addimpl я ожидаю, что я буду перенаправлен обратно на страницу поставщика или на "снова добавить страницу реализации (ту самую страницу, с которой я писал), в случае ошибки проверки.Но ничего из этого не происходит.Вместо этого меня отправляют на страницу ошибок по умолчанию.

Ответы [ 2 ]

0 голосов
/ 24 декабря 2018

Как уже говорили другие, проблема заключалась в том, что CSRF был включен, а токен CSRF не отправлялся с запросом POST.Однако я не хотел полностью отключать CSRF, так как хотел, чтобы приложение было защищено от CSRF-атак.Оказывается, добавить токен CSRF в это приложение очень просто.Я использую тимилиф в качестве инструмента для создания шаблонов, и это простое решение не найдено ни в одной из уже опубликованных ссылок, но оно найдено здесь: https://www.baeldung.com/csrf-thymeleaf-with-spring-security

Я включил этот код в форму входа в систему:

<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />

Согласно приведенной выше ссылке, это все, что необходимо, но для меня это не сработало, пока я не добавил нотацию тимелиста th: ко всем действиям с моей формой.Таким образом, вместо того, чтобы делать <form action="<url>", я должен был сделать <form th:action="@{<url>}".

0 голосов
/ 21 декабря 2018

CSRF (подделка межсайтовых запросов) включена по умолчанию.

Вы можете отключить его в своем классе AcvpUserDetailsService

Добавить:

 http.csrf().disable();

Чтениебольше о CSRF здесь: https://www.baeldung.com/spring-security-csrf

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...