Spring Boot 3.0 中的 Spring Security 访问被拒绝

huangapple go评论77阅读模式
英文:

Spring Security Access Denied with Spring Boot 3.0

问题

我在做一个社交网络的宠物项目,但在授权方面遇到了问题。当我使用JWT令牌发送请求时,我收到"Access Denied"错误。

我正在执行以下操作:

  1. 注册,我在其中指定了登录邮箱和密码(正常工作)。
  2. 授权,通过登录和密码获取令牌(正常工作)。
  3. 我试图在Postman中使用接收到的令牌发送请求,但出现错误(Access Denied)。

我的代码有什么问题?

我的安全配置:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(proxyTargetClass = true)
public class WebSecurityConfig {
    @Autowired
    UserDetailsServiceImpl userDetailsService;

    @Autowired
    private AuthEntryPointJwt unauthorizedHandler;

    @Bean
    public AuthTokenFilter authenticationJwtTokenFilter() {
        return new AuthTokenFilter();
    }
    
    // ... 其他配置
}

AuthTokenFilter:

@Slf4j
public class AuthTokenFilter extends OncePerRequestFilter {
    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    // ... 其他代码
}

JWTUtils:

@Component
@Slf4j
public class JwtUtils {

    @Value("${jwt.token.secret}")
    private String jwtSecret;

    @Value("${jwt.token.jwtExpirationMs}")
    private int jwtExpirationMs;

    // ... 其他代码
}

控制器方法:

@GetMapping("/requests")
@PreAuthorize("hasRole('ROLE_USER')")
public UsernamesResponse getFriendRequests() {
    return userService.getFriendRequests();
}

堆栈跟踪:

org.springframework.security.access.AccessDeniedException: Access Denied
    at org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.attemptAuthorization(AuthorizationManagerBeforeMethodInterceptor.java:257) ~[spring-security-core-6.0.0.jar:6.0.0]
    // ... 其他堆栈信息

编辑 1:
JWT令牌示例:

eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiaWF0IjoxNjg1NjIyMjc4LCJleHAiOjE2ODU2MjU4Nzh9.A_gw2V1c403vxANRcO5LCU7621TMYvYeBKKb-YJv_ZTjEPKym140YAlIjnqAOhoQtfhKRm2O-pJke-7zzglzSg

编辑 2:
在您提供的信息中,我无法立即确定问题的根本原因。可能的原因包括JWT令牌的生成和验证过程中存在问题,或者权限配置不正确。建议您执行以下步骤来诊断问题:

  1. 确保JWT令牌的生成和验证功能正常。您可以添加日志或调试语句来跟踪JWT令牌的生成和验证过程,并确保它们按预期工作。

  2. 检查Spring Security的配置。确保您的权限配置正确,并且控制器上的@PreAuthorize注解与用户的角色匹配。

  3. 检查Postman请求是否正确。确保您在Postman中正确设置了请求头,将JWT令牌添加到"Authorization"头中。

  4. 确保您的应用程序没有其他安全相关的配置问题,如CORS配置等。

如果您仍然遇到问题,请提供更多详细信息,例如JWT令牌的生成和验证代码,以便更具体地帮助您解决问题。

英文:

I'm doing a pet project of a social network and I'm having problems with authorization.I getting Access Denied when I send a request with a jwt token.

I am doing the following chain of actions:

  1. Registration where I specify the login email and password (Works well)
  2. Authorization where by login and password I get a token (Works well)
  3. I'm trying to send a request in the postman with the received token where I get an error(Access Denied)

Whats wrong with my code?

My Security Config:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity( proxyTargetClass = true)
public class WebSecurityConfig  {  
    @Autowired
    UserDetailsServiceImpl userDetailsService;

    @Autowired
    private AuthEntryPointJwt unauthorizedHandler;

    @Bean
    public AuthTokenFilter authenticationJwtTokenFilter() {
        return new AuthTokenFilter();
    }
    
    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();

        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());

        return authProvider;
    }
    
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .exceptionHandling()
                .authenticationEntryPoint(unauthorizedHandler)
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeHttpRequests()
                .requestMatchers("/auth/**").permitAll()
                .requestMatchers("/swagger/**").permitAll()
                .requestMatchers("/swagger-ui/**").permitAll()
                .requestMatchers("/v3/api-docs/**").permitAll()
                .requestMatchers("/auth/test/**").permitAll()
                .requestMatchers("/h2/**").permitAll()
                .anyRequest().authenticated();
        
        http.authenticationProvider(authenticationProvider());
        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

