аутентификация с пружинной безопасностью и angular - PullRequest
0 голосов
/ 09 марта 2020

Я реализую Spring Security с приложением Spring Boot- angular. В моем пользовательском контроллере я сопоставил метод по следующему URL: / api / user / login для подключения пользователя, получения информации о пользователе, идентификации пользователя и проверки его регистрации в базе данных. Когда я запускаю приложение angular, URL-адрес: http://localhost: 8080 / api / user / login запускается с ошибкой состояния 405. У меня есть несколько вопросов

1) это нормально, что url: http://localhost: 8080 / api / user / login , будет запущен в начале? Я не понимаю почему. Я хочу, чтобы подключение пользователя было выделено для всплывающей формы (для которой запускается http://localhost: 8080 / api / user / login url)

2), если это нормально, какова цель такого URL-адреса и как мне реализовать контроллер конечной точки с этим URL-адресом?

3) в моем приложении angular я настроил proxy.config. json как следующий

{
  "/api/*": {

    "target":  {
       "host": "localhost",
       "protocol": "http:",
       "port": 8080
     },
    "secure": false,
     "changeOrigin": true,
     "logLevel": "info"
  }
}

для перенаправления каждого локального узла: 42OO / api на localhost: 8080 / api. Для этого в интерфейсной части я запускаю приложение командой «ng serve --proxy-config proxy.config. json».

Когда "http://localhost: 8080 / api / user / login " запущено, у меня появляется следующее сообщение об ошибке:

    Blocage d’une requête multiorigines (Cross-Origin Request) : la politique « Same Origin » ne permet pas de consulter la ressource distante située sur http://localhost:8080/api/user/login. Raison : l’en-tête CORS « Access-Control-Allow-Origin » est manquant.
Blocking of a multi-origin request (Cross-Origin Request): the "Same Origin" policy does not allow consulting the remote resource located on http: // localhost: 8080 / api / user / login. Reason: The CORS header "Access-Control-Allow-Origin" is missing.

Итак, как это сделать this?

Вот конфигурация для проблем безопасности пружины

Файл WebSecurityConfig. java

package com.example.demoImmobilierBack;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import com.example.demoImmobilierBack.service.MyUserDetailsService;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public UserDetailsService userDetailsService() {
        return new MyUserDetailsService();
    }

    @Autowired
    private DataSource dataSource;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService());
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

    @Bean("authenticationManager")
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
}

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(authenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http         
        .headers()
         .frameOptions().sameOrigin()
         .and()
           .authorizeRequests()
            .antMatchers("/**/*.scss", "/**/*.js","/**/*.html").permitAll()
               .antMatchers("/").permitAll()
               .antMatchers("/admin/**").hasRole("ADMIN")
               .anyRequest().authenticated()
               .and()
           .formLogin()
               .loginPage("/api/user/login")
               .defaultSuccessUrl("/")
//               .failureUrl("/login?error")
               .failureUrl("/")
               .permitAll()
               .and()
           .logout()
            .logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
            .logoutSuccessUrl("/")
//            .logoutSuccessUrl("/login?logout")
            .deleteCookies("my-remember-me-cookie")
               .permitAll()
               .and()
            .rememberMe()
             //.key("my-secure-key")
             .rememberMeCookieName("my-remember-me-cookie")
             .tokenRepository(persistentTokenRepository())
             .tokenValiditySeconds(24 * 60 * 60)
             .and()
           .exceptionHandling()
           .and()
           .csrf().disable();
    }

    PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl tokenRepositoryImpl = new JdbcTokenRepositoryImpl();
        tokenRepositoryImpl.setDataSource(dataSource);
        return tokenRepositoryImpl;
    }

}

Пользовательский контроллер UserController. java

package com.example.demoImmobilierBack.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.example.demoImmobilierBack.dto.UserDTO;
import com.example.demoImmobilierBack.service.UserService;

@RestController
@RequestMapping({"/api/user"})
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping(value = "/login",
    method = RequestMethod.POST)
    public @ResponseBody UserDTO login(@RequestBody UserDTO userDTO){
        String message = userService.checkIfUserExistsAndGoodCredential(userDTO);
        if (message.isEmpty()) {
            userDTO = userService.findByEmailAndPassword(userDTO.getEmail(), userDTO.getPassword());
            userDTO.setPassword("");
        } else {
            userDTO.setMessage(message);
        }
        return userDTO;
    }

