簡體   English   中英

使用Spring Security進行無需授權的預身份驗證

[英]Pre-Authentication without Authorization using Spring Security

我的要求是:

在我的應用程序中,當用戶通過登錄屏幕(而不是使用Spring Security)登錄時,首次通過自定義的第三方API執行身份驗證。 現在,我們幾乎沒有使用其他服務調用的增強功能。 根據要求,在從我們的應用程序進行任何休息調用之前,我們需要針對數據庫重新驗證用戶。 由於用戶在使用登錄屏幕登錄時已經過驗證,並且請求中提供了這些詳細信息,因此我計划使用spring security來重新驗證用戶(預身份驗證方案)。我們沒有為此定義任何角色用戶在我們的應用程序 所以不必擔心角色。 我已閱讀參考手冊,但我沒有得到更多關於如何進一步的信息。 我理解的一件事是,在通過定制的第三方API進行身份驗證后,我們需要以某種方式告訴用戶彈簧上下文。 即使我谷歌搜索了一下,但無法得到一個符合我要求的好例子。 如果有人可以指導我如何開始一個例子,那將是很棒的。

我只需要告訴spring上下文類似“嘿......!此用戶已經過身份驗證,因此在用戶的憑據經過自定義的第三方API驗證后,可以允許他調用其余服務。

我不應該更改現有的初始身份驗證過程。 我應該只使用經過身份驗證的用戶信息,並進一步使用spring security來重新驗證用戶。

我的問題是類似於春季參考手冊中提到的問題http://docs.spring.io/spring-security/site/docs/3.0.x/reference/preauth.html

請不要用單行回答(除非它有一個合適的外部鏈接)。如果你能告訴我一個例子或偽代碼,那就太好了。

提前致謝。

我建議從Spring Security到第三方登錄頁面建立一個“橋梁”。 如果您使用Spring,我認為這是最好的工作方式。

這意味着,您有一個登錄處理程序,可以將用戶重定向到第三方登錄頁面。 登錄后,用戶將被重定向回Web應用程序。

你是這個意思嗎? 聽起來不錯嗎? 是否有意義?

如果是這樣,您可以使用我的文章獲得一些幫助:

<security:http entry-point-ref="legacyEntryPoint">

通常,這意味着只要相關的http調用嘗試訪問您的應用程序,這就是處理請求的入口點。 在您的情況下,legacyEntryPoint是您將實現的類,它將檢查用戶是否經過身份驗證; 如果沒有,它會將用戶重定向到第三方登錄系統,否則它會使用已知的“令牌”來使用您的應用程序。

希望有所幫助!

你試過這個嗎?

SecurityContextHolder.getContext().setAuthenticated(true);

http://docs.spring.io/autorepo/docs/spring-security/3.0.x/apidocs/org/springframework/security/core/Authentication.html

對於第一次身份驗證,我希望您有權使用某些輸入值(如用戶名/密碼)調用第三方API,並返回true / false。 如果是這樣,您可以編寫自己的AuthenticationProvider,如下所示。 並調用您的第三方認證,如下所示。 Spring安全框架會自動設置SecurityContextHolder.getContext()。setAuthenticated(true或false); 因此。 你不必設置它。

        public class MyAuthenticationProvider implements AuthenticationProvider {

            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {

                String user = (String) authentication.getPrincipal();
                String password = (String) authentication.getCredentials();

                List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
                authorities.add(new SimpleGrantedAuthority("ROLE_ONE"));
                authorities.add(new SimpleGrantedAuthority("ROLE_TWO"));

                UsernamePasswordAuthenticationToken authenticationToken = null;



                if (<your 3rd party authentication result == true>)) {
                    authenticationToken = new UsernamePasswordAuthenticationToken(user, password, authorities);
                } else {
                    throw new BadCredentialsException("Invalid credentials supplied. Please try again.");
                }
                return authenticationToken;
            }


            @Override
            public boolean supports(Class<?> authentication) {
                boolean supports = authentication.isAssignableFrom(UsernamePasswordAuthenticationToken.class);
                return supports;
            }

        }

