Spring Security с Rest API с React - PullRequest
       97

Spring Security с Rest API с React

0 голосов
/ 26 апреля 2020

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

Я искал Много и нашел статью о Spring Security с Basi c Auth, но я не могу понять, как преобразовать эту аутентификацию в остальные API и затем управлять ею через сеанс / куки. Даже те ссылки на github, которые я получил, очень стары или не полностью перешли на весеннюю безопасность 5.

Так что не в состоянии найти правильный подход к обеспечению остальных API. (Будет ли это просто весенняя защита, весенняя защита + jwt, весенняя защита + jwt + весенняя сессия + приготовление ie)

Редактировать

Проверка имени пользователя из БД

@Component
CustomUserDetailsService -> loadUserByUsername -> Mongo Db 

Pass Encryption

@Bean
public PasswordEncoder passwordEncoder() { ... }

Cross Origin

@Bean
public WebMvcConfigurer corsConfigurer() { ... }

Регистрационный контроллер

@RestController
public class RegistrationController {
@PostMapping("/registration")
@ResponseStatus(HttpStatus.CREATED)
@ResponseBody
public ResponseEntity registerUserAccount(... ) { ... }
]

Пн go Сессия

build.gradle
implementation 'org.springframework.session:spring-session-data-mongodb'
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'

@Configuration
@EnableMongoHttpSession

Итак, выше я уже реализовал. После этого я застрял на том, как сохранить пользователя в сеансе и продолжать проверять пользователя на этом.

Ответы [ 2 ]

0 голосов
/ 26 апреля 2020

Basi c авторизация:

(я предполагаю, что вы знаете, как создавать конечные точки, и у вас есть базовые знания о создании как простого приложения Spring Boot, так и приложения реагирования, поэтому я буду придерживайтесь только авторизации topi c.)

При авторизации basi c ваше веб-приложение должно отправлять учетные данные пользователя при каждом вызове API. И мы должны принять во внимание, что ваш бэкэнд, вероятно, открыт на localhost:8080 и во внешнем интерфейсе localhost:3000, поэтому нам приходится иметь дело с CORS. (подробнее о CORS Обмен ресурсами между источниками (CORS) и CORS в Spring Security Spring Security CORS )

Начнем с настройки безопасности, в которой мы видим конечные точки.

 @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
            // by default uses a Bean by the name of corsConfigurationSource
                .cors(withDefaults())
                .csrf().disable()
                .authorizeRequests()
                .antMatchers(HttpMethod.POST, "/login").authenticated()
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .antMatchers(HttpMethod.GET, "/cars").authenticated()
                .anyRequest().authenticated()
                .and()
                .httpBasic();
    }
//and cors configuration
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();

        configuration.setAllowedOrigins(Collections.singletonList("http://localhost:3000"));
        configuration.setAllowedHeaders(List.of("*"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST", "OPTIONS"));
        configuration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);

        return source;
    }

У нас есть конечные точки /login и /cars, которые требуют аутентификации. Если вы запустите бэкэнд-приложение и откроете браузер на localhost:8080/login (или /cars не имеет значения), то в середине экрана появится окно с базовой авторизацией c. Имя пользователя по умолчанию в Spring Security - user, и пароль генерируется в вашей консоли. Скопируйте и вставьте пароль.

Теперь go в приложение переднего плана. Предположим, что у нас есть простое приложение с двумя полями: имя пользователя и пароль и кнопка: логин. Теперь нам нужно реализовать logi c.

...
basicAuthorize = () => {
             let username = this.state.username;
             let password = this.state.password;

            fetch("http://localhost:8080/login", {
                headers: {
                    "Authorization": 'Basic ' + window.btoa(username + ":" + password)
                }
            }).then(resp => {
                console.log(resp);
                if (resp.ok) {
                    this.setState({
                        isLoginSucces: true});
                } else {
                    this.setState({isLoginSucces: false});
                }

                return resp.text();
            });
    }
...

Идя сверху, мы имеем:

  1. Учетные данные пользователя
  2. Заголовок для авторизации в соответствии с basi c authorization spe c on MDN web docks Авторизационный заголовок
  3. Если ответ ok, мы можем где-то хранить учетные данные пользователя, и при следующих вызовах API мы должны снова включить заголовок авторизации. (но мы не должны хранить конфиденциальные данные пользователя, например, LocalStorage или SessionStorage для производства, но для разработки все в порядке Хранение учетных данных в локальном хранилище )