AythTokenFilter:

@Slf4j
public class AuthTokenFilter extends OncePerRequestFilter {
    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        try {
            String accessToken = parseJwt(request);
            if (accessToken != null && jwtUtils.validateJwtToken(accessToken)) {
                String username = jwtUtils.getUserNameFromJwtToken(accessToken);

                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null,
                        userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception e) {
            log.error("Cannot set user authentication: {}", e.getMessage());
        }

        filterChain.doFilter(request, response);
    }

    private String parseJwt(HttpServletRequest request) {
        String headerAuth = request.getHeader("Authorization");

        if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
            return headerAuth.substring(7, headerAuth.length());
        }

        return null;
    }
}

JWTUtils:

@Component
@Slf4j
public class JwtUtils {

    @Value("${jwt.token.secret}")
    private String jwtSecret;

    @Value("${jwt.token.jwtExpirationMs}")
    private int jwtExpirationMs;

    public String generateJwtToken(UserDetailsImpl userPrincipal) {
        return generateTokenFromUsername(userPrincipal.getUsername());
    }

    public String generateTokenFromUsername(String username) {
        return Jwts.builder().setSubject(username).setIssuedAt(new Date())
                .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)).signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    public String getUserNameFromJwtToken(String token) {
        return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
    }

    public boolean validateJwtToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException e) {
            log.error("Invalid JWT signature: {}", e.getMessage());
        } catch (MalformedJwtException e) {
            log.error("Invalid JWT token: {}", e.getMessage());
        } catch (ExpiredJwtException e) {
            log.error("JWT token is expired: {}", e.getMessage());
        } catch (UnsupportedJwtException e) {
            log.error("JWT token is unsupported: {}", e.getMessage());
        } catch (IllegalArgumentException e) {
            log.error("JWT claims string is empty: {}", e.getMessage());
        }

        return false;
    }

}

Controller method:

@GetMapping("/requests")
    @PreAuthorize("hasRole('ROLE_USER')")
    public UsernamesResponse getFriendRequests() {
        return userService.getFriendRequests();
    }

Stacktrace:

org.springframework.security.access.AccessDeniedException: Access Denied
at org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.attemptAuthorization(AuthorizationManagerBeforeMethodInterceptor.java:257) ~[spring-security-core-6.0.0.jar:6.0.0]
at org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.invoke(AuthorizationManagerBeforeMethodInterceptor.java:198) ~[spring-security-core-6.0.0.jar:6.0.0]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.2.jar:6.0.2]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:752) ~[spring-aop-6.0.2.jar:6.0.2]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:703) ~[spring-aop-6.0.2.jar:6.0.2]
at ru.effectivemobile.socialnetwork.controller.UserController$$SpringCGLIB$$0.getFriendRequests(<generated>) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207) ~[spring-web-6.0.2.jar:6.0.2]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152) ~[spring-web-6.0.2.jar:6.0.2]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-6.0.2.jar:6.0.2]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884) ~[spring-webmvc-6.0.2.jar:6.0.2]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-6.0.2.jar:6.0.2]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.0.2.jar:6.0.2]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1080) ~[spring-webmvc-6.0.2.jar:6.0.2]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:973) ~[spring-webmvc-6.0.2.jar:6.0.2]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1003) ~[spring-webmvc-6.0.2.jar:6.0.2]
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:895) ~[spring-webmvc-6.0.2.jar:6.0.2]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:705) ~[tomcat-embed-core-10.1.1.jar:6.0]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:880) ~[spring-webmvc-6.0.2.jar:6.0.2]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814) ~[tomcat-embed-core-10.1.1.jar:6.0]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-10.1.1.jar:10.1.1]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:110) ~[spring-web-6.0.2.jar:6.0.2]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:365) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:100) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:131) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:85) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.0.jar:6.0.0]
at ru.effectivemobile.socialnetwork.security.jwt.AuthTokenFilter.doFilterInternal(AuthTokenFilter.java:47) ~[classes/:na]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.2.jar:6.0.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) ~[spring-web-6.0.2.jar:6.0.2]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.2.jar:6.0.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.2.jar:6.0.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.context.SecurityContextHolderFilter.doFilterInternal(SecurityContextHolderFilter.java:69) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.2.jar:6.0.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.2.jar:6.0.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.2.jar:6.0.2]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191) ~[spring-security-web-6.0.0.jar:6.0.0]
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:351) ~[spring-web-6.0.2.jar:6.0.2]
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267) ~[spring-web-6.0.2.jar:6.0.2]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.0.2.jar:6.0.2]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.2.jar:6.0.2]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.0.2.jar:6.0.2]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.2.jar:6.0.2]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.0.2.jar:6.0.2]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.2.jar:6.0.2]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1739) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.1.jar:10.1.1]
at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

