Рубрики
Без рубрики

Веб-сайт Json Знак: Как защитить API Spring Boot REST

В этом посте я покажу, как защитить ваш REST API на основе spring boot. Это было скорее тенденцией… Помеченный java, весенняя загрузка.

В этом посте я покажу, как защитить ваш REST API на основе spring boot. В последнее время стало более распространенной тенденцией защищать API REST, чтобы избежать ненужных вызовов общедоступных API. Мы будем использовать некоторые функции загрузки Spring для обеспечения безопасности Spring вместе с JSONWebTokens для авторизации.

Поток пользователей в этом случае составляет

  1. Пользователь входит в систему
  2. Мы проверяем учетные данные пользователя
  3. Маркер отправляется обратно агенту пользователя.
  4. Пользователь пытается получить доступ к защищенному ресурсу.
  5. Пользователь отправляет JWT при доступе к защищенному ресурсу. Мы подтверждаем JWT.
  6. Если JWT действителен, мы разрешаем пользователю получить доступ к ресурсу.

JSONWebTokens, известные как JWTS, используются для формирования авторизации пользователей. Это помогает нам создавать безопасные API, а также легко масштабируется. Во время аутентификации возвращается веб-токен JSON. Всякий раз, когда пользователь хочет получить доступ к защищенному ресурсу, браузер должен отправлять JWTS в заголовке авторизации вместе с запросом. Здесь следует понять одну вещь: защита REST API является хорошей практикой безопасности.

В принципе, мы покажем

  1. Проверка веб-токена JSON
  2. Подтвердите подпись
  3. Проверьте права доступа клиента

Что вам понадобится?

  • Java 8,
  • База данных MySQL
  • Редактор IntelliJ
  • Градация

Примечание – Это будет не полноценное приложение, а API-интерфейсы REST, основанные на Spring boot, Spring security.

API REST на основе весенней загрузки

Я буду защищать REST API для компании, которую я создал в этом сообщении в блоге REST API . Этот API также включает кэширование. Пользователь попытается получить доступ к /cachedemo/v1/компаниям/ и поскольку API защищены, он получит ответ, подобный приведенному ниже:

Теперь мы расскажем, как защитить этот API и как получить к нему доступ, когда он защищен.

Добавление пользователя и регистрация пользователя

Поскольку мы хотим добавить авторизацию для API, нам понадобится место, где пользователь сможет войти в систему и отправить учетные данные. Эти учетные данные будут проверены, и будет сгенерирован токен. Затем этот токен будет передан в запросе на вызов API. Маркер будет проверен в фильтре авторизации Spring security, который мы добавим. При наличии действительного токена пользователь сможет получить доступ к API.

Создайте пользовательскую модель

package com.betterjavacode.models;

import javax.persistence.*;
import java.io.Serializable;

@Entity(name = "User")
@Table(name = "user")
public class User implements Serializable
{
    public User()
    {

    }

    @Id
    @GeneratedValue(strategy =  GenerationType.IDENTITY)
    private long id;

    @Column(name = "username")
    private String username;

    @Column(name = "password")
    private String password;

    public long getId()
    {
        return id;
    }

    public void setId(long id)
    {
        this.id = id;
    }

    public String getUsername()
    {
        return username;
    }

    public void setUsername(String username)
    {
        this.username = username;
    }

    public String getPassword()
    {
        return password;
    }

    public void setPassword(String password)
    {
        this.password = password;
    }
}

Мы добавим контроллер, где пользователь может зарегистрироваться со своими данными для имени пользователя и пароль .

package com.betterjavacode.resources;

import com.betterjavacode.models.User;
import com.betterjavacode.repositories.UserRepository;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/cachedemo/v1/users")
public class UserController
{
    private UserRepository userRepository;
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    public UserController(UserRepository userRepository, BCryptPasswordEncoder bCryptPasswordEncoder)
    {
        this.userRepository = userRepository;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    @PostMapping("/signup")
    public void signUp(@RequestBody User user)
    {
        user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
        userRepository.save(user);
    }

}

Теперь, когда мы ОТПРАВЛЯЕМ запрос в /cachedemo/v1/пользователи/регистрация , пользователь будет сохранен в базе данных. Пароль для пользователя будет сохранен в зашифрованном формате, также используется BCryptPasswordEncoder . Мы покажем, как пользователь может войти в систему, чтобы создать токен.

Вход пользователя

Для обработки входа пользователя в систему мы добавим фильтр аутентификации , который будет добавлен в цепочку фильтров, и Spring boot соответствующим образом обработает его выполнение. Этот фильтр будет выглядеть так, как показано ниже:

package com.betterjavacode.SpringAppCache;


import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;

public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter
{
    private AuthenticationManager authenticationManager;

    public AuthenticationFilter(AuthenticationManager authenticationManager)
    {
        this.authenticationManager = authenticationManager;
        setFilterProcessesUrl("/login");
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException
    {
        try
        {
            com.betterjavacode.models.User creds = new ObjectMapper().readValue(request.getInputStream(), com.betterjavacode .models.User.class);
            return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(creds.getUsername(), creds.getPassword(),new ArrayList<>()));
        }
        catch(IOException e)
        {
            throw new RuntimeException("Could not read request" + e);
        }
    }

    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain, Authentication authentication)
    {
        String token = Jwts.builder()
                .setSubject(((User) authentication.getPrincipal()).getUsername())
                .setExpiration(new Date(System.currentTimeMillis() + 864_000_000))
                .signWith(SignatureAlgorithm.HS512, "SecretKeyToGenJWTs".getBytes())
                .compact();
        response.addHeader("Authorization","Bearer " + token);
    }
}