JWT:

Что такое JWT, вы можете прочитать на этом сайте Jwt.io . Вы также можете отлаживать токены, что полезно при попрошайничестве.

Создание конечной точки аутентификации и логики c.
JWT довольно сложно реализовать, поэтому полезно создать некоторые классы, которые помогут реализовать это.

Как и здесь, самое важное:

  • JwtTokenRequest tokenRequest - это POJO с username и password, просто чтобы получить его из логина с внешнего интерфейса и отправить его дальше.
  • JwtTokenResponse, также POJO, это просто единственная строка токена, которая отправляется в cook ie
  • Я также получаю TimeZone для установки срока действия токена.
@PostMapping("/authenticate")
    public ResponseEntity<String> createJwtAuthenticationToken(@RequestBody JwtTokenRequest tokenRequest, HttpServletRequest request, HttpServletResponse response, TimeZone timeZone)
    {
        try
        {
            JwtTokenResponse accessToken = authenticationService.authenticate(tokenRequest, String.valueOf(request.getRequestURL()), timeZone);

            HttpCookie accessTokenCookie = createCookieWithToken("accessToken", accessToken.getToken(), 10 * 60);


            return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, accessTokenCookie.toString()).body("Authenticated");
        }
        catch (AuthenticationException e)
        {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
        }
    }

//creating cookie
private HttpCookie createCookieWithToken(String name, String token, int maxAge)
    {
        return ResponseCookie.from(name, token)
                .httpOnly(true)
                .maxAge(maxAge)
                .path("/")
                .build();
    }

Служба, отвечающая за аутентификацию и создание токена

@Service
public class JwtAuthenticationService
{
    private AuthenticationManager authenticationManager;

    private final String SECRET_KEY = "SecretKey";

    public JwtAuthenticationService(AuthenticationManager authenticationManager)
    {
        this.authenticationManager = authenticationManager;
    }

    public JwtTokenResponse authenticate(JwtTokenRequest tokenRequest, String url, TimeZone timeZone) throws AuthenticationException
    {
        UserDetails userDetails = managerAuthentication(tokenRequest.getUsername(), tokenRequest.getPassword());

        String token = generateToken(userDetails.getUsername(), url, timeZone);

        return new JwtTokenResponse(token);
    }

Управление аутентификацией. Вам не нужно проверять, относится ли пароль к имени пользователя вручную, потому что если у вас реализован loadByUsername, Spring будет использовать этот метод для загрузки пользователя и проверки пароля. Проверка подлинности пользователя вручную с помощью Spring Security

private UserDetails managerAuthentication(String username, String password) throws AuthenticationException
    {
        Authentication authenticate = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));

        return (UserDetails) authenticate.getPrincipal();
    }

Если не выдано исключение, это означает, что учетные данные пользователя верны, тогда мы можем сгенерировать токен JWT.

В этом примере I Я использую библиотеку Java JWT , которую можно добавить в файл pom.xml.

Этот метод генерирует токен в соответствии с часовым поясом из запроса, а также сохраняет URL-адрес запроса информации.

private String generateToken(String username, String url, TimeZone timeZone)
    {
        try
        {
            Instant now = Instant.now();

            ZonedDateTime zonedDateTimeNow = ZonedDateTime.ofInstant(now, ZoneId.of(timeZone.getID()));

            Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
            String token = JWT.create()
                    .withIssuer(url)
                    .withSubject(username)
                    .withIssuedAt(Date.from(zonedDateTimeNow.toInstant()))
                    .withExpiresAt(Date.from(zonedDateTimeNow.plusMinutes(10).toInstant()))
                    .sign(algorithm);

            return token;
        }
        catch (JWTCreationException e)
        {
            e.printStackTrace();
            throw new JWTCreationException("Exception creating token", e);
        }
    }

Если все было в порядке, то токен хранится в http-cook ie.

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

public class JwtFilter extends OncePerRequestFilter
{
    private final String SECRET_KEY = "SecretKey";
}

