Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Securing REST API using custom tokens (stateless, no UI, no cookies, no basic authentication, no OAuth, no login page)

There are lots of guidelines, sample codes that show how to secure REST API with Spring Security, but most of them assume a web client and talk about login page, redirection, using cookie, etc. May be even a simple filter that checks for the custom token in HTTP header might be enough. How do I implement security for below requirements? Is there any gist/github project doing the same? My knowledge in spring security is limited, so if there is a simpler way to implement this with spring security, please let me know.

  • REST API served by stateless backend over HTTPS
  • client could be web app, mobile app, any SPA style app, third-party APIs
  • no Basic Auth, no cookies, no UI (no JSP/HTML/static-resources), no redirections, no OAuth provider.
  • custom token set on HTTPS headers
  • The token validation done against external store (like MemCached/Redis/ or even any RDBMS)
  • All APIs need to be authenticated except for selected paths (like /login, /signup, /public, etc..)

I use Springboot, spring security, etc.. prefer a solution with Java config (no XML)

like image 696
Karthik Karuppannan Avatar asked Aug 14 '14 20:08

Karthik Karuppannan


People also ask

Which is the most secure way of authenticating an API?

Every web API should use TLS (Transport Layer Security). TLS protects the information your API sends (and the information that users send to your API) by encrypting your messages while they're in transit. You might know TLS by its predecessor's name, SSL.

Is REST API secure?

REST APIs use HTTP and support Transport Layer Security (TLS) encryption. TLS is a standard that keeps an internet connection private and checks that the data sent between two systems (a server and a server, or a server and a client) is encrypted and unmodified.

Can OAuth be used for REST API?

OAuth is an authorization framework that enables an application or service to obtain limited access to a protected HTTP resource. To use REST APIs with OAuth in Oracle Integration, you need to register your Oracle Integration instance as a trusted application in Oracle Identity Cloud Service.

What is token based authentication in REST API?

Users of the REST API can authenticate by providing a user ID and password to the REST API login resource with the HTTP POST method. An LTPA token is generated that enables the user to authenticate future requests. This LTPA token has the prefix LtpaToken2 .


2 Answers

My sample app does exactly this - securing REST endpoints using Spring Security in a stateless scenario. Individual REST calls are authenticated using an HTTP header. Authentication information is stored on the server side in an in-memory cache and provides the same semantics as those offered by the HTTP session in a typical web application. The app uses the full Spring Security infrastructure with very minimum custom code. No bare filters, no code outside of the Spring Security infrastructure.

The basic idea is to implement the following four Spring Security components:

  1. org.springframework.security.web.AuthenticationEntryPoint to trap REST calls requiring authentication but missing the required authentication token and thereby deny the requests.
  2. org.springframework.security.core.Authentication to hold the authentication information required for the REST API.
  3. org.springframework.security.authentication.AuthenticationProvider to perform the actual authentication (against a database, an LDAP server, a web service, etc.).
  4. org.springframework.security.web.context.SecurityContextRepository to hold the authentication token in between HTTP requests. In the sample, the implementation saves the token in an EHCACHE instance.

The sample uses XML configuration but you can easily come up with the equivalent Java config.

like image 72
manish Avatar answered Oct 06 '22 03:10

manish


You're right, it isn't easy and there aren't many good examples out there. Examples i saw made it so you couldn't use other spring security stuff side by side. I did something similar recently, here's what i did.

You need a custom token to hold your header value

