所以我是 Spring Security 的新手,我尝试使用 REST 服务构建 Spring Boot 应用程序,并使用 JWT 实现 Spring Security。我大部分时间都遵循了安全教程,并且我运行了应用程序,但是当我尝试调用身份验证(/auth/**)端点时,我只收到 401 未经授权的错误。自从 spring 更新到 v 3.0.0 以来WebSecurityConfigurerAdapter
已弃用我已相应地更改了配置。
这是我的配置文件
import com.example.myshroombackend.security.JwtAuthenticationEntryPoint;
import com.example.myshroombackend.security.JwtAuthenticationFilter;
import com.example.myshroombackend.service.AuthServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig{
@Autowired
AuthServiceImpl authService;
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Bean JwtAuthenticationFilter jwtAuthenticationFilter(){
return new JwtAuthenticationFilter();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(authService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfiguration) throws Exception {
return authConfiguration.getAuthenticationManager();
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers("/js/**", "/images/**");
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf()
.and()
.cors()
.disable()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeHttpRequests()
.requestMatchers("/auth/**").permitAll() //every /auth/login or /auth/register are permitted
.anyRequest().authenticated(); //all other requests must be authenticated ==>
// you first have to authenticate yourself to perform the other requests
http.authenticationProvider(authenticationProvider());
http.addFilterBefore(jwtAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
我的自定义过滤器
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
AuthServiceImpl authService;
@Autowired
private JwtTokenProvider jwtTokenProvider;
@Override
protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response,@NotNull FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = parseJwt(request);
if (jwt != null && jwtTokenProvider.validateToken(jwt)) {
String username = jwtTokenProvider.getUserNameFromToken(jwt);
UserDetails userDetails = authService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails,
null,
userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
logger.error("Cannot set user authentication: {}", e);
}
filterChain.doFilter(request, response);
}
private String parseJwt(HttpServletRequest request) {
String jwt = jwtTokenProvider.getJwtFromCookies(request);
return jwt;
}
}
我的令牌服务
import com.example.myshroombackend.exception.JwtAuthenticationException;
import com.example.myshroombackend.service.AuthServiceImpl;
import com.example.myshroombackend.service.UserDetailsImpl;
import io.jsonwebtoken.*;
import jakarta.servlet.http.Cookie;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseCookie;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.util.WebUtils;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Optional;
@Component
@Slf4j
public class JwtTokenService {
@Value("${application.secret")
private String jwtSecret;
@Value("${application.jwtCookieName}")
private String jwtCookie;
private AuthServiceImpl authService;
private static final Instant EXPIRATIONTIME = Instant.now().plus(20, ChronoUnit.HOURS);
public String generateToken(String userName) {
Instant now = Instant.now();
return Jwts.builder()
.setSubject(userName)
.setIssuedAt(Date.from(now))
.setExpiration(Date.from(EXPIRATIONTIME))
.signWith(SignatureAlgorithm.HS512, jwtSecret.getBytes(StandardCharsets.UTF_8))
.compact();
}
public boolean validateToken(String token) {
if ((token != null) && (!"".equals(token))) {
Jws<Claims> claimsJws;
try{
claimsJws = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
if (claimsJws != null) {
final String userId = Optional
.ofNullable(claimsJws.getBody().get(new String("id")))
.map(Object::toString)//
.orElseThrow(
() -> new AuthenticationCredentialsNotFoundException("No username given in jwt"));
}
return true;
} catch (ExpiredJwtException e) {
throw new RuntimeException(e);
} catch (UnsupportedJwtException e) {
throw new RuntimeException(e);
} catch (MalformedJwtException e) {
throw new RuntimeException(e);
} catch (SignatureException e) {
throw new RuntimeException(e);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (AuthenticationCredentialsNotFoundException e) {
throw new RuntimeException(e);
}
}
return false;
}
public String generateToken(Authentication authentication) {
User user = (User) authentication.getPrincipal();
return generateToken(user.getUsername());
}
public String getJwtFromCookies(HttpServletRequest request) {
Cookie cookie = WebUtils.getCookie(request, jwtCookie);
if (cookie != null) {
return cookie.getValue();
} else {
return null;
}
}
public String generateTokenFromUsername(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + 10000000))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public ResponseCookie generateJwtCookie(UserDetailsImpl userPrincipal) {
String jwt = generateTokenFromUsername(userPrincipal.getUsername());
ResponseCookie cookie = ResponseCookie.from(jwtCookie, jwt).path("/auth").maxAge(24 * 60 * 60).httpOnly(true).build();
return cookie;
}
public String getUserNameFromToken(final String token) throws JwtAuthenticationException {
String userName = null;
try {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(jwtSecret.getBytes(StandardCharsets.UTF_8)).parseClaimsJws(token);
if (claimsJws != null) {
userName = claimsJws.getBody().getSubject();
}
} catch (final SignatureException | MalformedJwtException | UnsupportedJwtException ex) {
log.error("Unsupported jwt token {} with exception {}",
token,
ex.getMessage());
throw new JwtAuthenticationException(ex);
} catch (final ExpiredJwtException ex) {
log.error("Expired jwt token {}",
ex.getMessage());
throw new JwtAuthenticationException(ex);
} catch (final AuthenticationCredentialsNotFoundException ex) {
log.error("An error occured while trying to create authentication based on jwt token, missing credentials {}",
ex.getMessage());
throw new JwtAuthenticationException(ex);
} catch (final Exception ex) {
log.error("Unexpected exception occured while parsing jwt {} exception: {}",
token,
ex.getMessage());
throw new JwtAuthenticationException(ex);
}
return userName;
}
}
还有我的 RestController
import com.example.myshroombackend.dto.LoginRequestDto;
import com.example.myshroombackend.entity.Rank;
import com.example.myshroombackend.entity.UserEntity;
import com.example.myshroombackend.repository.UserRepository;
import com.example.myshroombackend.security.JwtTokenService;
import com.example.myshroombackend.service.UserDetailsImpl;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
@RestController
@RequestMapping("/auth")
@CrossOrigin
public class AuthController {
private final UserRepository userRepository;
private AuthenticationManager authenticationManager;
private JwtTokenService provider;
private PasswordEncoder encoder;
public AuthController(UserRepository userRepository, AuthenticationManager authenticationManager, JwtTokenService provider) {
this.userRepository = userRepository;
this.authenticationManager = authenticationManager;
this.provider = provider;
}
@PostMapping("/login")
public ResponseEntity<LoginRequestDto> loginUser(@RequestBody LoginRequestDto dto) {
Authentication authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(dto.getUserName(), dto.getPassword()));
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
SecurityContextHolder.getContext().setAuthentication(authentication);
ResponseCookie jwtCookie = provider.generateJwtCookie(userDetails);
return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, jwtCookie.toString())
.body(new LoginRequestDto(
userDetails.getUsername()));
}
@PostMapping("/register")
public ResponseEntity<?> registerUser( @RequestBody LoginRequestDto dto) {
Optional<UserEntity> optionalUserEntity = userRepository.findByUserName(dto.getUserName());
if (optionalUserEntity.isPresent()) {
return ResponseEntity.badRequest().body("User already in database");
}
// Create new user's account
UserEntity userEntity = new UserEntity();
userEntity.setUserName(dto.getUserName());
userEntity.setPassword(encoder.encode(dto.getUserName()));
userEntity.setRank(Rank.BEGINNER);
userRepository.save(userEntity);
return ResponseEntity.ok("User registered successfully!");
}
}
I use requestMatchers()
代替antMatchers()
此安全版本中不再包含该内容,并且authorizeHttpRequests()
代替authorizeRequests()
。我不知道问题是出在那里还是其他地方