В этом посте я покажу, как защитить ваш REST API на основе spring boot. В последнее время стало более распространенной тенденцией защищать API REST, чтобы избежать ненужных вызовов общедоступных API. Мы будем использовать некоторые функции загрузки Spring для обеспечения безопасности Spring вместе с JSONWebTokens для авторизации.
Поток пользователей в этом случае составляет
- Пользователь входит в систему
- Мы проверяем учетные данные пользователя
- Маркер отправляется обратно агенту пользователя.
- Пользователь пытается получить доступ к защищенному ресурсу.
- Пользователь отправляет JWT при доступе к защищенному ресурсу. Мы подтверждаем JWT.
- Если JWT действителен, мы разрешаем пользователю получить доступ к ресурсу.
JSONWebTokens, известные как JWTS, используются для формирования авторизации пользователей. Это помогает нам создавать безопасные API, а также легко масштабируется. Во время аутентификации возвращается веб-токен JSON. Всякий раз, когда пользователь хочет получить доступ к защищенному ресурсу, браузер должен отправлять JWTS в заголовке авторизации вместе с запросом. Здесь следует понять одну вещь: защита REST API является хорошей практикой безопасности.
В принципе, мы покажем
- Проверка веб-токена JSON
- Подтвердите подпись
- Проверьте права доступа клиента
Что вам понадобится?
- 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”