InitialDataLoader. java file

package com.example.demoImmobilierBack.service;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import javax.transaction.Transactional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import com.example.demoImmobilierBack.model.Privilege;
import com.example.demoImmobilierBack.model.Role;
import com.example.demoImmobilierBack.model.User;
import com.example.demoImmobilierBack.repository.PrivilegeRepository;
import com.example.demoImmobilierBack.repository.RoleRepository;
import com.example.demoImmobilierBack.repository.UserRepository;

@Component
public class InitialDataLoader implements
  ApplicationListener<ContextRefreshedEvent> {

    boolean alreadySetup = false;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private RoleRepository roleRepository;

    @Autowired
    private PrivilegeRepository privilegeRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    @Transactional
    public void onApplicationEvent(ContextRefreshedEvent event) {

        if (alreadySetup)
            return;
        Privilege readPrivilege
          = createPrivilegeIfNotFound("READ_PRIVILEGE");
        Privilege writePrivilege
          = createPrivilegeIfNotFound("WRITE_PRIVILEGE");

        List<Privilege> adminPrivileges = Arrays.asList(
          readPrivilege, writePrivilege);        
        createRoleIfNotFound("ADMIN", adminPrivileges);
        createRoleIfNotFound("LOUEUR", adminPrivileges);
        createRoleIfNotFound("ACHETER", adminPrivileges);
        createRoleIfNotFound("DEPOSE_LOUER", adminPrivileges);
        createRoleIfNotFound("DEPOSE_ACHETER", adminPrivileges);
        createRoleIfNotFound("AGENCE", adminPrivileges);
        createRoleIfNotFound("PROMOTEUR", adminPrivileges);



        Role adminRole = roleRepository.findByName("ADMIN");

        User user = userRepository.findByEmail("flamant@club-internet.fr");
        if (user == null) {
            user = new User();
            user.setGender("M");
            user.setFirstName("adminFirstName");
            user.setLastName("adminLastName");
            user.setRaisonSociale("adminLastName");
            user.setName("PARTICULIER");
            user.setLastName("adminLastName");
            user.setPassword(passwordEncoder.encode("adminPassword"));
            user.setEmail("flamant@club-internet.fr");
            user.setRoles(Arrays.asList(adminRole));
            user.setEnabled(true);
            user.setAccountNonExpired(true);
            user.setAccountNonLocked(true);
            user.setCredentialsNonExpired(true);
            userRepository.save(user);
        }

        alreadySetup = true;
    }

    @Transactional
    private Privilege createPrivilegeIfNotFound(String name) {

        Privilege privilege = privilegeRepository.findByName(name);
        if (privilege == null) {
            privilege = new Privilege(name);
            privilegeRepository.save(privilege);
        }
        return privilege;
    }

    @Transactional
    private Role createRoleIfNotFound(
      String name, Collection<Privilege> privileges) {

        Role role = roleRepository.findByName(name);
        if (role == null) {
            role = new Role(name);
            role.setPrivileges(privileges);
            roleRepository.save(role);
        }
        return role;
    }
}

Реализация UserDetailsService:

package com.example.demoImmobilierBack.service;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import javax.transaction.Transactional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.example.demoImmobilierBack.model.Privilege;
import com.example.demoImmobilierBack.model.Role;
import com.example.demoImmobilierBack.model.User;
import com.example.demoImmobilierBack.repository.RoleRepository;
import com.example.demoImmobilierBack.repository.UserRepository;

