当前位置: 首页 > 知识库问答 >
问题:

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

和和裕
2023-03-14

我试图在Spring Boot中使用JWT实现身份验证。在登录功能中,我在SecurityContextHolder中设置身份验证,以便能够在请求时获得它。登录功能可以工作,但当我尝试获取当前登录的用户时,我会变得不受欢迎。我进行了调试,SecurityContextHolder提供了匿名用户。为什么会这样?

UserController类:

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

}

共有1个答案

吴修洁
2023-03-14

I Spring留档,在请求之间存储SecurityContext部分说:

根据应用程序的类型,可能需要有一种策略来存储用户操作之间的安全上下文。在典型的Web应用程序中,用户登录一次,随后由他们的会话ID标识。服务器缓存会话持续时间的主体信息。在Spring Security中,在请求之间存储SecurityContext的责任落在SecurityContextPeristenceFilter身上,默认情况下,它将上下文存储为HTTP请求之间的HttpSession属性。它为每个请求将上下文恢复到SecurityContextHolder,并且至关重要的是,在请求完成时清除SecurityContextHolder

因此,基本上,当您手动创建安全上下文时,不会创建会话对象。只有当请求完成处理时,Spring Security机制才意识到会话对象为null(当它在处理请求后尝试将安全上下文存储到会话中时)。

在请求结束时,Spring Security会创建一个新的会话对象和会话ID。但是,这个新的会话ID永远不会发送到浏览器,因为它发生在请求结束时,在对浏览器做出响应之后。当下一个请求包含上一个会话ID时,这会导致新会话ID(以及包含手动登录用户的安全上下文)丢失。

我找到了两种解决方案来处理这种情况:

1、第一种解决方案:在会话中保存SecurityContext对象,然后在需要时从会话中提取:

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

然后,从会话中提取它。

根据这个答案,第二个解决方案是重构您的登录功能,如下所示:

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

};

以下是获取authenticationProvider的方法:

@Configuration public class WebConfig extends WebSecurityConfigurerAdapter {  
 
@Bean          
public AuthenticationManager authenticationProvider() throws Exception{  
   return super.authenticationManagerBean();     
  } 
}
 类似资料:
  • 似乎Springboot自动配置自己来使用Tomcat的Logback。我想禁用它,并使用我在类路径中提供的一个。 LoggerFactory不是LoggerContext,但Logback位于类路径上。删除Logback或类[org.slf4j.impl.SimpleLoggerFactory]的竞争实现(类org.slf4j.impl.SimpleLoggerFactory)对象必须是类ch.

  • 我尝试将springboot应用程序配置为每天登录一个文件,因此我将logback.xml配置为: 所以当我尝试运行我的应用程序时,我得到了这个错误:

  • 问题内容: 我无法使用JavaFX WebView登录到Google。当我单击“下一步”按钮时,该页面无法加载。 在不同网站上的其他登录也可以正常工作。 这是您可以运行的示例: 屏幕截图在这里 问题答案: 精简版: 在加载页面之前,将以下行添加到您的main方法中: 长版: 我的第一个直觉是JavaScript无法正常工作,但是我测试了伪邮件并正确地得到了错误: 找不到您的Google帐户 因此,

  • 我无法在JavaFX WebView中登录谷歌。当我单击“下一步”按钮时,页面未加载。 不同网站上的其他登录也可以。 这是一个您可以运行的示例: 此处截图

  • 我一直在关注symfony网站上的教程(http://symfony.com/doc/current/security.html)为我的Symfony 3.1项目添加登录名。它可以与HTTP基本身份验证一起工作,但是当我使用这里的教程添加form_登录时(http://symfony.com/doc/current/security/form_login_setup.html)它会一直重定向到登录

  • 以下是错误消息 java.lang.IllegalStateException:无法加载ApplicationContext 一个使用elasticSearch、mysql、redis等的Spingboot项目,谷歌有很多,但他只是一个新的Spingboot。网上的东西不管用。我不知道怎么改。 application-local.yml 应与ES的配置相关 控制器