我目前正在使用 Java Spring 开发一个简单的项目。 为了实现注册和登录功能(用户身份验证和授权),我一起使用 Spring Security 和 JWT 创建了一个身份验证和授权过滤器。
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("Not Found " + username));
return new UserDetailsImpl(user);
}
}
上面的代码是UserDetailsService的实现,用于实现Spring Security。
但是,我认为向数据库发送查询以进行用户身份验证与 JWT 的核心特性之一“无状态”相矛盾。因此,我计划重构代码,以允许在不通过 UserDetailsService 的情况下进行身份验证过程。换句话说,我的目标是仅根据 JWT 中包含的用户信息来实现用户身份验证,而不向数据库发送查询。
因此,我对代码进行了全面的修改,以在不使用UserDetailsService的情况下启用身份验证处理。但是,当我运行代码并发送身份验证请求时,查询仍然会发送到数据库,即使我的代码不使用 UserDetailsService。 (看来由于Spring Security的内部逻辑,必然要经过UserDetailsService。)
在不使用 Spring Security 的情况下创建用于身份验证和授权处理的自定义过滤器可以解决问题,但我很好奇是否有一种方法可以在仍然使用 Spring Security 的同时确保 JWT 的无状态性。
是的,Spring Security 可以。
首先,您需要在
pom.xml
上添加一些依赖项。
<dependencies>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version> <!-- or whatever version you want -->
<scope>runtime</scope>
</dependency>
<!-- Other necessary dependencies (Spring Boot, Spring Data, etc...) -->
<!-- ... -->
</dependencies>
使用自定义身份验证提供程序。
@Component
public class JwtAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
// This should be an appropriate service which would fetch user data and perform JWT validation
private JwtUtil jwtUtil;
@Autowired
public JwtAuthenticationProvider(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
// Your additional authentication checks if needed
}
@Override
protected UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
String token = (String) usernamePasswordAuthenticationToken.getCredentials();
String usernameFromToken = jwtUtil.getUsernameFromToken(token);
return new User(usernameFromToken, "", new ArrayList<>());
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return super.authenticate(authentication);
}
@Override
public boolean supports(Class<?> authentication) {
return (JwtAuthenticationToken.class.isAssignableFrom(authentication));
}
}
在您的安全配置文件中,您应该将
JwtAuthenticationProvider
注入到安全提供程序中。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationProvider jwtAuthenticationProvider;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authenticationProvider(jwtAuthenticationProvider)
// Your other configurations
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/register");
}
// Your other configurations
}
请记住,这样您就真正信任令牌,因此请确保您的令牌受到保护并正确失效,以防止未经授权的访问。
我只在处理内部 API 时使用这种身份验证。