TL;DR - How do I use a custom built JWT to protect my API WITHOUT using oauth2?
I've read this question but I'm still not sure how to untangle OAuth2 from JWT authentication in Spring Security 5. I have a strange case I'll try to explain
- my SPA will authenticate with Okta to get an access token
- my SPA will send make a GET to my boot API (that I control) at /token/exchange, which will return a custom built JWT. My /token/exchange route is projected via oauth2 using the okta spring boot starter. I have that part working.
- my SPA will send the custom JWT as an Authorization header on all subsequent requests to my API. I need to protect all routes starting with
/api
so that only requests with my custom token are accepted.
I'll spare the why unless someone really wants the details....but please trust me when I say I'm stuck between a rock and a hard place and this is how we've decided to solve the issue.
I understand I'll need two WebSecurityConfigurerAdapter
so I can protect my /token/exchange
route with Okta while protecting all my other routes with my custom JWT.
I'm confused about the security filter details. Should I override one? Which one? Should I be making a brand new filter? Where do I insert it? I've seen example online that use GenericFilter, OncePerRequestFilter, UsernameAndPasswordFilter, BasicAuthFilter. I'm a bit overwhelmed with options...
to me, +1's would indicate an opportunity to serve the community and showcases a need that the spring team could help fill.
------ the gory details ------
my SPA currently has a custom login page that submits the creds to my API. My API make a REST call to Ping Federate to get a JWT using the grant type password
. This JWT is returned to the SPA which the SPA uses for all subsequent requests.
I've been asked to convert the app to use the parent company's Okta instance. I don't control it and I can't get it to return me a toke that will exactly match the Ping Federate token my app is currently expecting.
my SPA is an angular 1.5 app. before it's manually bootstrapped, I plan to do the okta auth via redirect and exchange the Okta token for a custom token. Basically, instead of passing in my users creds, I can pass in my token as a sort of voucher...because I'll never see the users creds anymore....they will only be entered at Okta...which is sort of the whole point.
In my API, simply by using the subject of the okta token, I can lookup all the needed data and construct a token that looks identical in shape to the token provided by Ping Federate (which does not return a typical oauth2 token). By getting a token that matches what I am currently getting, most of the API logic can continue to work "as is".
-------- Failed attempts thus far
I see this in my logs
o.s.security.web.FilterChainProxy : /api/entities at position 6 of 12 in additional filter chain; firing Filter: 'BearerTokenAuthenticationFilter'
o.s.s.authentication.ProviderManager : Authentication attempt using org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider
.s.a.DefaultAuthenticationEventPublisher : No event was found for the exception org.springframework.security.oauth2.core.OAuth2AuthenticationException
.o.s.r.w.BearerTokenAuthenticationFilter : Authentication request for failed: org.springframework.security.oauth2.core.OAuth2AuthenticationException: An error occurred while attempting to decode the Jwt: Signed JWT rejected: Another algorithm expected, or no matching key(s) found
o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@3ec543f6
s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
this seems like spring security is trying to authenticate requests made to /api/entities
with my Okta oauth config, instead of with my "ApiConfig", which for now, I'm just making my entire /api/**
public
@Configuration
@Order(1)
public class OktaWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/token/exchange")
.authorizeRequests()
.antMatchers("/token/exchange").authenticated()
.and()
.oauth2ResourceServer().jwt();
http.cors();
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// https://github.com/okta/okta-spring-boot/blob/master/oauth2/src/main/java/com/okta/spring/boot/oauth/Okta.java
Okta.configureResourceServer401ResponseBody(http);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
}
and
@Configuration
@Order(200)
public class ApiWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/public/**","/api/**").permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated();
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.cors();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
}
any request to /token/exchange
must have an Okta provided token. this seems to be working as I expect. any request to /api/**
must have my custom created JWT. my SPA is sending the custom JWT, but in an effort to understand things better, I figured I could just set all the /api
calls to permitAll and prove my Okta filter is not getting in the way.
with more debugging, it seems that BearerTokenAuthenticationFilter
is getting called both both the /token/exchange
call and the /api/entities
call. Both times it's checking the token against the Okta Oauth provider. So it's failing on the /api/entities
call which has my custom token....NOT the okta token
I'm so confused