//or load from other source
public class JwtFilter extends OncePerRequestFilter
{
    private final String SECRET_KEY = ApplicationConstants.SECRET_KEY;
}
  • Реализация метода из родительского класса
  • Зависит от того, откуда вы получаете токены, нам просто нужно его загрузить. В этом примере я использую HttpOnly cook ie
  • Если присутствует повар ie, тогда выполните авторизацию
@Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException
    {
        Cookie tokenCookie = null;
        if (request.getCookies() != null)
        {
            for (Cookie cookie : request.getCookies())
            {
                if (cookie.getName().equals("accessToken"))
                {
                    tokenCookie = cookie;
                    break;
                }
            }
        }

        if (tokenCookie != null)
        {
            cookieAuthentication(tokenCookie);
        }

        chain.doFilter(request, response);
    }
  • Если вся проверка прошла, то установите в SecurityContextHolder, что этот пользователь аутентифицирован. Что такое SecurityContextHolder, вы можете прочитать здесь 10.1. SecurityContextHolder
private void cookieAuthentication(Cookie cookie)
    {
        UsernamePasswordAuthenticationToken auth = getTokenAuthentication(cookie.getValue());

        SecurityContextHolder.getContext().setAuthentication(auth);
    }

private UsernamePasswordAuthenticationToken getTokenAuthentication(String token)
    {
        DecodedJWT decodedJWT = decodeAndVerifyJwt(token);

        String subject = decodedJWT.getSubject();

        Set<SimpleGrantedAuthority> simpleGrantedAuthority = Collections.singleton(new SimpleGrantedAuthority("USER"));

        return new UsernamePasswordAuthenticationToken(subject, null, simpleGrantedAuthority);
    }

    private DecodedJWT decodeAndVerifyJwt(String token)
    {
        DecodedJWT decodedJWT = null;
        try
        {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET_KEY))
                    .build();

            decodedJWT = verifier.verify(token);

        } catch (JWTVerificationException e)
        {
            //Invalid signature/token expired
        }

        return decodedJWT;
    }

А теперь запрос фильтруется токеном в cook ie. Мы должны добавить пользовательский фильтр в Spring Security:

@Override
    protected void configure(HttpSecurity http) throws Exception
    {
...
//now 'session' is managed by JWT        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.addFilterBefore(new JwtFilter(), UsernamePasswordAuthenticationFilter.class);
    }

Во внешнем интерфейсе у вас мало работы.
В вашем запросе вам просто нужно добавить withCredentials: 'include', тогда куки будут отправлены с просьбой. Вы должны использовать 'include', потому что это перекрестный запрос. Request.credentials

Пример запроса:

fetch('http://localhost:8080/only-already-authenticated-users', {
      method: "GET",
      credentials: 'include',
      headers: {
        'Content-Type': 'application/json'
      },
    })
0 голосов
/ 26 апреля 2020

Для моего первого проекта полного стека безопасности пружины,

Я использовал React для переднего конца, пружинную защиту пружины для бэкенда.

Несколько простых шагов для запуска.

  1. Создание пользователя вручную в базе данных

  2. На странице входа в систему позвоните после входа в систему api:

    const config = {headers: {'Content-Type ':' application / x- www-form-urlencoded '}};

    axios.post('http://localhost:9090/login',querystring.stringify( { username: username.value, password: password.value }),config).then(response => {
      setLoading(false);
      setUserSession(null, username.value);
    
      props.history.push('/landingpage');
    })
    
  3. Создайте проект весенней загрузки, где - создайте API post rest для обработки входа в систему - Добавьте зависимость безопасности Spring в pom - Создайте класс, который реализует UserDetailsService (SpringDity предоставляет UserDetailsService для извлечения сведений о пользователе из БД и предоставления перехватчикам безопасности ) - Теперь настройте класс UserDetailsService в файле конфигурации с помощью @Bean, чтобы Spring Security могла его идентифицировать. Кроме того, в вашем файле конфигурации расширен класс WebSecurityConfigurerAdapter и объявлен метод configure в соответствии с вашим использованием (вы можете найти несколько примеров этого в сети)

Вот и все, вы готовы к go после этого.

Есть несколько способов, и это всего лишь простой способ научиться проверять форму Войти

...