@Service("userDetailsService")
@Transactional
public class MyUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    //@Autowired
    //private IUserService service;

    @Autowired
    private MessageSource messages;

    @Autowired
    private RoleRepository roleRepository;

    @Override
    public UserDetails loadUserByUsername(String email)
      throws UsernameNotFoundException {

        User user = userRepository.findByEmail(email);
        if (user == null) {
            return new org.springframework.security.core.userdetails.User(
              " ", " ", true, true, true, true, 
              (Collection<? extends GrantedAuthority>) getAuthorities(Arrays.asList(roleRepository.findByName("ROLE_USER"))));
        }

        return new org.springframework.security.core.userdetails.User(
          user.getEmail(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(), user.isCredentialsNonExpired(), 
          user.isAccountNonLocked(), getRolesAuthorities(user.getRoles()));
    }

    private Collection<? extends GrantedAuthority> getRolesAuthorities(
              Collection<Role> roles) {
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
        for (Role role :roles) {
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }

        return authorities;
    }

    private Collection<? extends GrantedAuthority> getAuthorities(
      Collection<Role> roles) {

        return getGrantedAuthorities(getPrivileges(roles));
    }

    private List<String> getPrivileges(Collection<Role> roles) {

        List<String> privileges = new ArrayList<>();
        List<Privilege> collection = new ArrayList<>();
        for (Role role : roles) {
            collection.addAll(role.getPrivileges());
        }
        for (Privilege item : collection) {
            privileges.add(item.getName());
        }
        return privileges;
    }

    private List<GrantedAuthority> getGrantedAuthorities(List<String> privileges) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        for (String privilege : privileges) {
            authorities.add(new SimpleGrantedAuthority(privilege));
        }
        return authorities;
    }
}

Ответы [ 3 ]

1 голос
/ 09 марта 2020

По крайней мере, до go вперед, вы должны включить CORS на своем бэкэнде. Приложение Angular обслуживается по адресу http://localhost:4200, и браузер отказывается отправлять запрос в другой домен (в данном случае http://localhost:8080).
Подробнее о CORS.

Итак, вы должны внести белый список front-end url в ваше backend приложение.

Вы можете легко сделать это с помощью Spring Boot, добавив несколько строк в ваш класс Application:

@SpringBootApplication
public class Application implements WebMvcConfigurer {
    ...

