如何在自定义 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 我已经有以上是关于如何在自定义 Spring security 3.0 身份验证中正确处理异常?的主要内容,如果未能解决你的问题,请参考以下文章
Grails spring security如何在自定义身份验证中记住我?
Spring Security getAuthenticationManager() 在自定义过滤器中返回 null
Spring Security 3-如何自定义用户名/密码参数?
如何使用 grails 1.3.2 和插件 spring-security-core 1 实现自定义 FilterSecurityInterceptor?