簡體   English   中英

如何在 Spring Security / SpringMVC 中手動設置經過身份驗證的用戶

[英]How to manually set an authenticated user in Spring Security / SpringMVC

新用戶提交“新帳戶”表單后,我想手動登錄該用戶,這樣他們就不必在后續頁面上登錄。

通過 spring 安全攔截器的正常表單登錄頁面工作正常。

在 new-account-form 控制器中,我正在創建一個 UsernamePasswordAuthenticationToken 並在 SecurityContext 中手動設置它:

SecurityContextHolder.getContext().setAuthentication(authentication);

在同一頁面上,我稍后檢查用戶是否登錄:

SecurityContextHolder.getContext().getAuthentication().getAuthorities();

這將返回我之前在身份驗證中設置的權限。 一切都很好。

但是當在我加載的下一頁上調用相同的代碼時,身份驗證令牌只是 UserAnonymous。

我不清楚為什么它沒有保留我在上一個請求中設置的身份驗證。 有什么想法嗎?

  • 是否與會話 ID 未正確設置有關?
  • 是否有可能以某種方式覆蓋我的身份驗證?
  • 也許我只需要另一個步驟來保存身份驗證?
  • 或者我需要做些什么來聲明整個會話而不是單個請求的身份驗證?

只是尋找一些可能幫助我了解這里發生的事情的想法。

我和你有一段時間有同樣的問題。 我記不起細節,但下面的代碼讓我覺得有用。 此代碼在Spring Webflow流中使用,因此使用RequestContext和ExternalContext類。 但與您最相關的部分是doAutoLogin方法。

public String registerUser(UserRegistrationFormBean userRegistrationFormBean,
                           RequestContext requestContext,
                           ExternalContext externalContext) {

    try {
        Locale userLocale = requestContext.getExternalContext().getLocale();
        this.userService.createNewUser(userRegistrationFormBean, userLocale, Constants.SYSTEM_USER_ID);
        String emailAddress = userRegistrationFormBean.getChooseEmailAddressFormBean().getEmailAddress();
        String password = userRegistrationFormBean.getChoosePasswordFormBean().getPassword();
        doAutoLogin(emailAddress, password, (HttpServletRequest) externalContext.getNativeRequest());
        return "success";

    } catch (EmailAddressNotUniqueException e) {
        MessageResolver messageResolvable 
                = new MessageBuilder().error()
                                      .source(UserRegistrationFormBean.PROPERTYNAME_EMAIL_ADDRESS)
                                      .code("userRegistration.emailAddress.not.unique")
                                      .build();
        requestContext.getMessageContext().addMessage(messageResolvable);
        return "error";
    }

}


private void doAutoLogin(String username, String password, HttpServletRequest request) {

    try {
        // Must be called from request filtered by Spring Security, otherwise SecurityContextHolder is not updated
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
        token.setDetails(new WebAuthenticationDetails(request));
        Authentication authentication = this.authenticationProvider.authenticate(token);
        logger.debug("Logging in with [{}]", authentication.getPrincipal());
        SecurityContextHolder.getContext().setAuthentication(authentication);
    } catch (Exception e) {
        SecurityContextHolder.getContext().setAuthentication(null);
        logger.error("Failure in autoLogin", e);
    }

}

我找不到任何其他完整的解決方案,所以我想我會發布我的。 這可能有點像黑客,但它解決了上述問題的問題:

public void login(HttpServletRequest request, String userName, String password)
{

    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userName, password);

    // Authenticate the user
    Authentication authentication = authenticationManager.authenticate(authRequest);
    SecurityContext securityContext = SecurityContextHolder.getContext();
    securityContext.setAuthentication(authentication);

    // Create a new session and add the security context.
    HttpSession session = request.getSession(true);
    session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
}

最終找出了問題的根源。

手動創建安全上下文時,不會創建任何會話對象。 只有當請求完成處理時,Spring Security機制才會意識到會話對象為空(當它在處理請求后嘗試將安全上下文存儲到會話中時)。

