未经授权的错误:需要完全身份验证才能使用 spring.security v.6 访问此资源

2023-12-06

所以我是 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()。我不知道问题是出在那里还是其他地方


When authorizeHttpRequests被用来代替authorizeRequests, then AuthorizationFilter被用来代替FilterSecurityInterceptor.

据我了解,在 Spring Security 6 之前,AuthorizationFilter and FilterSecurityInterceptor仅对第一个请求执行授权检查,但从 Spring Security 6 开始,它似乎至少适用于每个请求(即所有调度程序类型)AuthorizationFilter (FilterSecurityInterceptor现已弃用)。

所以我的猜测是,它实际上在您的请求未调度时有效,但一旦您的请求转发到另一个资源,它就会失败。

也许尝试明确表明您不想将授权规则应用于所有调度程序类型,方法是使用shouldFilterAllDispatcherTypes or dispatcherTypeMatchers.

例如,授予对调度程序类型的请求的所有访问权限ASYNC or FORWARD :

http
...
.authorizeHttpRequests()
.dispatcherTypeMatchers(DispatcherType.ASYNC, DispatcherType.FORWARD).permitAll()
...

当然你也可以自定义它来需要特定的角色(hasRole) 代替permitAll如果需要的话。

See the 文档了解更多信息。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

未经授权的错误:需要完全身份验证才能使用 spring.security v.6 访问此资源 的相关文章

随机推荐