【问题标题】:Authentification from Android Device against Spring Security Rest APIAndroid 设备针对 Spring Security Rest API 的身份验证
【发布时间】:2015-01-03 13:14:50
【问题描述】:

我有一个带有 REST-API 的 SPRING 后端。它由用户名和密码保护。当我首先用我的电脑浏览器打开它时,会显示登录屏幕,在我添加凭据后,我可以流畅地访问 api。

当我通过 Android APP 尝试相同的操作时,我每次都会看到登录屏幕。要在 Android 端进行身份验证,我使用默认可访问的 REST API 请求。 内部 Android 应用浏览器是否不兼容存储会话 cookie?每次创建新的 HTTP 会话时。我使用 Volley 来处理请求

spring-security.xml

    <http auto-config="true" use-expressions="true">
    <intercept-url pattern="/api/user/login" access="permitAll" /> <!--IS_AUTHENTICATED_ANONYMOUSLY-->
    <intercept-url pattern="/admin/**" access="hasAnyRole('ROLE_ADMIN','ROLE_GROUP_LEADER')" />
    <intercept-url pattern="/api/**" access="hasAnyRole('ROLE_USER','ROLE_ADMIN','ROLE_GROUP_LEADER')" />

    <form-login 
        login-page="/login" 
        default-target-url="/admin" 
        authentication-failure-url="/login?error" 
        username-parameter="username"
        password-parameter="password"    
    />
    <access-denied-handler error-page="/403" />
    <logout logout-success-url="/login?logout" />
</http>


<authentication-manager alias="authManager">
   <authentication-provider >

        <password-encoder ref="encoder" />

        <jdbc-user-service data-source-ref="dataSource"
      users-by-username-query=
        "select username,password, enabled from user where username=?"
      authorities-by-username-query=
        "select username, name as role from role r,user u where u.role_id = r.id and username =? " />
    </authentication-provider>
</authentication-manager>

来自rest api的控制器中的代码

    UserDetails userDetails = userDetailsSvc.loadUserByUsername(user.getUsername());

    UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails, "123",userDetails.getAuthorities());

    Authentication authentication = authManager.authenticate(token);

    log.debug("Logging in with [{}]", authentication.getPrincipal());
    SecurityContext securityContext = SecurityContextHolder.getContext();
    securityContext.setAuthentication(authentication);
    HttpSession session = request.getSession(true);
    session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);

Spring 安全调试

************************************************************

Request received for POST '/api/user/create':

org.apache.catalina.connector.RequestFacade@36dc8ced

servletPath:/api/user/create
pathInfo:null
headers: 
if-modified-since: Sat, 03 Jan 2015 12:35:50 GMT+00:00
content-type: application/json; charset=utf-8
user-agent: Dalvik/1.6.0 (Linux; U; Android 4.0.4; GT-P7100 Build/IMM76D)
host: 192.168.178.36:8088
connection: Keep-Alive
accept-encoding: gzip
content-length: 124


Security filter chain: [
  SecurityContextPersistenceFilter
  WebAsyncManagerIntegrationFilter
  LogoutFilter
  UsernamePasswordAuthenticationFilter
  BasicAuthenticationFilter
  RequestCacheAwareFilter
  SecurityContextHolderAwareRequestFilter
  AnonymousAuthenticationFilter
  SessionManagementFilter
  ExceptionTranslationFilter
  FilterSecurityInterceptor
]


************************************************************


2015-01-03 13:53:46 INFO  Spring Security Debugger:39 - 

************************************************************

New HTTP session created: A430DD754F7F6E466D07B10D1DDCCEF7

Call stack: 

    at org.springframework.security.web.debug.Logger.info(Logger.java:29)
    at org.springframework.security.web.debug.DebugRequestWrapper.getSession(DebugFilter.java:144)
    at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:238)
    at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:238)
    at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:238)
    at org.springframework.security.web.savedrequest.HttpSessionRequestCache.saveRequest(HttpSessionRequestCache.java:40)
    at org.springframework.security.web.access.ExceptionTranslationFilter.sendStartAuthentication(ExceptionTranslationFilter.java:184)
    at org.springframework.security.web.access.ExceptionTranslationFilter.handleSpringSecurityException(ExceptionTranslationFilter.java:168)
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:131)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:103)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:113)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:154)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:45)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilter(BasicAuthenticationFilter.java:150)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:199)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
    at org.springframework.security.web.debug.DebugFilter.invokeWithWrappedRequest(DebugFilter.java:70)
    at org.springframework.security.web.debug.DebugFilter.doFilter(DebugFilter.java:59)
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344)
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:503)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:136)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:74)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:516)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1015)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:652)
    at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:222)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1575)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1533)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:722)


************************************************************


2015-01-03 13:53:46 INFO  Spring Security Debugger:39 - 

************************************************************

Request received for GET '/login':

org.apache.catalina.connector.RequestFacade@36dc8ced

servletPath:/login
pathInfo:null
headers: 
if-modified-since: Sat, 03 Jan 2015 12:35:50 GMT+00:00
content-type: application/json; charset=utf-8
user-agent: Dalvik/1.6.0 (Linux; U; Android 4.0.4; GT-P7100 Build/IMM76D)
host: 192.168.178.36:8088
connection: Keep-Alive
accept-encoding: gzip