    /**
     * CORS configuration
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins(
                        "http://localhost:4200"
                )
                .allowedMethods(
                        "GET",
                        "PUT",
                        "POST",
                        "DELETE",
                        "PATCH",
                        "OPTIONS"
                );
    }

    ...
0 голосов
/ 15 марта 2020

Я нашел, как исправить неправильное поведение. Было достаточно добавить строку

.antMatchers("/**/*").permitAll()

в

@Override
protected void configure(HttpSecurity http) throws Exception {
    http         
    .headers()
     .frameOptions().sameOrigin()
     .and()
       .authorizeRequests()
        .antMatchers("/**/*.scss", "/**/*.js","/**/*.html").permitAll()
           .antMatchers("/**/*").permitAll()
           .antMatchers("/admin/**").hasRole("ADMIN")
           .anyRequest().authenticated()
           .and()
       .formLogin()
           .loginPage("/api/user/login")
           .defaultSuccessUrl("/")
           .failureUrl("/")
           .permitAll()
           .and()
       .logout()

. Добавление этой строки позволит неаутентифицированному пользователю получить доступ к запросам, инициированным на домашней странице, и поэтому не спрашивать не авторизован для отображения формы входа в систему

Принимая во внимание разрешение запросов CORS, я все еще жду объяснения, чтобы понять разницу между SOLUTION1 и SOLUTION2

0 голосов
/ 11 марта 2020

gnana и Arpit и спасибо за ваш ответ.

Рассматривая проблему с CORS, я добавил код, который вы мне дали, и он работает. Но можете ли вы объяснить мне разницу между вашим решением (SOLUTION1) и (SOLUTION2 :) тем, что я добавил файл proxy.config. json

{
  "/api/*": {

    "target":  {
       "host": "localhost",
       "protocol": "http:",
       "port": 8080
     },
    "secure": false,
     "changeOrigin": true,
     "logLevel": "info"
  }
}

и запустил переднюю часть с

ng serve --proxy-config proxy.config.json

В SOLUTION2 каждый запрос, запускаемый во внешней части (то есть http://localhost: 4200 ), переводится в http://localhost: 8080 и что о следующем запросе http://localhost: 8080 / api / user / login , который автоматически запускается при запуске приложения angular

С помощью вашего решения (SOLUTION1) вы разрешаете запрос такого как http://localhost: 4200 (но на самом деле, когда SOLUTION2 все еще работает, этот запрос преобразуется в передней части в http://localhost: 8080 ) и в то же время во внутренней части принять на себя запрос http://localhost: 8080 / api / user / login , который запускается при первом запуске приложения angular (домашняя страница приложения, не содержащая логин страница (позже отображается, когда я нажмите на кнопку))

Если вы можете дать мне какое-то объяснение, спасибо

Учитывая другие вопросы, которые я задаю в первом посте, я исследовал дальше

Дон Не знаю, почему запрос http://localhost: 8080 / api / user / login запускается, когда я запускаю домашнюю страницу внешнего интерфейса приложения. Тьерри предложил поделиться моим кодом angular, но я не уверен, что он поможет: единственный запрос, который предоставляет логин для части angular, не сработал. Я проверил это. Процесс аутентификации для входа в интерфейсную часть основан на форме, которая появляется (появляется) при нажатии на кнопку. Тьерри, не могли бы вы предложить мне, какой частью приложения angular вы хотите, чтобы я поделился. Спасибо

Что я могу сказать вам, когда у меня есть следующий контроллер

@RestController
@RequestMapping({"/api/user"})
public class UserController {

@Autowired
private UserService userService;

@RequestMapping(value = "/login",
method = RequestMethod.POST)
public @ResponseBody UserDTO login(@RequestBody UserDTO userDTO){
    String message = userService.checkIfUserExistsAndGoodCredential(userDTO);
    if (message.isEmpty()) {
        userDTO = userService.findByEmailAndPassword(userDTO.getEmail(), userDTO.getPassword());
        userDTO.setPassword("");
    } else {
        userDTO.setMessage(message);
    }
    return userDTO;
}

Следующий URL: http://localhost: 8080 / api / user / login срабатывает (почему? нет никакой причины, которую я вижу, единственное, что я могу сказать, это то, что он зависит от следующего кода на обратной стороне

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http         
        .headers()
         .frameOptions().sameOrigin()
         .and()
           .authorizeRequests()
            .antMatchers("/**/*.scss", "/**/*.js","/**/*.html").permitAll()
               .antMatchers("/").permitAll()
               .antMatchers("/admin/**").hasRole("ADMIN")
               .anyRequest().authenticated()
               .and()
           .formLogin()
               .loginPage("/api/user/login")
               .defaultSuccessUrl("/")
//               .failureUrl("/login?error")
               .failureUrl("/")
               .permitAll()

Когда я проверяю Firebug, у меня есть следующий заголовок

URL de la requête : http://localhost:8080/api/user/login
Méthode de la requête : GET
Adresse distante : 127.0.0.1:8080
Code d’état :405

и следующий ответ

timestamp   2020-03-11T11:09:05.482+0000
status  405
error   Method Not Allowed
message Request method 'GET' not supported
path    /api/user/login

Этот ответ является ожидаемым, поскольку он сопоставлен с методом POST, но при изменении сопоставления контроллера на следующий

@RestController
@RequestMapping({"/api/user"})
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping(value = "/dummylogin",
    method = RequestMethod.POST)
    public @ResponseBody UserDTO login(@RequestBody UserDTO userDTO){
        String message = userService.checkIfUserExistsAndGoodCredential(userDTO);
        if (message.isEmpty()) {
            userDTO = userService.findByEmailAndPassword(userDTO.getEmail(), userDTO.getPassword());
            userDTO.setPassword("");
        } else {
            userDTO.setMessage(message);
        }
        return userDTO;
    }

    @RequestMapping(value = "/login",
    method = RequestMethod.GET)
    public @ResponseBody String login(){
        return "Hello world";
    } 

Я получаю следующий заголовок

URL de la requête :http://localhost:8080/api/user/login
Méthode de la requête : GET
Adresse distante : 127.0.0.1:8080
Code d’état :200

и следующий ответ

Hello world

Не могли бы вы объяснить, почему этот запрос запускается автоматически, и если я не могу отказаться от этого поведения, как я могу должны отобразить его на части контроллера? Спасибо

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