Reputation: 191
We have 2 parts to our application.
The Web interface uses SAML 2.0 authentication supported by Okta as the Identity Provider. The backend creates the HTTP Session and sends the JSESSIONID in the cookie.
Now the UI calls the REST end-points to display data. We need to add an authentication layer to our REST API's and I had asked a separate question about that over here Authenticating rest endpoints and the UI using Okta.
My question here specifically is what can I pass from the UI as a means of authentication to these API calls because the UI is really authenticated and the HTTP session is still valid. So I shouldn't need to create a separate OAuth 2.0 token pass it to the UI, so that UI can pass that back to the backend. The OAuth 2.0 flow makes sense for an external client using our REST end points.
Update 1
This is the excerpt of my securityContext.xml that defines both the authentication schemes:
<!-- Authenticating REST APIs -->
<security:http pattern="/rest/**" use-expressions="false">
<security:intercept-url pattern="/nltools/**" access="IS_AUTHENTICATED_FULLY" />
<security:custom-filter before="BASIC_AUTH_FILTER" ref="authValidationFilter" />
<security:http-basic/>
</security:http>
<!-- SAML processing endpoints -->
<security:http pattern="/saml/**" entry-point-ref="samlEntryPoint">
<security:custom-filter before="FIRST" ref="metadataGeneratorFilter" />
<security:custom-filter before="CSRF_FILTER" ref="samlFilter" />
<security:custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter" />
</security:http>
<!-- Secured pages with SAML as entry point -->
<security:http entry-point-ref="samlEntryPoint" use-expressions="false">
<security:csrf />
<security:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" />
<security:custom-filter before="FIRST" ref="metadataGeneratorFilter" />
</security:http>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="httpBasicAuthenticationProvider" />
<!-- Register authentication manager for SAML provider -->
<security:authentication-provider ref="samlAuthenticationProvider"/>
<!-- Register authentication manager for administration UI -->
<security:authentication-provider>
<security:user-service id="adminInterfaceService">
<security:user name="admin" password="admin" authorities="ROLE_ADMIN"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
I am not sure how can I execute SecurityContextPersistenceFilter. Should I override and add that one of the filters for my /rest/**
pattern?
Update 2
Here is the JS Code (React) that makes a call to the backend:
return Promise.resolve().then(() => {
return request.post('/rest/v1/projects')
.send(data)
.then((success) => {
console.log('success!', success);
var projectName = success.body.name;
var projectId = success.body.id;
self.props.dispatch( projectActions.addNewProject(
projectId,
projectName
));
self.props.dispatch( appActions.displayGoodRequestMessage( projectName + " Saved") );
self.props.dispatch( projectActions.fetchProject( projectId ) );
self.props.router.push('/projects');
}
Now JS code can choose to send all the cookies associated with this domain and that way the backend can get the JSESSION ID cookie, however, JS doesn't do that and I don't think its the right thing to do.
If on the other hand I execute https://mydomain/rest/v1/projects
in the browser and as long as I am logged in, I will get results because this time when my filter checks for the valid HTTP Session, it can get the session from the request with request.getSession(false)
, though that is not true when the JS calls the API. It becomes completely a different user-agent.
Update 3
Per suggestion by @Vladimír Schäfer, I could change the above JS code just a bit to send cookies as .withCredentials()
and authenticate with the backend without having to do anything special
return Promise.resolve().then(() => {
return request.post('/rest/v1/projects')
.withCredentials()
.send(data)
.then((success) => {
console.log('success!', success);
var projectName = success.body.name;
var projectId = success.body.id;
self.props.dispatch( projectActions.addNewProject(
projectId,
projectName
));
self.props.dispatch( appActions.displayGoodRequestMessage( projectName + " Saved") );
self.props.dispatch( projectActions.fetchProject( projectId ) );
self.props.router.push('/projects');
}
Upvotes: 0
Views: 1676
Reputation: 15533
As long as the REST API is part of the same application which front-end uses for authentication with Spring Security, it will have access to the JSESSIONID and therefore to the context of Spring Security which contains all the information about the authenticated user. Therefore there's no need for any additional authentication mechanism.
If you execute filter SecurityContextPersistenceFilter
when handling your Jersey calls, you'll be able to access the security context using:
SecurityContextHolder.getContext().getAuthentication();
The SecurityContextPersistenceFilter
uses its configured repository to fetch the Authentication object and stores it in the SecurityContextHolder. Have a look at the HttpContextRepository which it uses by default. There you will find that the security context is stored is HttpSession under key SPRING_SECURITY_CONTEXT
, so you can also fetch it directly.
Of course you can also use Spring Security to enforce authentication and authorization on your REST API, just like you do on the front-end - then everything will be handled for you.
Upvotes: 2