[英]How can I resolve this Spring Security-related cyclical dependency?
我有一個 Spring Boot/Spring Security 應用程序,我想在其中使用 JWT auth 和 JdbcUserDetailsManager,由 Spring Security 提供。
這是我到目前為止所擁有的:
ExampleApplication.java
package org.example.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ExampleApplication {
public static void main(String[] args) {
SpringApplication.run(ExampleApplication.class, args);
}
}
JwtTokenFilter.java (使用 UserDetailsService)
package org.example.app.auth;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import static org.springframework.util.ObjectUtils.isEmpty;
@Component
public class JwtTokenFilter extends OncePerRequestFilter {
private final JwtTokenUtil jwtTokenUtil;
private final UserDetailsService userDetailsService;
public JwtTokenFilter(JwtTokenUtil jwtTokenUtil, JdbcUserDetailsManager jdbcUserDetailsManager) {
this.jwtTokenUtil = jwtTokenUtil;
this.userDetailsService = jdbcUserDetailsManager;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
// Get authorization header and validate
final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (isEmpty(header) || !header.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}
// Get jwt token and validate
final String token = header.split(" ")[1].trim();
if (!jwtTokenUtil.validate(token)) {
chain.doFilter(request, response);
return;
}
// Get user identity and set it on the spring security context
String username = jwtTokenUtil.getUsername(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken
authentication = new UsernamePasswordAuthenticationToken(
userDetails, null,
userDetails == null ?
List.of() : userDetails.getAuthorities()
);
authentication.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
}
}
JwtTokenUtil.java (對於示例而言並不重要)
package org.example.app.auth;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import static java.lang.String.format;
@Component
public class JwtTokenUtil {
Logger logger = LoggerFactory.getLogger(JwtTokenUtil.class);
private final String jwtSecret = "aYEFtKMCn0xCg5caH1nnFuHfdAB0lBOvdonxq80VqOGNnG6QcyagXWOLrUdqJnzexUXYceMhGNFNYsA" +
"6rblSibUEh0yRsJ3XO1um1iMdoekOPzj4zKlokcu9TxTbz5DHYVLkqX3q9JrLgbLZFXD8ynOHfRHRL5Ge64iFZBVm9X517fwZrNornOm" +
"K2L7hUz10SgZpxAz6";
private final String jwtIssuer = "example.org";
public String generateAccessToken(User user) {
byte[] keyBytes = Decoders.BASE64.decode(jwtSecret);
Key key = Keys.hmacShaKeyFor(keyBytes);
return Jwts.builder()
.setSubject(format("%s", user.getUsername()))
.setIssuer(jwtIssuer)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 60 * 1000)) // 1 hour
.signWith(key, SignatureAlgorithm.HS512)
.compact();
}
public String getUsername(String token) {
JwtParser jwtParser = Jwts.parserBuilder()
.setSigningKey(jwtSecret).build();
Claims claims = jwtParser
.parseClaimsJws(token)
.getBody();
return claims.getSubject().split(",")[1];
}
public boolean validate(String token) {
try {
JwtParser jwtParser = Jwts.parserBuilder()
.setSigningKey(jwtSecret).build();
jwtParser.parseClaimsJws(token);
return true;
} catch (SecurityException ex) {
logger.error("Invalid JWT signature - {}", ex.getMessage());
} catch (MalformedJwtException ex) {
logger.error("Invalid JWT token - {}", ex.getMessage());
} catch (ExpiredJwtException ex) {
logger.error("Expired JWT token - {}", ex.getMessage());
} catch (UnsupportedJwtException ex) {
logger.error("Unsupported JWT token - {}", ex.getMessage());
} catch (IllegalArgumentException ex) {
logger.error("JWT claims string is empty - {}", ex.getMessage());
}
return false;
}
}
SecurityConfiguration.java (使用 JwtTokenFilter)
package org.example.app.config;
import org.example.app.auth.JwtTokenFilter;
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.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.sql.DataSource;
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final JwtTokenFilter jwtTokenFilter;
private final DataSource dataSource;
public SecurityConfiguration(JwtTokenFilter jwtTokenFilter, DataSource dataSource) {
this.jwtTokenFilter = jwtTokenFilter;
this.dataSource = dataSource;
}
public void configureHttpSecurity(HttpSecurity http) {
http.addFilterBefore(
jwtTokenFilter,
UsernamePasswordAuthenticationFilter.class
);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Autowired
@Bean
public JdbcUserDetailsManager userDetailsManager(AuthenticationManager authenticationManager,
AuthenticationManagerBuilder authenticationManagerBuilder)
throws Exception {
JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcUserDetailsManagerConfigurer =
authenticationManagerBuilder.jdbcAuthentication().dataSource(dataSource);
JdbcUserDetailsManager jdbcUserDetailsManager = jdbcUserDetailsManagerConfigurer.getUserDetailsService();
jdbcUserDetailsManager.setAuthenticationManager(authenticationManager);
return jdbcUserDetailsManager;
}
}
當應用程序嘗試啟動時,我看到以下內容:
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| jwtTokenFilter defined in file [/example-app/build/classes/java/main/org/example/app/auth/JwtTokenFilter.class]
↑ ↓
| securityConfiguration defined in file [/example-app/build/classes/java/main/org/example/app/config/SecurityConfiguration.class]
└─────┘
我想知道如何解決這種周期性依賴,同時牢記:
免責聲明:受https 啟發的 JWT 相關代碼://www.toptal.com/spring/spring-security-tutorial
注意:我不確定在數據庫中驗證用戶權限是否有意義,因為 JWT 的賣點之一是它是無狀態的......我可以將權限放入 JWT 令牌中。
查看@Lazy注釋,我遇到了同樣的問題,它就像一個魅力。
嘗試類似:
public JwtTokenFilter(JwtTokenUtil jwtTokenUtil, @Lazy JdbcUserDetailsManager jdbcUserDetailsManager) {
this.jwtTokenUtil = jwtTokenUtil;
this.userDetailsService = jdbcUserDetailsManager;
}
如果您使用@Autowired
而不是構造函數注入,則可以解決該問題。
例子:
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private JwtTokenFilter jwtTokenFilter;
private final DataSource dataSource;
public Security(DataSource dataSource)
{
this.dataSource = dataSource;
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.