简体   繁体   English

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

[英]Unauthorized error: Full authentication is required to access this resource with spring.security v.6

So I am new to Spring Security and I tried to build a Spring Boot app with REST services and also implement Spring Security with JWT. I have followed a security tutorial for the most part and I got the app running but when I try to call an authentication(/auth/**) endpoint I just get the 401 Unauthorized error.所以我是 Spring Security 的新手,我尝试使用 REST 服务构建一个 Spring Boot 应用程序,并使用 JWT 实现 Spring Security。我大部分时间都遵循了安全教程,我让应用程序运行但是当我尝试调用authentication(/auth/**) 端点我刚收到 401 Unauthorized 错误。 Since spring has updated to v 3.0.0 and now WebSecurityConfigurerAdapter is deprecated I have changed the configurations accordingly.由于 spring 已更新到 v 3.0.0,现在WebSecurityConfigurerAdapter已弃用,我已相应地更改了配置。

This is my configuration file这是我的配置文件

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();
    }





}

my custom filter我的自定义过滤器


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;
    }

}

my token service我的代币服务


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;
    }

}

also my RestController还有我的 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() instead of antMatchers() that is no longer in this security version and authorizeHttpRequests() instead of authorizeRequests() .我使用requestMatchers()代替不再在此安全版本中的antMatchers()authorizeHttpRequests()代替authorizeRequests() I don't know if the problem is from there or it's something else我不知道问题是出自那里还是其他原因

When authorizeHttpRequests is used instead of authorizeRequests , then AuthorizationFilter is used instead of FilterSecurityInterceptor .当使用authorizeHttpRequests代替authorizeRequests时,则使用AuthorizationFilter代替FilterSecurityInterceptor

And as I understand, prior to Spring Security 6, AuthorizationFilter and FilterSecurityInterceptor were performing authorization checks on the first request only, but since Spring Security 6, it seems to be applied to every request (ie on all dispatcher types) at least for AuthorizationFilter ( FilterSecurityInterceptor is now deprecated).据我了解,在 Spring Security 6 之前, AuthorizationFilterFilterSecurityInterceptor仅对第一个请求执行授权检查,但自 Spring Security 6 以来,它似乎至少适用于AuthorizationFilter ( FilterSecurityInterceptor现已弃用)。

So my guess is that it actually works while your request is non-dispatched but then fail as soon as your request is forwarded to another resource.所以我的猜测是它实际上在您的请求未分派时有效,但一旦您的请求被转发到另一个资源就会失败。

Maybe try to explicitly indicate that you do not want to apply the authorization rules to all dispatcher types, either by using shouldFilterAllDispatcherTypes or dispatcherTypeMatchers .也许尝试通过使用shouldFilterAllDispatcherTypesdispatcherTypeMatchers来明确表明您不想将授权规则应用于所有调度程序类型。

For example to grant all access on requests with dispatcher type ASYNC or FORWARD :例如,授予对调度程序类型为ASYNCFORWARD的请求的所有访问权限:

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

Of course you can also customize it to require a specific role ( hasRole ) instead of permitAll if needed.当然,如果需要,您也可以自定义它以要求特定角色 ( hasRole ) 而不是permitAll

See the documentation for more information.有关详细信息,请参阅文档

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 错误未经授权,需要完全验证才能访问此资源 - Error Unauthorized, Full authentication is required to access this resource 未经授权的错误:需要完全身份验证才能访问此资源 - Unauthorized error: Full authentication is required to access this resource cesecurity.jwt.AuthEntryPointJwt:未经授权的错误:访问此资源需要完全身份验证 - c.e.security.jwt.AuthEntryPointJwt : Unauthorized error: Full authentication is required to access this resource Spring Boot 2: {&quot;error&quot;:&quot;unauthorized&quot;,&quot;error_description&quot;:&quot;访问此资源需要完整身份验证&quot;} - Spring Boot 2: {"error":"unauthorized","error_description":"Full authentication is required to access this resource"} 状态=401,错误=未授权,消息=在尝试接收令牌时需要完全身份验证才能在 Spring 框架中访问此资源 - status=401, error=Unauthorized, message=Full authentication is required to access this resource in Spring Framework when trying to receive a token 使用Spring Security和Keycloak访问此资源需要完全认证 - Full authentication is required to access this resource with Spring Security and Keycloak Spring-Security-Oauth2:访问此资源需要完全身份验证 - Spring-Security-Oauth2: Full authentication is required to access this resource 测试弹簧安全站。 需要完全认证才能访问此资源 - Testing spring security post. Full authentication is required to access this resource Spring Security OAuth - 访问此资源需要完全身份验证 - Spring Security OAuth - Full authentication is required to access this resource spring oauth错误,需要完全认证才能访问此资源 - spring oauth error Full authentication is required to access this resource
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM