在我的应用中,我有两个不同的身份验证网关,分别可以很好地工作:
现在我想实现两步身份验证:1)OAuth然后基于表单。
复杂性在于,我不想在步骤1(OAuth)之后丢失存储在Principal中的特定于上下文的信息;我只想在基于表单的身份验证完成后向安全上下文添加一些新的特定于用户的信息,并在同一身份验证会话中添加新角色ROLE_USER。
能否顺利实施?如何在第二步(基于表单的身份验证)中提取现有的主体信息并将其添加到新的主体中?
有没有重新发明轮子的任何“模板解决方案”?
我当前的直接解决方案是:
但是我不喜欢它,似乎很la脚,因为我必须手动处理第二个安全请求。
如何才能以Spring-ish方式正确实现此目的?谢谢。
PS 。出于遗留原因,我必须使用Oauth 1.0,无法将其升级到v.2或任何其他解决方案。
答案 0 :(得分:0)
好的,这就是我设法完成这项任务的方式。
public class OAuthAwareAccessDeniedHandler implements AccessDeniedHandler {
private static final Log LOG = LogFactory.getLog(OAuthAwareAccessDeniedHandler.class);
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (oauthSecurityUtils.isUserWithOnlyOAuthRole(auth)) {
LOG.debug("Prohibited to authorize OAuth user trying to access protected resource.., redirected to /login");
// Remember the request pathway
RequestCache requestCache = new HttpSessionRequestCache();
requestCache.saveRequest(request, response);
response.sendRedirect(request.getContextPath() + "/login");
return;
}
LOG.debug("Ordinary redirection to /accessDenied URL..");
response.sendRedirect(request.getContextPath() + "/accessDenied");
}
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// all the config
.and()
.exceptionHandling().accessDeniedHandler(oauthAwareAccessDeniedHandler());
}
public class OAuthAwareUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private static final Log LOG = LogFactory.getLog(LTIAwareUsernamePasswordAuthenticationFilter.class);
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
Authentication previousAuth = SecurityContextHolder.getContext().getAuthentication();
// Check for OAuth authentication in place
if (oauthSecurityUtils.isUserWithOnlyOAuthRole(previousAuth)) {
LOG.debug("OAuth authentication exists, try to authenticate with UsernamePasswordAuthenticationFilter in the usual way");
SecurityContextHolder.clearContext();
Authentication authentication = null;
try {// Attempt to authenticate with standard UsernamePasswordAuthenticationFilter
authentication = super.attemptAuthentication(request, response);
} catch (AuthenticationException e) {
// If fails by throwing an exception, catch it in unsuccessfulAuthentication() method
LOG.debug("Failed to upgrade authentication with UsernamePasswordAuthenticationFilter");
SecurityContextHolder.getContext().setAuthentication(previousAuth);
throw e;
}
LOG.debug("Obtained a valid authentication with UsernamePasswordAuthenticationFilter");
Principal newPrincipal = authentication.getPrincipal();
// Here extract all needed information about roles and domain-specific info
Principal rememberedPrincipal = previousAuth.getPrincipal();
// Then enrich this remembered principal with the new information and return it
LOG.debug("Created an updated authentication for user");
return newAuth;
}
LOG.debug("No OAuth authentication exists, try to authenticate with UsernamePasswordAuthenticationFilter in the usual way");
return super.attemptAuthentication(request, response);
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
Authentication previousAuth = SecurityContextHolder.getContext().getAuthentication();
if (oauthSecurityUtils.isUserWithOnlyOAuthRole(previousAuth)) {
LOG.debug("unsuccessfulAuthentication upgrade for OAuth user, previous authentication :: "+ previousAuth);
super.unsuccessfulAuthentication(request, response, failed);
LOG.debug("fallback to previous authentication");
SecurityContextHolder.getContext().setAuthentication(previousAuth);
} else {
LOG.debug("unsuccessfulAuthentication for a non-OAuth user with UsernamePasswordAuthenticationFilter");
super.unsuccessfulAuthentication(request, response, failed);
}
}
}
剩下的唯一事情是在UsernamePasswordAuthenticationFilter之前添加此过滤器,并将其仅应用于给定的端点:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.addFilterBefore(oauthAwareUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
// here come ant rules
.and()
.formLogin()
.and()
.exceptionHandling().accessDeniedHandler(oauthAwareAccessDeniedHandler());
}
就是这样。该示例经过测试是可行的。不确定之后可能会发现一些副作用。另外,我敢肯定,可以通过更精细的方式来完成此操作,但现在我将继续使用此代码。