我有一个 Spring Boot/Spring Security 应用程序,我想将 JWT 身份验证与 Spring Security 提供的 JdbcUserDetailsManager 一起使用。
这是我目前所拥有的:
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://www.toptal.com/spring/spring-security-tutorial 启发的 JWT 相关代码
注意:我不确定在数据库中验证用户权限是否有意义,因为 JWT 的卖点之一是它是无状态的......我可以将权限放在 JWT 令牌中。
答案 0 :(得分:0)
查看 @Lazy 注释,我遇到了同样的问题,但它的效果非常好。
尝试类似:
val recyclerView = view.recyclerView
recyclerView.adapter = adapter
recyclerView.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
recyclerView.itemAnimator = SlideInUpAnimator().apply {
addDuration = 350
答案 1 :(得分:0)
如果使用@Autowired
代替构造函数注入可以解决问题。
示例:
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private JwtTokenFilter jwtTokenFilter;
private final DataSource dataSource;
public Security(DataSource dataSource)
{
this.dataSource = dataSource;
}
}