我需要一个单独的JWT过滤器用于多个登录吗?



用户登录工作良好,但我想添加一个客户模块的项目。我知道我需要编写一个自定义UserDetails类来获取客户用户名,但我想问是否需要为客户登录验证编写另一个自定义JWT过滤器。目前这是过滤器类,我有用户登录。我已经向Customer实体添加了一个用户名和密码字段。


@Component
public class JwtRequestFilter extends OncePerRequestFilter {

@Autowired
private JwtTokenUtil jwtTokenUtil;

@Autowired
private UserAccountService myUserDetailsService;


@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;

if (requestTokenHeader != null) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} 

if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

UserDetails userDetails = this.myUserDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {

UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());

String authorities = userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority)
.collect(Collectors.joining());
System.out.println("Authorities granted : " + authorities);

usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
else {
System.out.println("Not Valid Token");
}
}
chain.doFilter(request, response);
}

}

可以看到,Filter正在使用自定义UserDetails来验证用户名。如何将Customer userdetails服务添加到过滤器中?这是我的第一个多重登录项目,请对我宽容。

在记录时区分用户和客户。相应地,调用不同的服务来获取用户详细信息。更多信息可以在这里找到。Spring Security用户对客户和员工的身份验证

  1. 如何将Customer userdetails服务添加到过滤器中?像注射UserAccountService一样注射。如果你这样做,你使用1个过滤器(当然,这个过滤器是在1个SecurityFilterChain),你基本上可以实现你的过滤器:尝试通过myUserDetailsService验证你的用户,如果它不成功,继续使用myCustomerDetailsService

  2. 对于多个登录项目。第二种方法是使用2SecurityFilterChain。例如,UserJwtFilter用于1个SecurityFilterChain, CustomJwtFilter用于1个SecurityFilterChain。人们通常对不同的登录机制这样做Basic, OAuth2, SAML2。例句:

  • 基本身份验证:
@Configuration
@Order(2)
public class BasicAuthenticationFilterChain extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/login", "/logout")
.and()
  • OAuth2身份认证:
@Configuration
@Order(3)
public class OAuth2AuthenticationFilterChain extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/oauth")
.and()

在这种情况下,当一个带有"/login"它将被定向到BasicAuthenticationFilterChain,并带有"/oauth"我会去OAuth2AuthenticationFilterChain。关于Order:越低优先级越高,一旦请求被SecurityFilterChain处理,它就不会转到另一个SecurityFilterChain。你可以用这种方式实现你的项目。

结论:有很多方法可以实现你的spring安全的想法,这取决于你的选择。

在我看来你已经这么做了。

@Autowired
private UserAccountService myUserDetailsService;

但是我建议使用构造函数而不是@Autowired。Spring将以同样的方式填充构造函数参数。当您也使用lombok库时,这可能非常小。使用构造函数还可以使模拟测试更容易一些。


按注释中讨论的更新:


@Log //another lombok thing
@RequiredArgsConstructor
@Component
public class JwtRequestFilter extends Filter{

private final JwtTokenUtil jwtTokenUtil;

private final UserAccountService myUserDetailsService;

private final CustomerAccountService myCustomerDetailsService;
private static final String AUTH_HEADER = "authorization";

@Override
protected void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
String tokenHeader = ((HttpServletRequest) request).getHeader(AUTH_HEADER);
if(hasValue(tokenHeader) && tokenHeader.toLowerCase().startsWith("bearer ")){
jwtToken = requestTokenHeader.substring(7);
String username;
String jwtToken;

try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);

if (uSecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = myUserDetailsService.loadUserByUsername(username);
if(isNull(userDetails)){
userDetails = myCustomerDetailsService.loadCustomerByUsername(username);
}
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
var token = createSecurityToken(userDetails);
SecurityContextHolder.getContext().setAuthentication(token);
} else {
throw new RuntimeException("Not a Valid Token.");
}
} else {
log.info("Authorization already present");
}
} catch (IllegalArgumentException e) {
throw new("Unable to get JWT Token",e);
} catch (ExpiredJwtException e) {
throw new("JWT Token has expired",e);
}
} else {
throw new RuntimeException("No valid authorization header found.");
}
chain.doFilter(request, response);
}
private UsernamePasswordAuthenticationToken createSecurityToken(UserDetails userDetails){
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
log.info("Authorities granted : {}", userDetails.getAuthorities());
token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
return token;
}
}