我需要一个单独的 JWT 过滤器来进行多次登录吗?
Posted
技术标签:
【中文标题】我需要一个单独的 JWT 过滤器来进行多次登录吗?【英文标题】:Do I need a separate JWT Filter for Multiple Logins? 【发布时间】:2021-12-11 16:23:40 【问题描述】:用户登录运行良好,但我想将客户模块添加到项目中。我知道我需要编写一个自定义 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);
如您所见,过滤器正在使用自定义 UserDetails 来验证用户名。如何将 Customer userdetails 服务添加到过滤器?这是我的第一个多重登录项目,请多多包涵。
【问题讨论】:
您介意提供您构建此过滤器所遵循的教程的链接吗?另外,您是否考虑过使用Resource Server support?它允许您在不编写自定义过滤器实现的情况下验证 JWT,并且内置在 Spring Security 中。 【参考方案1】:登录时区分用户和客户。因此,调用不同的服务来获取用户详细信息。更多可以在这里找到。 Spring Security user authentication against customers and employee
【讨论】:
也通过这个turreta.com/2016/12/26/…【参考方案2】:如何将 Customer userdetails 服务添加到过滤器?:像使用 UserAccountService
一样注入它。如果你这样做,你正在使用 1 个过滤器(当然,这个过滤器在 1 个 SecurityFilterChain
中),你基本上可以实现你的过滤器,比如:尝试通过 myUserDetailsService
验证你的用户,如果它不成功,继续myCustomerDetailsService
。
对于多登录项目。第二种方法是使用 2 SecurityFilterChain
。例如,1 SecurityFilterChain
的 UserJwtFilter 和 1 SecurityFilterChain
的 CustomJwtFilter。人们通常对不同的登录机制 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 Security 实现您的想法,这取决于您的选择。
【讨论】:
【参考方案3】:在我看来你已经做到了。
@Autowired
private UserAccountService myUserDetailsService;
但我建议使用构造函数而不是 @Autowired。 Spring 将同样填写构造函数参数。当您也使用 lombok 库时,这可能会非常小。 使用构造函数还可以更轻松地进行模拟测试。
如 cmets 中所述已更新:
@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;
【讨论】:
非常感谢@3Fish,自动装配的 UserAccountService 类适用于 Users 。现在我也有一个 CustomerAccountService 用于客户登录。如何使这个单一的过滤器类验证这两个服务。 您可以像使用 myUserDetailsService 一样将其服务添加为另一个注入的依赖项,或者添加另一个执行相同操作的过滤器类。第二种方法可能有更多的代码,但如果两种情况是不同的关注点,它们也应该单独实现。这将使您的课程简短且可维护。您可以使用 @Order(chain.doFilter(request, response);
移至“有效”部分,否则无效请求也会被进一步处理。以上是关于我需要一个单独的 JWT 过滤器来进行多次登录吗?的主要内容,如果未能解决你的问题,请参考以下文章
微服务 - 如何使用 JWT 对单独的 API 微服务进行身份验证