EDIT 1

Jwt token example:

eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiaWF0IjoxNjg1NjIyMjc4LCJleHAiOjE2ODU2MjU4Nzh9.A_gw2V1c403vxANRcO5LCU7621TMYvYeBKKb-YJv_ZTjEPKym140YAlIjnqAOhoQtfhKRm2O-pJke-7zzglzSg

EDIT 2
Spring Boot 3.0 中的 Spring Security 访问被拒绝
Spring Boot 3.0 中的 Spring Security 访问被拒绝
Spring Boot 3.0 中的 Spring Security 访问被拒绝
Spring Boot 3.0 中的 Spring Security 访问被拒绝

答案1

得分: 0

您的问题涉及到Spring中"ROLES"和"Authorities"之间的区别,以及将"Roles"视为以"ROLE_"前缀的"Authorities"的Spring hack

为了使用注解@PreAuthorize("hasRole('ROLE_USER')"),您需要定义一个Permission如下:

ROLE_USER("ROLE_USER")

这与您的ERole枚举相关联,如下所示:

ROLE_USER(Set.of(Permission.ROLE_USER, Permission.READ))

如果您仅在控制器级别进行权限检查时使用Permission.READ等权限,请将它们删除。

或者,您可以使用以下方式的注解@PreAuthorize("hasAuthority('READ')")并删除角色。

我已经在分支https://github.com/KoosieDeMoer/social-network.git上进行了更改,使用了带有存根存储库的解决方法。

英文:

Your problem relates to the difference in Spring between ROLES and Authorities and the Spring hack that treats Roles as Authorities prefixed with "ROLE_"

In order to use the annotation @PreAuthorize("hasRole('ROLE_USER')") you need a Permission as follows:

ROLE_USER("ROLE_USER")

Which is related to your ERole enum as follows:
ROLE_USER(Set.of(Permission.ROLE_USER, Permission.READ))

If you are only using the Permission.READ, etc for Controller level authority checking then drop them.

Alternatively use annotation like this @PreAuthorize("hasAuthotity('READ')")and drop the roles.

I have made changes (on branch fix/authorities at https://github.com/KoosieDeMoer/social-network.git) with stubbed out repo that work.

答案2

得分: 0

以下是您要翻译的部分:

你只使用了JWT方法的一半功效。结果是,每次请求都在TokenAuthFilter中进行了数据库读取。这是非常低效的。

如果您提取当前JWT的JSON内容,它包含以下内容:

{
"sub": "test",
"iat": 1685622278,
"exp": 1685625878
}

如果令牌包含以下内容,将会更好(而且相当标准):

eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiZW1haWwiOiJ0ZXN0QG1haWwuY29tIiwicm9sZXMiOiJST0xFX1VTRVIiLCJpYXQiOjE2ODU3NzY5MDAsImV4cCI6MTY4NTc4MDUwMH0.tR5ikXzTEs4dmDfANi69QgI-LjW424fzNu_spovqnyWPzcSfvboTu8fZLHFZFOFFIH3299HFXVYNor0-IcgyBg

因为它包含了角色和电子邮件,所以不需要TokenAuthFilter每次收到请求时从数据库读取此信息。

以下是您代码的示例实现:

JwtUtils

public String generateJwtToken(UserDetailsImpl userPrincipal) {
    return Jwts.builder().setSubject(userPrincipal.getUsername()).claim("email", userPrincipal.getEmail()).claim("roles", userPrincipal.getRole()).setIssuedAt(new Date())
    .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)).signWith(SignatureAlgorithm.HS512, jwtSecret)
    .compact();
}

public UserDetails getUserPrincipalFromJwtToken(String token) {
    User partialUser = new User();
    
    Claims jwtBody = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
    partialUser.setUserName(jwtBody.get("sub", String.class));
    partialUser.setEmail(jwtBody.get("email", String.class));
    String role = jwtBody.get("roles", String.class);
    partialUser.setRole(ERole.valueOf(role));
    
    return UserDetailsImpl.build(partialUser);
}

