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.
I use Springboot, spring security, etc.. prefer a solution with Java config (no XML)
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.
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.
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.
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 .
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:
org.springframework.security.web.AuthenticationEntryPoint
to trap REST calls requiring authentication but missing the required authentication token and thereby deny the requests.org.springframework.security.core.Authentication
to hold the authentication information required for the REST API.org.springframework.security.authentication.AuthenticationProvider
to perform the actual authentication (against a database, an LDAP server, a web service, etc.).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.
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); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With