Spring Boot - 自定义过滤器/无状态身份验证和@Secured注释

时间:2017-03-06 22:23:57

标签: spring-boot spring-security

在阅读了大约10篇不同的文章后,我一直在努力奋斗超过2个小时而没有运气 我想使用自定义过滤器根据DB和@Secured注释中的角色执行无状态授权。

让我们从数据库中用api-key标识的示例帐户开始:' 6c1bb23e-e24c-41a5-8f12-72d3db0a6979'。
他从DB中获取了以下字符串角色:' FREE_USER_ROLE'。

我的过滤器:

public class ApiKeyAuthFilter extends OncePerRequestFilter {

private final AccountService accountService;

private final GlobalExceptionsAdvice exceptionAdvice;

private static final String API_KEY_HEADER_FIELD = "X-AUTH-KEY";

public static final List<String> NON_AUTH_END_POINTS
        = Collections.unmodifiableList(Arrays.asList("/Accounts", "/Accounts/Login"));

AntPathMatcher pathMatcher = new AntPathMatcher();

public ApiKeyAuthFilter(AccountService accountService, GlobalExceptionsAdvice exceptionAdvice) {
    this.accountService = accountService;
    this.exceptionAdvice = exceptionAdvice;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain fc) throws ServletException, IOException {
    Optional authKey = Optional.ofNullable(request.getHeader(API_KEY_HEADER_FIELD));
    if (!authKey.isPresent()) {
        sendForbiddenErrorMessage(response);
    } else {
        try {
            AccountDTO account = accountService.findByApiKey(authKey.get().toString());
            Set<GrantedAuthority> roles = new HashSet();
            account.getRoles().forEach((singleRole) -> roles.add(new SimpleGrantedAuthority(singleRole.getName())));
            Authentication accountAuth = new UsernamePasswordAuthenticationToken(account.getEmail(), account.getApiKey(),
                    roles);
            SecurityContextHolder.getContext().setAuthentication(accountAuth);
            SecurityContextHolder.getContext().getAuthentication().getAuthorities().forEach((role) -> {
                System.out.println(role.getAuthority());
            });
            fc.doFilter(request, response);
        } catch (ElementDoesNotExistException ex) {
            //TODO: Add logging that user tried to falsy authenticate
            sendForbiddenErrorMessage(response);
        }
    }
}

@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
    return NON_AUTH_END_POINTS.stream().anyMatch(p -> {
        return pathMatcher.match(p, request.getServletPath())
                && request.getMethod().equals("POST");
    });
}

private void sendForbiddenErrorMessage(HttpServletResponse resp) throws IOException {
    ObjectMapper mapper = new ObjectMapper();
    ErrorDetail error = exceptionAdvice.handleAccessDeniedException();
    resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
    resp.setContentType("application/json");
    resp.setCharacterEncoding("UTF-8");
    resp.getWriter().write(mapper.writeValueAsString(error));
}

正如您所看到的,我正在使用X-AUTH-KEY标头来检索提供的apiKey,然后我根据该密钥从数据库中获取信息,并将适当的角色分配给SecurityContextHolder。在此之前一切正常。我正在发送poper apiKey,DB返回&#39; FREE_USER_ROLE&#39;。

我的@Configuration注释类。 (我打赌这里有些不对劲但我不知道是什么):

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class ApiKeySecurityConfiguration extends WebSecurityConfigurerAdapter {

AccountService accountService;

GlobalExceptionsAdvice exceptionAdvice;

@Autowired
public ApiKeySecurityConfiguration(AccountService accountService, GlobalExceptionsAdvice exceptionAdvice) {
    this.accountService = accountService;
    this.exceptionAdvice = exceptionAdvice;
}

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    httpSecurity.csrf().disable();

    httpSecurity.authorizeRequests().anyRequest().authenticated();
    httpSecurity.addFilterBefore(new ApiKeyAuthFilter(accountService, exceptionAdvice), UsernamePasswordAuthenticationFilter.class);
}
}

最后一块拼图 - 使用@Secured的控制器:

@RestController
@RequestMapping("/Accounts")
public class AccountsResource {

   @Secured({"FREE_USER_ROLE"})
   @PutMapping()
   public boolean testMethod() {
       return true;
   }
}

我尝试了两种&#39; FREE_USER_ROLE&#39;和&#39; ROLE_FREE_USER_ROLE&#39;。每次我得到403 Forbidden。

1 个答案:

答案 0 :(得分:0)

所以我昨天花了一些时间在那上面,并且我已经设法使用@PreAuthorize注释。在下面发布代码,因为它可能对将来有用。

过滤器:

@Component
public class ApiKeyAuthFilter extends OncePerRequestFilter {

private final AccountService accountService;

private final GlobalExceptionsAdvice exceptionAdvice;

private static final String API_KEY_HEADER_FIELD = "X-AUTH-KEY";

public static final List<String> NON_AUTH_END_POINTS
        = Collections.unmodifiableList(Arrays.asList("/Accounts", "/Accounts/Login"));

AntPathMatcher pathMatcher = new AntPathMatcher();

@Autowired
public ApiKeyAuthFilter(AccountService accountService, GlobalExceptionsAdvice exceptionAdvice) {
    this.accountService = accountService;
    this.exceptionAdvice = exceptionAdvice;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain fc) throws ServletException, IOException {
    Optional authKey = Optional.ofNullable(request.getHeader(API_KEY_HEADER_FIELD));
    if (!authKey.isPresent()) {
        sendForbiddenErrorMessage(response);
    } else {
        try {
            AccountDTO account = accountService.findByApiKey(authKey.get().toString());
            Set<GrantedAuthority> roles = new HashSet();
            account.getRoles().forEach((singleRole) -> roles.add(new SimpleGrantedAuthority(singleRole.getName())));
            Authentication accountAuth = new UsernamePasswordAuthenticationToken(account.getEmail(), account.getApiKey(),
                    roles);
            SecurityContextHolder.getContext().setAuthentication(accountAuth);
            SecurityContextHolder.getContext().getAuthentication().getAuthorities().forEach((role) -> {
                System.out.println(role.getAuthority());
            });
            fc.doFilter(request, response);
        } catch (ElementDoesNotExistException ex) {
            //TODO: Add logging that user tried to falsy authenticate
            sendForbiddenErrorMessage(response);
        }
    }
}

@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
    return NON_AUTH_END_POINTS.stream().anyMatch(p -> {
        return pathMatcher.match(p, request.getServletPath())
                && request.getMethod().equals("POST");
    });
}

private void sendForbiddenErrorMessage(HttpServletResponse resp) throws IOException {
    ObjectMapper mapper = new ObjectMapper();
    ErrorDetail error = exceptionAdvice.handleAccessDeniedException();
    resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
    resp.setContentType("application/json");
    resp.setCharacterEncoding("UTF-8");
    resp.getWriter().write(mapper.writeValueAsString(error));
}

}

配置文件:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ApiKeySecurityConfiguration extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity.
        csrf().disable().
        sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}

允许任何人使用的安全方法和方法:

@RestController
@RequestMapping("/Accounts")
public class AccountsResource {

@PostMapping
@PreAuthorize("permitAll()")
public boolean forAll() {
    return true;
}


@PutMapping()
@PreAuthorize("hasAuthority('FREE_USER_ROLE')")
public boolean testMethod() {
    return true;
}
}