在進行進一步的REST API調用之前,在Spring控制器中,您可以使用代碼SecurityContextHolder.getContext()來檢查用戶是否有效.getAuthentication()。isAuthenticated();

您還可以使用下面的代碼獲取許多其他用戶信息。

                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                System.out.println("getAuthorities : " + authentication.getAuthorities());
                System.out.println("getName : " + authentication.getName());
                System.out.println("getCredentials : " + authentication.getCredentials());
                System.out.println("getDetails : " + authentication.getDetails());
                System.out.println("getPrincipal : " + authentication.getPrincipal());

                if (authentication.getPrincipal() instanceof User) {
                    User user = (User) authentication.getPrincipal();
                    System.out.println(user.getUsername());
                    System.out.println(user.getPassword());
                    System.out.println(user.getAuthorities());
                }

我正在做一些非常相似的事情。 我正在為無狀態REST后端進行身份驗證,因此我希望用戶進行一次身份驗證,然后對於每個后續請求,身份驗證必須是透明的。 我正在使用令牌。 登錄時,用戶提供的憑據用於驗證和生成令牌(盡管最終,我們希望使用外部服務來獲取令牌)。 令牌作為標頭返回。 然后angularjs前端在每個后續REST調用上發送令牌。 后端檢查令牌的有效性,如果它是好的,則標記'已驗證'為真。

這是我的security-context.xml:

<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:sec="http://www.springframework.org/schema/security"
xsi:schemaLocation="
  http://www.springframework.org/schema/security
  http://www.springframework.org/schema/security/spring-security-3.2.xsd
  http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

<http use-expressions="true" 
      entry-point-ref="restAuthenticationEntryPoint"
      create-session="stateless">
    <intercept-url pattern="/secured/extreme/**" access="hasRole('ROLE_SUPERVISOR')"/>
    <intercept-url pattern="/secured/**" access="isAuthenticated()" />
    <intercept-url pattern="/j_spring_security_check" requires-channel="https" access="permitAll"/>
    <intercept-url pattern="/logon.jsp" requires-channel="https" access="permitAll"/>
    <sec:custom-filter ref="authenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER" />
</http>

<beans:bean id="restAuthenticationEntryPoint" class="com.company.project.authentication.security.RestAuthenticationEntryPoint" />

<beans:bean id="authenticationTokenProcessingFilter" class="com.company.project.authentication.security.AuthenticationTokenProcessingFilter" >
    <beans:property name="authenticationManager" ref="authenticationManager" />
    <beans:property name="userDetailsServices"> 
                <beans:list>
                    <beans:ref bean="inMemoryUserDetailsService" />
                    <beans:ref bean="tmpUserDetailsService" />
                </beans:list>
    </beans:property>

</beans:bean>

<beans:bean id="tmpUserDetailsService" class="com.company.project.authentication.security.TokenUserDetailsServiceImpl" />

<user-service id="inMemoryUserDetailsService">
            <user name="temporary" password="temporary" authorities="ROLE_SUPERVISOR" />
            <user name="user" password="userPass" authorities="ROLE_USER" />
</user-service>

<authentication-manager alias="authenticationManager">
    <!-- Use some hard-coded values for development -->
    <authentication-provider user-service-ref="inMemoryUserDetailsService" />
    <authentication-provider ref='companyLdapProvider' />
</authentication-manager>

對於身份驗證過濾器,我將UsernamePasswordAuthenticationFilter子類化。 當它是登錄請求時,則會發生身份驗證提供程序的身份驗證,然后生成令牌。 如果從標頭中讀取令牌,則檢查令牌以進行身份​​驗證。 這是我的身份驗證過濾器(它仍然不是生產就緒的,但它可以幫助您了解您可以執行的操作):

