java 针对 Spring Security Rest API 从 Android 设备进行身份验证

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27755249/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 12:20:15  来源:igfitidea点击:

Authentification from Android Device against Spring Security Rest API

javaandroidspringrestspring-mvc

提问by siser

i have an SPRING Backend with an REST-API. It is secured by username and password. When i open it with my pc browser at first the login screen is shown and after i added the credentials i can access the api fluendly.

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

When i try the same through an Android APP, i get every time referred to the login screen. To auth on the Android Side i use an REST API Request which is by default accessable. Is the internally Android App Browser not compatible to store session cookies? Everytime a new HTTP session gets created. Im using Volley for the requests

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

spring-security.xml

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>

the code in the controller from the rest api

来自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 Security DEBUG

春季安全调试

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

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.

volley

凌空抽射

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

    mQueue = Volley.newRequestQueue(context);

采纳答案by rhinds

I suspect the problem may be that you are implementing your own custom authentication controller endpoint and not using the full spring-security chain.

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

The normal filter chain will still get invoked on all requests as per your xml config, but by just calling authenticate() method in a controller will just do that, and not the rest of the authentication sucess handler stuff e.g. you won't actually be setting a cookie on the response with your adhoc controller authentication)

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

The easiest way to test this is just curl the url directly, or use something like postman (chrome plugin for rest apis) to test the api authentication endpoit and see if any cookeis are being set on the response.

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

If you have control over the serverside code (e.g. you can change it and aren't just working on the android app), here are a few thoughts:

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

  • I would avoid custom authentication endpoints and trying to hand-roll the security stuff - spring security is really good at that stuff.

  • Assuming you don't want to go with oauth and the complexity of that solutions for the API, then take a look at Google's recommended approach for securing APIs for mobile apps. It's similar to approach you have taken, but for the login you just embed a web view in the android app and use it to let the user login directly on the standard spring security form (so get to take advantage of Spring-security default behaviour e.g. cookies etc), then in the app you just grab a user token from the web response and then store that - which can then be used in all API requests as a request header (so user doesnt have to keep logging in on the mobile app)

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

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

I wrote up an overview of the approach here:

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

Securing your API for mobile access

保护您的 API 以进行移动访问

and have put together a spring security implementation here as well.

并在此处整合了一个 spring 安全实现。

Securing your mobile API with Spring Security

使用 Spring Security 保护您的移动 API

回答by siser

Tried your suggestion and got it working.

尝试了您的建议并使其正常工作。

After i had checked, that the backend transport the needed cookie, i adjust volley so that it stores the transmitted cookie and retransmit it on every request after that as a token. The cookie get stored in the preferences.

在我检查过后端传输了所需的 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);
               .....
    }

as stated by vmirinov on go herewith slidly modification

正如 vmirinov 所说的,在这里进行滑动修改

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 "";
    }