Настройка фильтра в Spring Security
Пытаюсь настроить приложение с использованием Spring Security, но не сталкивался еще с созданием собственных фильтров, провайдеров и реализации класса UsernamePasswordAuthenticationToken. Задача: реализовать генерацию JWT-токена, но с добавлением вышеперечисленных классов. Прошу помощи, так как не знаю, куда копать Так Выглядит класс SecurityConfig
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtAuthenticationProvider provider;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService() {
return new CustomUserDetailsService();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authenticationProvider(provider)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.cors(httpSecurityCorsConfigurer -> httpSecurityCorsConfigurer.configurationSource(request ->
new CorsConfiguration().applyPermitDefaultValues()))
.authorizeHttpRequests(authorize -> authorize.requestMatchers("/auth/**").permitAll()
.requestMatchers(HttpMethod.GET, "/secured/**").authenticated())
.exceptionHandling(exceptions -> exceptions.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)));
return http.build();
}
}
MainController
@RestController
@RequestMapping("/secured")
public class MainController {
@GetMapping(value = "/user")
public ResponseEntity<String> getInfo(Principal principal){
return ResponseEntity.ok(principal.getName());
}
}
SecurityController
@RestController
@RequestMapping("/auth")
public class SecurityController {
private final CustomUserDetailsService userService;
private final JwtUtil jwtUtil;
public SecurityController(CustomUserDetailsService userService, JwtUtil jwtUtil) {
this.userService = userService;
this.jwtUtil = jwtUtil;
}
@PostMapping(value = "/signup")
public ResponseEntity<?> signup(@RequestBody RequestDTO signupDTO) {
return ResponseEntity.ok(userService.createPerson(signupDTO));
}
@PostMapping(value = "/signin")
public ResponseEntity<?> signin(@RequestBody RequestDTO requestDTO) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
UserDetails userDetails = userService.loadUserByUsername(requestDTO.username());
if (auth.getName().equals(userDetails.getUsername())) {
String token = jwtUtil.generateToken(userDetails);
return ResponseEntity.ok(token);
}
return ResponseEntity.badRequest().body("NOT VALID");
}
}
RequestDTO
public record RequestDTO(String username, String password) {
}
Класс JwtAuthenticationFilter
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Autowired
private AuthenticationManager authenticationManager;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = extractTokenFromRequest(request);
if (token != null && validateToken(token)) {
Authentication auth = createAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(request, response);
}
private String extractTokenFromRequest(HttpServletRequest request) {
// Логика извлечения токена из запроса (например, из заголовка Authorization)
String headerAuth = request.getHeader("Authorization");
if (headerAuth != null && headerAuth.startsWith("Bearer ")) {
return headerAuth.substring(7);
}
return null;
}
private boolean validateToken(String token) {
// Логика верификации токена
Claims claims = Jwts.parser().setSigningKey(jwtUtil.getSecret()).parseClaimsJws(token).getBody();
Date expirationDate = claims.getExpiration();
return !expirationDate.before(new Date());
}
private Authentication createAuthentication(String token) {
// Логика создания объекта Authentication на основе токена
CustomUserDetails userDetails = extractUserDetailsFromToken(token);
return new JwtAuthenticationToken(userDetails, token, userDetails.getAuthorities());
}
private CustomUserDetails extractUserDetailsFromToken(String token) {
// Логика извлечения информации о пользователе из токена
return jwtUtil.extractUserDetailsFromToken(token);
}
}
Класс JwtAuthenticationProvider
@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserRepository userRepository;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
Person person = userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("Not found"));
if (!password.equals(person.getPassword())){
throw new BadCredentialsException("Invalid password");
}
CustomUserDetails userDetails = (CustomUserDetails) User.builder()
.username(person.getUsername())
.password(person.getPassword())
.roles(person.getRole()).build();
return new JwtAuthenticationToken(userDetails, password, userDetails.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Класс JwtAuthenticationToken
@Component
public class JwtAuthenticationToken extends UsernamePasswordAuthenticationToken {
public JwtAuthenticationToken(CustomUserDetails principal, String credentials, Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
}
}
Класс JwtUtil
@Component
@Data
public class JwtUtil {
@Value("${testing.app.secret}")
private String secret;
@Value("${testing.app.expirationMs}")
private int lifeTime;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private CustomUserDetailsService userDetailsService;
public String generateToken(UserDetails userDetails){
return Jwts.builder().setSubject(userDetails.getUsername())
.setIssuedAt(new Date()).setExpiration(new Date((new Date()).getTime() + lifeTime))
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
public String getNameFromJwt(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();
}
public Authentication createAuthentication(String token) {
// Получение информации о пользователе из токена
String username = getNameFromJwt(token);
CustomUserDetails userDetails = userDetailsService.loadUserByUsername(username);
// Создание объекта Authentication
return authenticationManager.authenticate(new JwtAuthenticationToken(userDetails, token, userDetails.getAuthorities()));
}
public boolean validateToken(String token) {
// Логика верификации токена
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
Date expirationDate = claims.getExpiration();
if (expirationDate.before(new Date())) {
return false;
}
return true;
}
public CustomUserDetails extractUserDetailsFromToken(String token) {
// Логика извлечения информации о пользователе из токена
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
String username = claims.get("username", String.class);
return userDetailsService.loadUserByUsername(username);
}
}
Сущность Person
@Data
@Entity
@Table(name = "persons_table")
@AllArgsConstructor
@NoArgsConstructor
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String role;
public Person(String username, String password, String role) {
this.username = username;
this.password = password;
this.role = role;
}
}
Реализация UserDetails
@Data
public class CustomUserDetails implements UserDetails {
private Person person;
@Autowired
public CustomUserDetails(Person person) {
this.person = person;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("ROLE_USER"));
}
@Override
public String getPassword() {
return person.getPassword();
}
@Override
public String getUsername() {
return person.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;
}
}
Реализация UserDetailsService
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<Person> person = userRepository.findByUsername(username);
try {
return person.map(CustomUserDetails::new).orElseThrow(Exception::new);
} catch (Exception e) {
throw new UsernameNotFoundException("Person not found");
}
}
public Person createPerson(RequestDTO signupDTO) {
Person person = new Person(signupDTO.username(), passwordEncoder.encode(signupDTO.password()), "ROLE_USER");
return userRepository.save(person);
}
}
Главный класс SpringSecurityJwtApplication
@SpringBootApplication
public class SpringSecurityJwtApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSecurityJwtApplication.class, args);
}
}
Файл application.yml
spring:
application:
name: SpringSecurityJWT
datasource:
url: jdbc:postgresql://localhost:5432/postgres
username: postgres
password: 1
driver-class-name: org.postgresql.Driver
jpa:
database: postgresql
database-platform: org.hibernate.dialect.PostgreSQLDialect
show-sql: true
main:
allow-bean-definition-overriding: true
allow-circular-references: true
liquibase:
change-log: db/changelog/db.changelog-master.yaml
enabled: true
testing:
app:
secret: qBTmv4oXFFR2GwjexDJ4t6fsIUIUhhXqlktXjXdkcyygs8nPVEwMfo29VDRRepYDVV5IkIxBMzr7OEHXEHd37w==
expirationMs: 60000
Также файлы миграции liquibase
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd">
<changeSet id="1" author="labazov">
<createTable tableName="persons_table">
<column name="id" type="BIGINT" autoIncrement="true">
<constraints primaryKey="true" nullable="false" unique="true"/>
</column>
<column name="username" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="password" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="role" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
</databaseChangeLog>
При такой конфигурации приложения в терминале ошибка:
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2024-06-08T00:28:57.242+07:00 ERROR 5372 --- [SpringSecurityJWT] [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.example.SpringSecurityJWT.jwt.JwtAuthenticationToken required a bean of type 'com.example.SpringSecurityJWT.service.CustomUserDetails' that could not be found.
Action:
Consider defining a bean of type 'com.example.SpringSecurityJWT.service.CustomUserDetails' in your configuration.
Подскажите, куда копать, что за чем идет? Я сталкивался с генерацией токена один раз, а тут сразу реализация собственных фильтров, провайдеров и так далее...