Security filter chain: [
  SecurityContextPersistenceFilter
  WebAsyncManagerIntegrationFilter
  LogoutFilter
  UsernamePasswordAuthenticationFilter
  BasicAuthenticationFilter
  RequestCacheAwareFilter
  SecurityContextHolderAwareRequestFilter
  AnonymousAuthenticationFilter
  SessionManagementFilter
  ExceptionTranslationFilter
  FilterSecurityInterceptor
]


************************************************************


MonitorFilter::WARNING: the monitor filter must be the first filter in the chain.

凌空

    CookieManager manager = new CookieManager();
    CookieHandler.setDefault( manager  );

    mQueue = Volley.newRequestQueue(context);

【问题讨论】:

    标签: java android spring rest spring-mvc


    【解决方案1】:

    我怀疑问题可能是您正在实现自己的自定义身份验证控制器端点,而不是使用完整的 spring-security 链。

    正常的过滤器链仍然会根据您的 xml 配置在所有请求上被调用,但只需在控制器中调用 authenticate() 方法就可以做到这一点,而不是其余的身份验证成功处理程序的东西,例如您实际上不会使用临时控制器身份验证在响应上设置 cookie)

    最简单的测试方法是直接 curl url,或者使用 postman(rest apis 的 chrome 插件)之类的东西来测试 api 身份验证端点,看看是否在响应中设置了任何 cookie。

    如果您可以控制服务器端代码(例如,您可以更改它而不只是在 Android 应用上工作),这里有一些想法:

    • 我会避免自定义身份验证端点并尝试手动滚动安全性内容 - spring security 非常擅长这些内容。

    • 假设您不想使用 oauth 以及该 API 解决方案的复杂性,请查看 Google's recommended approach for securing APIs for mobile apps。它与您采用的方法类似,但是对于登录,您只需在 android 应用程序中嵌入一个 Web 视图并使用它让用户直接在标准 Spring Security 表单上登录(因此可以利用 Spring-security 默认行为,例如cookie 等),然后在应用程序中,您只需从 Web 响应中获取用户令牌,然后将其存储 - 然后可在所有 API 请求中用作请求标头(因此用户不必继续登录移动应用程序)

    我在这里写了一个方法的概述:

    Securing your API for mobile access

    并且还在这里整合了一个 Spring 安全实现。

    Securing your mobile API with Spring Security

    【讨论】:

    • 我试过邮递员插件。登录可以使用我编写的代码流畅地在 chrome 上运行。所以安全链似乎是正确完成的。即使我跳过 .authenticate 部分,它似乎也可以在 pc 端工作。我会尝试你的其他建议。 cookie 被发回
    • API 登录在 chrome 上有效吗? (例如,注销/清除浏览器 cookie,然后向其余 API 发出请求,将您需要的任何凭据传递给该 api 请求?)您如何将登录凭据传递给 API 身份验证?您是否使用用户名/密码向 API 发布表单?
    • 我听从了你的建议,用 postmann 试了一下。我注销/清除浏览器缓存,发出发布请求并得到带有 cookie 的响应。之后我可以使用 api 和管理后端。它是一个带有用户名和密码的 post 请求,这些请求被发送到 api 并由上面的代码验证。在密码为 123 的示例中(内部编码)。 json 看起来像 {"username":"...","password":"..."} 并返回一个类似 "successfull" 的字符串和一个类似 A13B1D3300B6B7D48ACC4626966872AE 的 JSESSIONID 作为 cookie
    • 有趣 - 我仍然会避免在控制器中使用定制的身份验证方法,但如果该控制器仍在浏览器中正确提供 cookie,那么问题一定出在 android 应用程序中。您是否看过有关 HttpStack 的这个问题 - 是否都设置正确? stackoverflow.com/questions/16680701/…
    【解决方案2】:

    尝试了您的建议并成功了。

    在我检查了后端是否传输了所需的 cookie 后,我调整了 volley 以便它存储传输的 cookie 并在之后的每个请求中将其作为令牌重新传输。 cookie 存储在首选项中。

    public GsonRequest(...)
      .....
    
    if(PreferencesManager.getInstance().getSessionCookie()!=null)
           this.headers.put("Cookie", "JSESSIONID="+ PreferencesManager.getInstance().getSessionCookie());
    
    @Override
        protected Response<T> parseNetworkResponse(NetworkResponse response) {
                String cookie = MyApp.get().checkSessionCookie(response.headers);
                PreferencesManager.getInstance().setSessionCookie(cookie);
                   .....
        }
    

    正如 vmirinov 在go here 上所说的,经过巧妙修改

    public final String checkSessionCookie(Map<String, String> headers) {
            if (headers.containsKey(SET_COOKIE_KEY)
                    && headers.get(SET_COOKIE_KEY).toLowerCase().contains(SESSION_COOKIE)) {
                String cookie = headers.get(SET_COOKIE_KEY);
                if (cookie.length() > 0) {
                    String[] splitCookie = cookie.split(";");
                    String[] splitSessionId = splitCookie[0].split("=");
                    cookie = splitSessionId[1];
                    SharedPreferences.Editor prefEditor = _preferences.edit();
                    prefEditor.putString(SESSION_COOKIE, cookie);
                    prefEditor.commit();
                    return cookie;
                }
            }
            return "";
        }
    

    【讨论】:

      猜你喜欢
      • 2013-12-05
      • 2019-02-07
      • 2014-03-18
      • 2016-03-20
      • 2022-01-25
      • 2016-04-30
      • 2017-01-14
      • 2016-01-17
      • 2018-01-01
      相关资源
      最近更新 更多