简体   繁体   English

无法在 Spring Boot 中从 SecurityContextHolder 设置登录用户

[英]Unable to set logged user from SecurityContextHolder in Spring Boot

I am trying to implement authentication using JWT in Spring Boot.我正在尝试在 Spring Boot 中使用 JWT 实现身份验证。 In the login function I am setting the authentication in the SecurityContextHolder in order to be able to get it when requested.在登录功能中,我在 SecurityContextHolder 中设置身份验证,以便能够在请求时获取它。 The login functionality works, but when I try to get the current logged user, I am getting unathorized.登录功能有效,但是当我尝试获取当前登录的用户时,我得到了未经授权。 I debugged and the SecurityContextHolder gives anonymous user.我进行了调试,SecurityContextHolder 提供了匿名用户。 Why is this happening?为什么会这样?

UserController class:用户控制器类:

@RestController
@CrossOrigin(origins = "http://localhost:3000")
@RequestMapping("/api")
public class UserController {

    @Autowired
    private UserService userService;
    
    @Autowired
    private CustomAuthenticationManager authenticationManager;
    
    @Autowired
    private JwtEncoder jwtEncoder;
    
    @PostMapping("/user/login")
      public ResponseEntity<User> login(@RequestBody @Valid AuthDto request) {
        try {
          Authentication authentication = authenticationManager
            .authenticate(new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword()));

          String userEmail = (String) authentication.getPrincipal();
          User user = userService.findUserByEmail(userEmail);
          Instant now = Instant.now();
          long expiry = 36000L;

          String scope = authentication.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .collect(joining(" "));

          JwtClaimsSet claims = JwtClaimsSet.builder()
            .issuer("uni.pu")
            .issuedAt(now)
            .expiresAt(now.plusSeconds(expiry))
            .subject(format("%s,%s", user.getId(), user.getEmail()))
            .claim("roles", scope)
            .build();

          String token = this.jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
          

          SecurityContextHolder.getContext().setAuthentication(authentication);

          return ResponseEntity.ok()
            .header(HttpHeaders.AUTHORIZATION, token)
            .body(user);
        } catch (BadCredentialsException ex) {
          return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
      }
    
    @GetMapping("/user/current")
    public ResponseEntity<User> getLoggedUser(){
        try{
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            return ResponseEntity.ok()
                    .body((User)auth.getPrincipal());
        }
        catch(Exception e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
        }
         
    }
}

WebSecurityConfig:网络安全配置:

@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, jsr250Enabled = true, prePostEnabled = true)

public class WebSecurityConfig {
private static final String[] WHITE_LIST_URLS = {"/api/user/login", "/api/user/current"};

@Autowired
private MyUserDetailsService userDetailsService;

@Value("${jwt.public.key}")
private RSAPublicKey rsaPublicKey;
@Value("${jwt.private.key}")
private RSAPrivateKey rsaPrivateKey;

@Bean
public PasswordEncoder encoder() {
    return new BCryptPasswordEncoder(10);
}

@Bean
public DaoAuthenticationProvider authenticationProvider() {
    DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
    authProvider.setUserDetailsService(userDetailsService);
    authProvider.setPasswordEncoder(encoder());

    return authProvider;
}

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    // Enable CORS and disable CSRF
    http = http.cors().and().csrf().disable();

    // Set session management to stateless
    http = http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and();
    // Set unauthorized requests exception handler
    http = http.exceptionHandling(
            (exceptions) -> exceptions.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
                    .accessDeniedHandler(new BearerTokenAccessDeniedHandler()));

    http = http.authenticationProvider(authenticationProvider());
    
    // Set permissions on endpoints
    http.authorizeHttpRequests().antMatchers(WHITE_LIST_URLS).permitAll().antMatchers("/api/**").authenticated()
            // Our private endpoints
            .anyRequest().authenticated()
            // Set up oauth2 resource server
            .and().httpBasic(Customizer.withDefaults()).oauth2ResourceServer().jwt();
    
    return http.build();
}

@Bean
public JwtEncoder jwtEncoder() {
    JWK jwk = new RSAKey.Builder(this.rsaPublicKey).privateKey(this.rsaPrivateKey).build();
    JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
    return new NimbusJwtEncoder(jwks);
}

// Used by JwtAuthenticationProvider to decode and validate JWT tokens
@Bean
public JwtDecoder jwtDecoder() {
    return NimbusJwtDecoder.withPublicKey(this.rsaPublicKey).build();
}

// Extract authorities from the roles claim
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
    JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
    jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
    jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");

    JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
    jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
    return jwtAuthenticationConverter;
}
@Bean
public CorsFilter corsFilter() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOrigin("*");
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");
    source.registerCorsConfiguration("/**", config);
    return new CorsFilter(source);
}

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

@Bean
public RoleHierarchy roleHierarchy() {
    RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
    String hierarchy = "ROLE_ADMIN > ROLE_INSPECTOR \n ROLE_INSPECTOR > ROLE_STUDENT";
    roleHierarchy.setHierarchy(hierarchy);
    return roleHierarchy;
}

} }