public class AuthenticationTokenProcessingFilter extends UsernamePasswordAuthenticationFilter {
//~ Static fields/initializers =====================================================================================

private static final String HEADER_AUTH_TOKEN = "X-Auth-Token";    
private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationTokenProcessingFilter.class);

private List<UserDetailsService> userDetailsServices = new ArrayList<UserDetailsService>();
//~ Constructors ===================================================================================================

public AuthenticationTokenProcessingFilter() {
    super();
}

//~ Methods ========================================================================================================
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
                ServletException {
    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;

        String authToken = this.extractAuthTokenFromRequest(request);
        if (authToken == null) {
            super.doFilter(request,  res,  chain);
            return;
        }
        String userName = TokenUtils.getUserNameFromToken(authToken);

        if (userName != null) {

                UserDetails userDetails = loadUserByUsername(userName);

                if (TokenUtils.validateToken(authToken, userDetails)) {

                        UsernamePasswordAuthenticationToken authentication =
                                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                }
        }

        chain.doFilter(request, response);
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    if (!request.getMethod().equals("POST")) {
        throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
    }

    UsernamePasswordAuthenticationToken authRequest = authenticateWithForm(request, response);
    // Allow subclasses to set the "details" property
    setDetails(request, authRequest);

    Authentication authentication = this.getAuthenticationManager().authenticate(authRequest);

    if (authentication.isAuthenticated()) {
        try {
            String authToken = TokenUtils.createToken(obtainUsername(request), obtainPassword(request));
            LOGGER.info("Setting HTTP header {} = {}", HEADER_AUTH_TOKEN, authToken);
            response.addHeader(HEADER_AUTH_TOKEN, authToken);
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();                
            LOGGER.info("authorities = {}", authorities);
            // Now we should make an in-memory table of the token and userdetails for later use 
        } catch(Exception e) {
            LOGGER.warn("Error creating token for authentication. Authorization token head cannot be created.", e);
        }

    }

    return authentication;
}

protected UsernamePasswordAuthenticationToken authenticateWithForm(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
    String username = obtainUsername(request);
    String password = obtainPassword(request);

    if (username == null) {
        username = "";
    }

    if (password == null) {
        password = "";
    }

    username = username.trim();

    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

    return authRequest;
}

private String extractAuthTokenFromRequest(HttpServletRequest httpRequest) {
        /* Get token from header */
        String authToken = httpRequest.getHeader(HEADER_AUTH_TOKEN);

        /* If token not found get it from request parameter */
        if (authToken == null) {
                authToken = httpRequest.getParameter("token");
        }

        return authToken;
}

public List<UserDetailsService> getUserDetailsServices() {
    return userDetailsServices;
}

public void setUserDetailsService(UserDetailsService userDetailsService) {
    this.userDetailsServices.add(userDetailsService);
}

public void setUserDetailsServices(List<UserDetailsService> users) {
    if (users != null) {
        this.userDetailsServices.clear();
        this.userDetailsServices.addAll(users);
    }
}
private UserDetails loadUserByUsername(String username) {
    UserDetails user = null;
    List<Exception> exceptions = new ArrayList<Exception>();
    for (UserDetailsService service: userDetailsServices) {
        try {
            user = service.loadUserByUsername(username);
            break;
        } catch (Exception e) {
            LOGGER.warn("Could not load user by username {} with service {}", username, service.getClass().getName());
            LOGGER.info("Exception is: ",e);
            exceptions.add(e);
        }
    }
    if (user == null && !exceptions.isEmpty()) {
        throw new AuthenticationException(exceptions.get(0));
    }
    return user;
}
}

我仍然在努力改進UserDetailsS​​ervice。 通常,您可以使用身份驗證提供程序來獲取UserDetails,但由於我有一個無狀態應用程序,因此當我想驗證令牌時,我必須確定要使用哪個UserDetailsS​​ervice。 我正在使用自定義代碼執行此操作。

暫無
暫無

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

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