[9] SecurityContextHolderAwareRequestFilter

SecurityContextHolderAwareRequestFilter

介紹

Spring Security TokenEndpoint中獲取token的請求,有這樣一個參數:Principal。 對於一個普通HttpServletRequest,是沒有Principal參數類型的。SecurityContextHolderAwareRequestFilter通過HttpServletRequestFactory將HttpServletRequest請求包裝成SecurityContextHolderAwareRequestWrapper,它實現了HttpServletRequest,並進行了擴展,添加一些額外的方法,比如:getPrincipal()方法等。這樣就可以那些需要Principal等參數的Controller就可以接收到對應參數了。除了這個地方的應用,在其他地方,也可以直接調用request#getUserPrincipal()獲取對應信息。

代碼分析

步驟1

SecurityContextHolderAwareRequestFilter#doFilter()方法很簡單,主要操作都在requestFactory.create()方法之中。SecurityContextHolderAwareRequestFilter初始化後,通過Bean後置處理器調用updateFactory()方法,該方法以"ROLE_"爲參數創建了一個HttpServlet3RequestFactory並設置爲過濾器的HttpServletRequestFactory。

private String rolePrefix = "ROLE_";
private HttpServletRequestFactory requestFactory;

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
        throws IOException, ServletException {
    chain.doFilter(this.requestFactory.create((HttpServletRequest) req,
            (HttpServletResponse) res), res);
}

@Override
public void afterPropertiesSet() throws ServletException {
    super.afterPropertiesSet();
    updateFactory();
}

private void updateFactory() {
    String rolePrefix = this.rolePrefix;
    this.requestFactory = createServlet3Factory(rolePrefix);
}

private HttpServletRequestFactory createServlet3Factory(String rolePrefix) {
    HttpServlet3RequestFactory factory = new HttpServlet3RequestFactory(rolePrefix);
    factory.setTrustResolver(this.trustResolver);
    factory.setAuthenticationEntryPoint(this.authenticationEntryPoint);
    factory.setAuthenticationManager(this.authenticationManager);
    factory.setLogoutHandlers(this.logoutHandlers);
    return factory;
}

步驟2

當請求經過過濾器時,requestFactory#create()會把請求進行包裝成Servlet3SecurityContextHolderAwareRequestWrapper,它繼承自SecurityContextHolderAwareRequestWrapper,用戶getUserPrincipal()、getRemoteUser()方法,這2個方法都是從上下文中獲取對應的信息,SpringMvc的ServletRequestMethodArgumentResolver方法參數解析中也有用到getUserPrincipal()。當參數類型是Principal時,就會調用request#getUserPrincipal(),這樣就可以填充到TokenEndpoint的對應方法裏了,代碼如下:

@Override
public HttpServletRequest create(HttpServletRequest request,
        HttpServletResponse response) {
    return new Servlet3SecurityContextHolderAwareRequestWrapper(request,
            this.rolePrefix, response);
}
private Authentication getAuthentication() {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();

    if (!trustResolver.isAnonymous(auth)) {
        return auth;
    }

    return null;
}

@Override
public String getRemoteUser() {
    Authentication auth = getAuthentication();

    if ((auth == null) || (auth.getPrincipal() == null)) {
        return null;
    }

    if (auth.getPrincipal() instanceof UserDetails) {
        return ((UserDetails) auth.getPrincipal()).getUsername();
    }

    return auth.getPrincipal().toString();
}

@Override
public Principal getUserPrincipal() {
    Authentication auth = getAuthentication();

    if ((auth == null) || (auth.getPrincipal() == null)) {
        return null;
    }

    return auth;
}
else if (Principal.class.isAssignableFrom(paramType)) {
    Principal userPrincipal = request.getUserPrincipal();
    if (userPrincipal != null && !paramType.isInstance(userPrincipal)) {
        throw new IllegalStateException(
                "Current user principal is not of type [" + paramType.getName() + "]: " + userPrincipal);
    }
    return userPrincipal;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章