In Spring documentation , section Storing the SecurityContext between requests says :Spring 文档中,在请求之间存储 SecurityContext部分说:

Depending on the type of application, there may need to be a strategy in place to store the security context between user operations.根据应用程序的类型,可能需要制定策略来存储用户操作之间的安全上下文。 In a typical web application, a user logs in once and is subsequently identified by their session Id.在典型的 Web 应用程序中,用户登录一次,随后由其会话 ID 标识。 The server caches the principal information for the duration session.服务器缓存持续时间会话的主体信息。 In Spring Security, the responsibility for storing the SecurityContext between requests falls to the SecurityContextPersistenceFilter, which by default stores the context as an HttpSession attribute between HTTP requests.在 Spring Security 中,存储请求之间的 SecurityContext 的责任落在了 SecurityContextPersistenceFilter 上,它默认将上下文存储为 HTTP 请求之间的 HttpSession 属性。 It restores the context to the SecurityContextHolder for each request and, crucially, clears the SecurityContextHolder when the request completes它将每个请求的上下文恢复到 SecurityContextHolder,并且至关重要的是,在请求完成时清除 SecurityContextHolder

So basically, when you create the security context manually no session object is created.所以基本上,当您手动创建安全上下文时,不会创建会话对象。 Only when the request finishes processing does the Spring Security mechanism realize that the session object is null (when it tries to store the security context to the session after the request has been processed).只有当请求完成处理时,Spring Security 机制才会意识到会话对象为空(当它在请求处理后尝试将安全上下文存储到会话中时)。

At the end of the request Spring Security creates a new session object and session ID.在请求结束时,Spring Security 创建一个新的会话对象和会话 ID。 However this new session ID never makes it to the browser because it occurs at the end of the request, after the response to the browser has been made.然而,这个新的会话 ID 永远不会到达浏览器,因为它发生在请求结束时,在对浏览器做出响应之后。 This causes the new session ID (and hence the Security context containing my manually logged on user) to be lost when the next request contains the previous session ID.当下一个请求包含前一个会话 ID 时,这会导致新的会话 ID(以及因此包含我手动登录的用户的安全上下文)丢失。

I found two solutions to hande this situation:我找到了两种解决方案来处理这种情况:

1.First solution : Save SecurityContext object in session and then extract it from session when needed : 1.第一个解决方案:将SecurityContext对象保存在会话中,然后在需要时从会话中提取它:

HttpSession session = request.getSession(true);
session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);

and then, extract it from session.然后,从会话中提取它。

  1. Second solution according to this answer would be to refactor your login function like this:根据此答案的第二种解决方案是重构您的登录功能,如下所示:

     private void doAutoLogin(String username, String password, HttpServletRequest request) { try { // Must be called from request filtered by Spring Security, otherwise SecurityContextHolder is not updated UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password); token.setDetails(new WebAuthenticationDetails(request)); Authentication authentication = this.authenticationProvider.authenticate(token); logger.debug("Logging in with [{}]", authentication.getPrincipal()); SecurityContextHolder.getContext().setAuthentication(authentication); } catch (Exception e) { SecurityContextHolder.getContext().setAuthentication(null); logger.error("Failure in autoLogin", e); }

    }; };

This is how you shoud get authenticationProvider :这就是你应该如何获得 authenticationProvider :

@Configuration public class WebConfig extends WebSecurityConfigurerAdapter {  
 
@Bean          
public AuthenticationManager authenticationProvider() throws Exception{  
   return super.authenticationManagerBean();     
  } 
}

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

相关问题 春季:从SecurityContextHolder获取自定义用户对象 - Spring: Get Custom User Object from SecurityContextHolder 在春季启动中无法在激活中将受让人设置为第二个用户任务 - Unable to set Assignee to second user task in activiti in spring boot 如何从 Spring MVC\\Boot Controller 方法中正确检索登录的用户信息? - How to correctly retrieve the logged user information from a Spring MVC\Boot Controller method? 如何在 Spring Boot 中找出当前登录的用户? - How to find out the currently logged-in user in Spring Boot? Azure Spring Boot-获取已登录用户的OAuth 2.0访问令牌 - Azure Spring Boot - Get OAuth 2.0 Access Token of Logged In User Spring 启动 - 确保数据属于当前登录用户 - Spring Boot - make sure data belongs to current logged in user 为什么 spring 引导对于当前登录的用户返回空值? - Why is spring boot returning empty for currently logged in user? Spring Boot 创建自定义注释以获取登录用户? - Spring boot create a custom annotation to get logged in user? Spring 尝试验证用户是否已登录任何活动的引导问题 - Spring Boot problem trying to verify if user is logged or not for any activity 检查用户是否使用stormpath和spring-boot登录 - Checking whether a user is logged in using stormpath and spring-boot
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM