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());
}
而已。這個例子被測試是可行的。以后可能會發現一些副作用,不確定。此外,我確信它可以以更精致的方式完成,但我現在將使用此代碼。
添加回答
舉報