По сути, пользователь отправит учетные данные в запросе на URL, заканчивающийся на/login . Этот фильтр поможет аутентифицировать пользователя, при успешной аутентификации в заголовок ответа будет добавлен токен с ключом авторизации.

Проверка и авторизация токенов

Мы добавляем еще один фильтр авторизации фильтра для проверки токена, который мы ранее пропустили через фильтр аутентификации. Этот фильтр будет выглядеть так, как показано ниже:

package com.betterjavacode.SpringAppCache;

import io.jsonwebtoken.Jwts;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;


public class AuthorizationFilter extends BasicAuthenticationFilter
{
    public AuthorizationFilter(AuthenticationManager authenticationManager)
    {
        super(authenticationManager);
    }

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws IOException, ServletException
    {
        String header = request.getHeader("Authorization");
        if(header == null || !header.startsWith("Bearer"))
        {
            filterChain.doFilter(request,response);
            return;
        }

        UsernamePasswordAuthenticationToken authenticationToken = getAuthentication(request);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        filterChain.doFilter(request,response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request)
    {
        String token = request.getHeader("Authorization");
        if(token != null)
        {
            String user = Jwts.parser().setSigningKey("SecretKeyToGenJWTs".getBytes())
                    .parseClaimsJws(token.replace("Bearer",""))
                    .getBody()
                    .getSubject();
            if(user != null)
            {
                return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
            }
            return null;
        }
        return null;
    }
}

Если проверка токена прошла успешно, пользователь возвращается и назначается контексту безопасности.

Чтобы включить Spring security, мы добавим новый класс WebSecurityConfiguration с аннотацией @EnableWebSecurity . Этот класс расширит стандарт WebSecurityConfigurerAdapter . В этом классе мы ограничим наши API, а также добавим некоторые URL-адреса из белого списка, к которым нам потребуется доступ без какого-либо маркера авторизации. Это будет выглядеть так, как показано ниже:

package com.betterjavacode.SpringAppCache;

import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter
{
    private BCryptPasswordEncoder bCryptPasswordEncoder;
    private UserDetailsService userDetailsService;

    private static final String[] AUTH_WHITELIST = {
            "/v2/api-docs",
            "/swagger-resources",
            "/swagger-resources/**",
            "/configuration/ui",
            "/configuration/security",
            "/swagger-ui.html",
            "/webjars/**"
    };

    public WebSecurityConfiguration(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder)
    {
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
        this.userDetailsService = userDetailsService;
    }


    protected void configure(HttpSecurity httpSecurity) throws Exception
    {
        httpSecurity.cors().and().csrf().disable().authorizeRequests()
                .antMatchers(AUTH_WHITELIST).permitAll()
                .antMatchers(HttpMethod.POST, "/cachedemo/v1/users/signup").permitAll()
                .anyRequest().authenticated()
                .and().addFilter(new AuthenticationFilter(authenticationManager()))
                .addFilter(new AuthorizationFilter(authenticationManager()))
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception
    {
        authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource()
    {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**",new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }
}

В настройке метода мы ограничили большинство API, разрешив только URL-адреса Swagger и URL-адрес регистрации. Мы также добавляем фильтры для защиты Http. Мы добавим наш собственный класс UserDetailsServiceImpl для проверки учетных данных пользователя.

package com.betterjavacode.services;

import com.betterjavacode.models.User;
import com.betterjavacode.repositories.UserRepository;
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.Component;

import java.util.Collections;

@Component
public class UserDetailsServiceImpl implements UserDetailsService
{
    private UserRepository userRepository;

    public UserDetailsServiceImpl(UserRepository userRepository)
    {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
    {
        User user = userRepository.findByUsername(username);
        if(user == null)
        {
            throw new UsernameNotFoundException(username);
        }
        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), Collections.emptyList());
    }
}

Демонстрация

Со всеми изменениями кода теперь мы готовы создать пользователя, войти в систему и получить доступ к защищенным API REST. На изображении выше пользователь получает ошибку “Отказано в доступе” для доступа к защищенным API. Чтобы продемонстрировать это, я уже зарегистрировал пользователя с именем пользователя test1 и паролем test@123.

Запрос POST для входа в систему выдаст нам в ответ токен авторизации. Теперь мы используем этот токен в нашем запросе GET для получения данных о компаниях. Этот запрос на получение будет выглядеть следующим образом:

Таким образом, мы показали, как защитить REST API с помощью веб-токена JSON.

Оригинальное сообщение в блоге было опубликовано в моем блоге лучший java-код

Оригинал: “https://dev.to/betterjavacode/json-web-token-how-to-secure-spring-boot-rest-api-3cg2”