public class CustomToken extends AbstractAuthenticationToken {   private final String value;    //Getters and Constructor.  Make sure getAutheticated returns false at first.   //I made mine "immutable" via:        @Override public void setAuthenticated(boolean isAuthenticated) {     //It doesn't make sense to let just anyone set this token to authenticated, so we block it     //Similar precautions are taken in other spring framework tokens, EG: UsernamePasswordAuthenticationToken     if (isAuthenticated) {          throw new IllegalArgumentException(MESSAGE_CANNOT_SET_AUTHENTICATED);     }      super.setAuthenticated(false); } } 

You need a spring security filter to extract the header and ask the manager to authenticate it, something like thisemphasized text

public class CustomFilter extends AbstractAuthenticationProcessingFilter {       public CustomFilter(RequestMatcher requestMatcher) {         super(requestMatcher);          this.setAuthenticationSuccessHandler((request, response, authentication) -> {         /*          * On success the desired action is to chain through the remaining filters.          * Chaining is not possible through the success handlers, because the chain is not accessible in this method.          * As such, this success handler implementation does nothing, and chaining is accomplished by overriding the successfulAuthentication method as per:          * http://docs.spring.io/autorepo/docs/spring-security/3.2.4.RELEASE/apidocs/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html#successfulAuthentication(javax.servlet.http.HttpServletRequest,%20javax.servlet.http.HttpServletResponse,%20javax.servlet.FilterChain,%20org.springframework.security.core.Authentication)          * "Subclasses can override this method to continue the FilterChain after successful authentication."          */         });      }        @Override     public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)             throws AuthenticationException, IOException, ServletException {           String tokenValue = request.getHeader("SOMEHEADER");          if(StringUtils.isEmpty(tokenValue)) {             //Doing this check is kinda dumb because we check for it up above in doFilter             //..but this is a public method and we can't do much if we don't have the header             //also we can't do the check only here because we don't have the chain available            return null;         }           CustomToken token = new CustomToken(tokenValue);         token.setDetails(authenticationDetailsSource.buildDetails(request));          return this.getAuthenticationManager().authenticate(token);     }        /*      * Overriding this method to maintain the chaining on authentication success.      * http://docs.spring.io/autorepo/docs/spring-security/3.2.4.RELEASE/apidocs/org/springframework/security/web/authentication/AbstractAuthenticationProcessingFilter.html#successfulAuthentication(javax.servlet.http.HttpServletRequest,%20javax.servlet.http.HttpServletResponse,%20javax.servlet.FilterChain,%20org.springframework.security.core.Authentication)      * "Subclasses can override this method to continue the FilterChain after successful authentication."      */     @Override     protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {           //if this isn't called, then no auth is set in the security context holder         //and subsequent security filters can still execute.           //so in SOME cases you might want to conditionally call this         super.successfulAuthentication(request, response, chain, authResult);          //Continue the chain         chain.doFilter(request, response);      }   } 

Register your custom filter in spring security chain

 @Configuration  public static class ResourceEndpointsSecurityConfig extends WebSecurityConfigurerAdapter {                //Note, we don't register this as a bean as we don't want it to be added to the main Filter chain, just the spring security filter chain       protected AbstractAuthenticationProcessingFilter createCustomFilter() throws Exception {         CustomFilter filter = new CustomFilter( new RegexRequestMatcher("^/.*", null));         filter.setAuthenticationManager(this.authenticationManagerBean());         return filter;       }         @Override        protected void configure(HttpSecurity http) throws Exception {                                http             //fyi: This adds it to the spring security proxy filter chain             .addFilterBefore(createCustomFilter(), AnonymousAuthenticationFilter.class)        } } 

A custom auth provider to validate that token extracted with the filter.

public class CustomAuthenticationProvider implements AuthenticationProvider {       @Override     public Authentication authenticate(Authentication auth)             throws AuthenticationException {          CustomToken token = (CustomToken)auth;          try{            //Authenticate token against redis or whatever you want              //This i found weird, you need a Principal in your Token...I use User             //I found this to be very redundant in spring security, but Controller param resolving will break if you don't do this...anoying             org.springframework.security.core.userdetails.User principal = new User(...);               //Our token resolved to a username so i went with this token...you could make your CustomToken take the principal.  getCredentials returns "NO_PASSWORD"..it gets cleared out anyways.  also the getAuthenticated for the thing you return should return true now             return new UsernamePasswordAuthenticationToken(principal, auth.getCredentials(), principal.getAuthorities());         } catch(Expection e){             //TODO throw appropriate AuthenticationException types             throw new BadCredentialsException(MESSAGE_AUTHENTICATION_FAILURE, e);         }       }      @Override     public boolean supports(Class<?> authentication) {         return CustomToken.class.isAssignableFrom(authentication);     }   } 

Finally, register your provider as a bean so the authentication manager finds it in some @Configuration class. You probably could just @Component it too, i prefer this method

@Bean public AuthenticationProvider createCustomAuthenticationProvider(injectedDependencies)  {     return new CustomAuthenticationProvider(injectedDependencies); } 
like image 32
Chris DaMour Avatar answered Oct 06 '22 01:10

Chris DaMour