在請求結束時,Spring Security會創建一個新的會話對象和會話ID。 但是,這個新的會話ID永遠不會進入瀏覽器,因為它發生在請求結束后,在對瀏覽器做出響應之后。 當下一個請求包含先前的會話ID時,這會導致新的會話ID(以及包含我的手動登錄用戶的安全上下文)丟失。

打開調試日志記錄以更好地了解正在發生的事情。

您可以使用瀏覽器端調試器來查看是否正在設置會話cookie,以查看HTTP響應中返回的標頭。 (還有其他方法。)

一種可能性是SpringSecurity正在設置安全會話cookie,並且您請求的下一頁有一個“http”URL而不是“https”URL。 (瀏覽器不會為“http”URL發送安全cookie。)

Servlet 2.4中的新過濾功能基本上緩解了過濾器只能在應用服務器實際請求處理之前和之后的請求流中運行的限制。 相反,Servlet 2.4過濾器現在可以在每個調度點與請求調度程序進行交互。 這意味着當Web資源將請求轉發給另一個資源(例如,將請求轉發到同一應用程序中的JSP頁面的servlet)時,過濾器可以在目標資源處理請求之前運行。 它還意味着如果Web資源包含來自其他Web資源的輸出或函數(例如,包含多個其他JSP頁面的輸出的JSP頁面),Servlet 2.4過濾器可以在每個包含的資源之前和之后工作。

要打開您需要的功能:

web.xml中

<filter>   
    <filter-name>springSecurityFilterChain</filter-name>   
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 
</filter>  
<filter-mapping>   
    <filter-name>springSecurityFilterChain</filter-name>   
    <url-pattern>/<strike>*</strike></url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>

這個RegistrationController

return "forward:/login?j_username=" + registrationModel.getUserEmail()
        + "&j_password=" + registrationModel.getPassword();

我試圖測試一個extjs應用程序,並在成功設置了testingAuthenticationToken后突然停止工作,沒有明顯的原因。

我無法得到上述工作的答案所以我的解決方案是在測試環境中跳過這一點彈簧。 我在這樣的春天引入了一條縫:

public class SpringUserAccessor implements UserAccessor
{
    @Override
    public User getUser()
    {
        SecurityContext context = SecurityContextHolder.getContext();
        Authentication authentication = context.getAuthentication();
        return (User) authentication.getPrincipal();
    }
}

用戶是此處的自定義類型。

然后我將它包裝在一個類中,該類只有一個選項可以讓測試代碼切換出來。

public class CurrentUserAccessor
{
    private static UserAccessor _accessor;

    public CurrentUserAccessor()
    {
        _accessor = new SpringUserAccessor();
    }

    public User getUser()
    {
        return _accessor.getUser();
    }

    public static void UseTestingAccessor(User user)
    {
        _accessor = new TestUserAccessor(user);
    }
}

測試版本看起來像這樣:

public class TestUserAccessor implements UserAccessor
{
    private static User _user;

    public TestUserAccessor(User user)
    {
        _user = user;
    }

    @Override
    public User getUser()
    {
        return _user;
    }
}

在調用代碼中,我仍在使用從數據庫加載的適當用戶:

    User user = (User) _userService.loadUserByUsername(username);
    CurrentUserAccessor.UseTestingAccessor(user);

顯然,如果您確實需要使用安全性,但我正在運行測試部署的無安全設置,這將不合適。 我以為其他人可能會遇到類似的情況。 這是我之前用於模擬靜態依賴項的模式。 另一種選擇是你可以維護包裝類的靜態性,但我更喜歡這個,因為你必須將CurrentUserAccessor傳遞給需要它的類,因為代碼的依賴性更加明確。

UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(phone_number, password); 對我來說,這個語句會拋出 BadCredentialsException。 但憑據是正確的。 但仍然生成的查詢顯示 phone_number 與用戶表中的電子郵件 ID 列進行了比較。 怎么處理? 我如何編碼以便 phone_number 與表中的 phone_number 列進行比較?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM