如何在自定义 Spring security 3.0 身份验证中正确处理异常?

Posted

技术标签:

【中文标题】如何在自定义 Spring security 3.0 身份验证中正确处理异常?【英文标题】:How to handle exceptions properly in custom Spring security 3.0 authentication? 【发布时间】:2014-04-26 07:58:44 【问题描述】:

我正在开发基于令牌的 REST 服务。当用户通过 curl 使用用户名和密码进入 ../rest/authenticate 时,获取一个有效令牌以使用整个 API。

当用户忘记在其他方法中插入用户名、密码或令牌时出现我的问题,因为我没有设法按我的意愿处理身份验证异常。

我可以处理异常,但 tomcat 得到响应并插入一些我不期望的 html

这是tomcat的典型响应。

是否有可能收到没有此 html 代码的 200 OK 之类的响应?

目前,这是我的配置:

AuthenticationProcessingFilter

决定网址是否安全。如果必须进行保护,请调用身份验证管理器以对其进行验证。如果收到认证异常调用 AuthenticationEntryPoint

public class AuthenticationTokenProcessingFilter extends GenericFilterBean 

    private final Collection<String> nonTokenAuthUrls = Lists.newArrayList("/rest","/rest/authenticate");

    TokenAuthenticationManager tokenAuthenticationManager;
    RestAuthenticationEntryPoint restAuthenticationEntryPoint;

    public AuthenticationTokenProcessingFilter(TokenAuthenticationManager tokenAuthenticationManager, RestAuthenticationEntryPoint restAuthenticationEntryPoint) 
        this.tokenAuthenticationManager = tokenAuthenticationManager;
        this.restAuthenticationEntryPoint = restAuthenticationEntryPoint;
    

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException 
        HttpServletRequest httpRequest = (HttpServletRequest)request;
        HttpServletResponse httpResponse = (HttpServletResponse)response;
        try
            if(!nonTokenAuthUrls.contains(httpRequest.getRequestURI())) //Auth by token
                 String hash = httpRequest.getHeader("token");
                 UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(hash, null);
                 authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails((HttpServletRequest) request));
                 SecurityContextHolder.getContext().setAuthentication(tokenAuthenticationManager.authenticate(authentication));
            
            response.reset();
            chain.doFilter(request, response); 
        catch(AuthenticationException authenticationException)
            SecurityContextHolder.clearContext();
            restAuthenticationEntryPoint.commence(httpRequest, httpResponse, authenticationException);
        
    

身份验证管理器

public class TokenAuthenticationManager implements AuthenticationManager

    @Autowired
    UserService userService;

    @Autowired
    TokenService tokenService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException 
        Object hash = authentication.getPrincipal();
        if(hash == null)
            throw new BadCredentialsException("Token is required");
        User user = tokenService.getUserFromTokenHash((String)hash); 
        if(user == null)
            throw new BadCredentialsException("Non-existent token");
        if(!tokenService.validate((String)hash)) 
            throw new BadCredentialsException("Expired Token");
        org.springframework.security.core.userdetails.User userDetails = new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getUserGrantedAuthorities(user.getRoles()));
        return new UsernamePasswordAuthenticationToken(userDetails, user.getPassword(), getUserGrantedAuthorities(user.getRoles()));
    

身份验证入口点

这个类工作正常。收到的代码是 401 未授权但消息在 tomcat html 中

公共类 RestAuthenticationEntryPoint 实现 AuthenticationEntryPoint

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authenticationException) throws IOException, ServletException 
    response.setContentType("application/json");
    response.sendError( HttpServletResponse.SC_UNAUTHORIZED, authenticationException.getMessage() );
    response.getOutputStream().println(" \"error\": \"" + authenticationException.getMessage() + "\" ");

RestAccessDeniedHanler 也没有被调用。这很困难,因为需要实现很多类。

我查看了 *** 和其他网站上的一些帖子,我的方法包括在 AuthenticationProcessingFilter 中捕获异常并手动调用 AuthenticationEntryPoint。我决定这样做是因为我尝试在 applicationContext-security.xml 中配置它但没有成功。

appliacionContext-security.xml

<b:bean id="restAuthenticationEntryPoint" class="...web.security.RestAuthenticationEntryPoint" /> 

<b:bean id="tokenAuthenticationManager" class="...dp.web.security.TokenAuthenticationManager"/>

<b:bean id="AuthenticationTokenProcessingFilter" class="...web.security.AuthenticationTokenProcessingFilter">
    <b:constructor-arg type="...dp.web.security.TokenAuthenticationManager" ref="tokenAuthenticationManager"></b:constructor-arg>
    <b:constructor-arg type="...dp.web.security.RestAuthenticationEntryPoint" ref="restAuthenticationEntryPoint"></b:constructor-arg>
</b:bean>

<b:bean id="accessDeniedHandler" class="...dp.web.security.RestAccessDeniedHandler">
</b:bean>

<http realm="Protected REST API" pattern="/rest/**" use-expressions="true" auto-config="false" create-session="stateless"  entry-point-ref="restAuthenticationEntryPoint">
    <custom-filter ref="AuthenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER" />
    <access-denied-handler ref="accessDeniedHandler"/>
</http> 

如何发送带有错误代码和消息的干净响应?

【问题讨论】:

【参考方案1】:

您可以在 web.xml 中使用错误页面来拦截 Tomcat 的错误页面。例如,

<error-page>
    <error-code>404</error-code>
    <location>/404</location>
</error-page>

现在您使用 RequestMapping 将 /404 映射到一个页面,该页面返回您的 JSON 响应而没有任何 HTML:

@RequestMapping(value = "/404", method = RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE)
@ResponseBody
public ResponseEntity<ResponseStatus> handle404() 

    HttpStatus status = null;
    ResponseStatus responseStatus = new ResponseStatus("404", "Wrong path to resource.");
    status = HttpStatus.NOT_FOUND;

    ResponseEntity<ResponseStatus> response = new ResponseEntity<ResponseStatus>(responseStatus, status);

    return response;

这将简单地返回一个名为 Response Status 的 JSON 对象,其中包含错误代码和错误消息作为字段。

【讨论】:

我没有提到我的应用中有两个http.一种用于 dp/rest,无需 Web 浏览器即可访问,而 /dp 则具有可通过 Web 浏览器访问的视图(jsp 页面)。这只能通过 web.xml 实现吗?因为在 web.xml 我已经有 404.. /errors/notfound.jsp.. 是否可以分离错误? 如果你知道异常是什么,你可以在 Spring 中定义 ExceptionHandlers 在抛出异常时做任何你想做的事情。这听起来像你在找什么。如果您需要更多信息,请告诉我......但这里是一个开始寻找的好地方:spring.io/blog/2013/11/01/exception-handling-in-spring-mvc

以上是关于如何在自定义 Spring security 3.0 身份验证中正确处理异常?的主要内容,如果未能解决你的问题,请参考以下文章

Grails spring security如何在自定义身份验证中记住我?

Spring Security getAuthenticationManager() 在自定义过滤器中返回 null

Spring Security 多登录实现

Spring Security 3-如何自定义用户名/密码参数?

如何使用 grails 1.3.2 和插件 spring-security-core 1 实现自定义 FilterSecurityInterceptor?

如何在自定义 Spring 存储库中实现自定义方法? [复制]