publicvoidinvoke(FilterInvocation fi)throws IOException, ServletException { if ((fi.getRequest() != null) && (fi.getRequest().getAttribute(FILTER_APPLIED) != null) && observeOncePerRequest) { // filter already applied to this request and user wants us to observe // once-per-request handling, so don't re-do security checking fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } else { // first time this request being called, so perform security checking if (fi.getRequest() != null && observeOncePerRequest) { fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); }
Collection<ConfigAttribute> getAttributes(Object object) 方法的实现:肯定是获取请求中的 URI 来和 所有的 资源配置中的 Ant Pattern 进行匹配以获取对应的资源配置, 这里需要将资源查询接口查询的资源配置封装为 AntPathRequestMatcher以方便进行 Ant Match 。
/** * Json login post processor json login post processor. * * @return the json login post processor */ @Bean public JsonLoginPostProcessor jsonLoginPostProcessor() { returnnewJsonLoginPostProcessor(); }
/** * Pre login filter pre login filter. * * @param loginPostProcessors the login post processors * @return the pre login filter */ @Bean public PreLoginFilter preLoginFilter(Collection<LoginPostProcessor> loginPostProcessors) { returnnewPreLoginFilter(LOGIN_PROCESSING_URL, loginPostProcessors); }
URI 访问资源必然要用 URI 来定位,我们同样通过 URI 来和资源接口进行匹配;最好是 Ant match,因为/user/1 和 /user/2 有可能访问的是同一个资源接口。如果你想避免这种情况,要么在开发规约中禁止这种风格,这样的好处是配置人员可以不必熟悉 Ant 风格;要么必须让配置人员掌握 Ant 风格。
对于受限的访问资源,并不是对所有认证通过的用户开放的。比如 A 用户的角色是会计,那么他就可以访问财务相关的资源。B 用户是人事,那么他只能访问人事相关的资源。我们在上一文中也对基于角色的访问控制的相关概念进行了探讨。在实际开发中我们如何对资源进行角色粒度的管控呢?今天我来告诉你 Spring Security 是如何来解决这个问题的。
2. 将角色写入 UserDetails
我们使用 UserDetailsService 加载 UserDetails 时也会把用户的 GrantedAuthority 权限集写入其中。你可以将角色持久化并在这个点进行注入然后配置访问策略,后续的问题交给 Spring Security 。
if (Objects.nonNull(jsonObject)) { Stringusername= jsonObject.getStr("aud");
// 从缓存获取 token JwtTokenPairjwtTokenPair= jwtTokenStorage.get(username); if (Objects.isNull(jwtTokenPair)) { if (log.isDebugEnabled()) { log.debug("token : {} is not in cache", jwtToken); } // 缓存中不存在就算 失败了 thrownewCredentialsExpiredException("token is not in cache"); } StringaccessToken= jwtTokenPair.getAccessToken();
if (jwtToken.equals(accessToken)) { // 解析 权限集合 这里 JSONArrayjsonArray= jsonObject.getJSONArray("roles");
Stringroles= jsonArray.toString();
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList(roles); Useruser=newUser(username, "[PROTECTED]", authorities); // 构建用户认证token UsernamePasswordAuthenticationTokenusernamePasswordAuthenticationToken=newUsernamePasswordAuthenticationToken(user, null, authorities); usernamePasswordAuthenticationToken.setDetails(newWebAuthenticationDetailsSource().buildDetails(request)); // 放入安全上下文中 SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); } else { // token 不匹配 if (log.isDebugEnabled()){ log.debug("token : {} is not in matched", jwtToken); }
thrownewBadCredentialsException("token is not matched"); } } else { if (log.isDebugEnabled()) { log.debug("token : {} is invalid", jwtToken); } thrownewBadCredentialsException("token is invalid"); } } }
具体看代码注释部分,逻辑有些地方根据你业务进行调整。匿名访问必然是不能带 Token 的!
3.2 配置 JwtAuthenticationFilter
首先将过滤器 JwtAuthenticationFilter 注入 Spring IoC 容器 ,然后一定要将 JwtAuthenticationFilter 顺序置于 UsernamePasswordAuthenticationFilter 之前: