1 回答
TA贡献1829条经验 获得超7个赞
好的,这就是我设法完成这项任务的方法。
我有一个经过身份验证的用户(实际上是服务),角色为 ROLE_OAUTH 并打开了身份验证会话,以及有关会话中保留的上下文的一些关键信息,这些信息硬连接到 OAuth 请求中;
现在,当尝试访问需要另一个角色(例如 ROLE_USER)的受保护资源时,Spring 给了我AccessDeniedException并发送 403 禁止响应(请参阅AccessDeniedHandlerImpl),如果需要,请在自定义 AccessDeniedHandler 中覆盖默认行为。这是代码示例:
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());
}
在此步骤之后,默认UsernamePasswordAuthenticationFilter将通过使用输入的凭据创建另一个 Authentication 对象来处理输入,默认行为只是丢失连接到先前 OAuth Authentication 对象的现有信息。所以我们需要通过扩展这个类来覆盖这个默认行为,就像这样,在标准的 UsernamePasswordAuthenticationFilter 之前添加这个过滤器。
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());
}
而已。这个例子被测试是可行的。以后可能会发现一些副作用,不确定。此外,我确信它可以以更精致的方式完成,但我现在将使用此代码。
添加回答
举报