我正在嘗試在 Spring Boot 中使用 JWT 實作身份驗證。在登錄功能中,我在 SecurityContextHolder 中設定身份驗證,以便能夠在請求時獲取它。登錄功能有效,但是當我嘗試獲取當前登錄的用戶時,我得到了未經授權。我進行了除錯,SecurityContextHolder 提供了匿名用戶。為什么會這樣?
用戶控制器類:
@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();
}
}
}
網路安全配置:
@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;
}
}
uj5u.com熱心網友回復:
在Spring 檔案中,在請求之間存盤 SecurityContext部分說:
根據應用程式的型別,可能需要制定策略來存盤用戶操作之間的安全背景關系。在典型的 Web 應用程式中,用戶登錄一次,隨后由其會話 ID 標識。服務器快取持續時間會話的主體資訊。在 Spring Security 中,存盤請求之間的 SecurityContext 的責任落在了 SecurityContextPersistenceFilter 上,它默認將背景關系存盤為 HTTP 請求之間的 HttpSession 屬性。它將每個請求的背景關系恢復到 SecurityContextHolder,并且至關重要的是,在請求完成時清除 SecurityContextHolder
所以基本上,當您手動創建安全背景關系時,不會創建會話物件。只有當請求完成處理時,Spring Security 機制才會意識到會話物件為空(當它在請求處理后嘗試將安全背景關系存盤到會話中時)。
在請求結束時,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();
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/489125.html
上一篇:為什么C 不能單獨從函式指標原型推匯出模板型別引數,而不是作為一個包?
下一篇:單頁應用程式和CSRF
