带有 JWT 令牌的 Spring Security 在每个请求上加载 spring UserDetails 对象
Posted
技术标签:
【中文标题】带有 JWT 令牌的 Spring Security 在每个请求上加载 spring UserDetails 对象【英文标题】:Spring Security with JWT token is loading spring UserDetails object on every request 【发布时间】:2020-01-21 14:03:44 【问题描述】:我正在学习使用 JWT 令牌和 Spring Boot 的 Spring Security。我已经正确实施它并且工作正常。但我对 JwtRequestFilter 的工作原理有一个疑问。我已经浏览了几个网站来了解使用 spring boot 的 spring security 并发现了同样的事情。所以让我去主要怀疑。 我在下面添加 JwtRequestFilter 文件。
JwtRequestFilter.java
@Component
public class JwtRequestFilter extends OncePerRequestFilter
@Autowired
private JwtUserDetailsService jwtUserDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
// JWT Token is in the form "Bearer token". Remove Bearer word and get
// only the Token
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer "))
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");
else
logger.warn("JWT Token does not begin with Bearer String");
// Once we get the token validate it.
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null)
// This below line is calling on every request
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
// if token is valid configure Spring Security to manually set
// authentication
if (jwtTokenUtil.validateToken(jwtToken, userDetails))
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// After setting the Authentication in the context, we specify
// that the current user is authenticated. So it passes the
// Spring Security Configurations successfully.
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
filterChain.doFilter(request, response);
为了验证令牌,我们必须提供 spring UserDetails 对象,并且我们从 jwtUserDetailsService 获取 spring UserDetails 对象。因此,此过滤器将调用的每个请求都会执行令牌验证,我们必须在每个请求上调用 jwtUserDetailsService。 我的疑问在于我的 jwtUserDetailsService 我正在添加几个验证并添加用户权限。因此,在 jwtUserDetailsService 中重复以下步骤。
-
使用数据库中的用户名获取用户。
获取用户角色
从数据库获取用户权限。
为 userDetails 分配权限。
JwtUserDetailsService.java
@Service("jwtUserDetailsService")
@Transactional
public class JwtUserDetailsService implements UserDetailsService
@Autowired
private UserRepository userRepository;
@Autowired
private IUserService service;
@Autowired
private MessageSource messages;
@Autowired
private RoleRepository roleRepository;
@Override
public UserDetails loadUserByUsername(String email)
throws UsernameNotFoundException
User user = userRepository.findByEmail(email);
if (user == null)
return new org.springframework.security.core.userdetails.User(
" ", " ", true, true, true, true,
getAuthorities(Arrays.asList(
roleRepository.findByName("ROLE_USER"))));
return new org.springframework.security.core.userdetails.User(
user.getEmail(), user.getPassword(), user.isEnabled(), true, true,
true, getAuthorities(user.getRoles()));
private Collection<? extends GrantedAuthority> getAuthorities(
Collection<Role> roles)
return getGrantedAuthorities(getPrivileges(roles));
private List<String> getPrivileges(Collection<Role> roles)
List<String> privileges = new ArrayList<>();
List<Privilege> collection = new ArrayList<>();
for (Role role : roles)
collection.addAll(role.getPrivileges());
for (Privilege item : collection)
privileges.add(item.getName());
return privileges;
private List<GrantedAuthority> getGrantedAuthorities(List<String> privileges)
List<GrantedAuthority> authorities = new ArrayList<>();
for (String privilege : privileges)
authorities.add(new SimpleGrantedAuthority(privilege));
return authorities;
因此,对于每个请求,这些查询都会执行。有没有更好的方法来做到这一点?因为一旦我在 Spring UserDetails 对象中添加用户权限,为什么我们需要对每个请求再次执行此操作。或者那些只有请求范围。我曾在 spring mvc 上工作过,一旦我们在 spring UserDetails 对象中添加权限,它就会一直存在,直到我点击注销意味着它将存在于 spring 安全上下文中,直到我们将其删除。春季靴子会一样吗?如果我在 Spring UserDetails 对象中添加一次角色和权限详细信息,为什么我们需要再次添加它?
【问题讨论】:
spring security 5 中已经有了 jwt 过滤器,为什么还要实现自己的过滤器 @ThomasAndolf,对不起,我刚刚开始学习这个。所以我不知道 spring security 5 提供了这样的功能。您能否提供任何链接,以便我可以通过它来理解概念。谢谢。 太多人用谷歌搜索和阅读旧教程。始终先查看他们自己的文档。其实真的很好docs.spring.io/spring-security/site/docs/current/reference/… @ThomasAndolf,谢谢,我会调查的。 @ThomasAndolf 至少他们在谷歌搜索:-)。我正在查看文档,但在列表中没有看到 JWT 过滤器(我见过有人扩展了 UsernamePasswordAuthenticationFilter 或 OncePerFilter)。我在 OAuth 下看到了一个令牌过滤器,但他没有在上面实现 OAuth。 dev.to/d_tomov/… 【参考方案1】:一旦用户登录,他的身份验证就建立了,所以你不需要再次进行db调用,在每个请求用户登录后,应该只检查身份验证期间在令牌中设置的角色,你需要验证令牌在每个请求中都没有被篡改 而不是通过从数据库调用加载用户详细信息来创建用户详细信息
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
您还可以在 JWT 声明中对用户的用户名和角色进行编码 并通过解析来自 JWT 的声明来创建 UserDetails 对象。
【讨论】:
【参考方案2】:所以这个过滤器将调用的每个请求然后令牌验证将 执行,我们必须在每个请求上调用 jwtUserDetailsService。
这是不正确的,因为您有一个条件if (SecurityContextHolder.getContext().getAuthentication() == null)
。
因此,第一次验证令牌时,您查询您的用户详细信息服务,获取所有授权并将它们设置为安全上下文(您已经在这样做:SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
)。
此外,使用 JWT 身份验证,您通常甚至不需要访问任何用户详细信息服务,因为理想情况下,所有授权都应包含在令牌本身中。所以你唯一需要做的就是验证令牌的签名。
【讨论】:
我同意你的观点,在每次请求时都不应该调用 jwtUserDetailsService。在这里,我的前端是有角度的,所以每次我收到请求 SecurityContextHolder.getContext().getAuthentication() 总是为空。从角度来看,我在标题中传递令牌。我需要传递任何其他参数吗?据我所知,过滤器将在 Spring Security 之前调用,因此我们无法从 SecurityContextHolder 获取用户。因为我们将如何识别来自 spring 安全上下文持有者的用户身份验证对象。如果我错了,请告诉我。 您可能错误地注册了过滤器。您可以添加SecurityConfig
代码(您实际将此过滤器添加到过滤器链的位置)吗?以上是关于带有 JWT 令牌的 Spring Security 在每个请求上加载 spring UserDetails 对象的主要内容,如果未能解决你的问题,请参考以下文章
带有加密 JWT 访问令牌的 Spring Boot OAuth2
使用带有spring security的keycloak JWT令牌时如何修复403