AuthTokenFilter

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
    try {
        String accessToken = parseJwt(request);
        if (accessToken != null && jwtUtils.validateJwtToken(accessToken)) {

            UserDetails userDetails = jwtUtils.getUserPrincipalFromJwtToken(accessToken);
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null,
                    userDetails.getAuthorities());
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
    } catch (Exception e) {
        log.error("Cannot set user authentication: {}", e.getMessage());
    }

    filterChain.doFilter(request, response);
}

AuthService

public JwtResponse authenticate(SigninRequest request){
    Authentication authentication = authenticationManager
            .authenticate(new UsernamePasswordAuthenticationToken(request.username(), request.password()));

   SecurityContextHolder.getContext().setAuthentication(authentication);

    UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();

    String accessToken = jwtUtils.generateJwtToken(userDetails);

    ERole role  = userDetails.getRole();

    RefreshToken refreshToken = refreshTokenService.createRefreshToken(userDetails.getId());

    return new JwtResponse(accessToken,refreshToken.getToken());
}

public NewTokensResponse updateToken(UpdateTokenRequest request){
    String requestRefreshToken = request.refreshToken();

    return refreshTokenService.findByToken(requestRefreshToken)
            .map(refreshTokenService::verifyExpiration)
            .map(RefreshToken::getUser)
            .map(user -> {
                String token = jwtUtils.generateJwtToken(UserDetailsImpl.build(user));
                return new NewTokensResponse(token, requestRefreshToken);
            })

            .orElseThrow(() -> new RuntimeException("Refresh token is not in database"));
}

这是一个不错的资源。

英文:

You are only using half the power of the JWT approach. As a result you are doing a DB read in TokenAuthFilter for every request. This is very inefficient.

If you extract the JSON contents of your current JWT it contains the following:

{
"sub": "test",
"iat": 1685622278,
"exp": 1685625878
}

It would be better (and pretty standard) if the token contained the following:

eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiZW1haWwiOiJ0ZXN0QG1haWwuY29tIiwicm9sZXMiOiJST0xFX1VTRVIiLCJpYXQiOjE2ODU3NzY5MDAsImV4cCI6MTY4NTc4MDUwMH0.tR5ikXzTEs4dmDfANi69QgI-LjW424fzNu_spovqnyWPzcSfvboTu8fZLHFZFOFFIH3299HFXVYNor0-IcgyBg

.

{
"sub": "test",
"email": "test@mail.com",
"roles": "ROLE_USER",
"iat": 1685776900,
"exp": 1685780500
}

Because it contains the roles and email, it removes the need for the TokenAuthFilter to read this info from the DB for each incoming request.

The following is the example implementation on your code:

JwtUtils

public String generateJwtToken(UserDetailsImpl userPrincipal) {
return Jwts.builder().setSubject(userPrincipal.getUsername()).claim("email", userPrincipal.getEmail()).claim("roles", userPrincipal.getRole()).setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)).signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public UserDetails getUserPrincipalFromJwtToken(String token) {
User partialUser = new User();
Claims jwtBody = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
partialUser.setUserName(jwtBody.get("sub", String.class));
partialUser.setEmail(jwtBody.get("email", String.class));
String role = jwtBody.get("roles", String.class);
partialUser.setRole(ERole.valueOf(role));
return UserDetailsImpl.build(partialUser);
}

AuthTokenFilter

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String accessToken = parseJwt(request);
if (accessToken != null && jwtUtils.validateJwtToken(accessToken)) {
UserDetails userDetails = jwtUtils.getUserPrincipalFromJwtToken(accessToken);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null,
userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception e) {
log.error("Cannot set user authentication: {}", e.getMessage());
}
filterChain.doFilter(request, response);
}

AuthService

public JwtResponse authenticate(SigninRequest request){
Authentication authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(request.username(), request.password()));
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
String accessToken = jwtUtils.generateJwtToken(userDetails);
ERole role  = userDetails.getRole();
RefreshToken refreshToken = refreshTokenService.createRefreshToken(userDetails.getId());
return new JwtResponse(accessToken,refreshToken.getToken());
}
public NewTokensResponse updateToken(UpdateTokenRequest request){
String requestRefreshToken = request.refreshToken();
return refreshTokenService.findByToken(requestRefreshToken)
.map(refreshTokenService::verifyExpiration)
.map(RefreshToken::getUser)
.map(user -> {
String token = jwtUtils.generateJwtToken(UserDetailsImpl.build(user));
return new NewTokensResponse(token, requestRefreshToken);
})
.orElseThrow(() -> new RuntimeException("Refresh token is not in database"));
}

This is a good resource.

huangapple
  • 本文由 发表于 2023年6月1日 19:17:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/76381320.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定