I am using version 1.4.0 of the Okta Spring Boot Starter to validate incoming JWT tokens. By using the Starter, both authentication and authorization works out of the box (the default works so well you don't even need to define anything in your own security configuration).
But the default created roles are not to my liking, so I want to map the incoming scopes & roles to my own defined Spring authorities. When using the plain OAuth starter, one can define a AuthoritiesExtractor bean to do just that:
#Bean
public AuthoritiesExtractor authoritiesExtractor() {
return new YourOwnAuthoritiesExtractor();
}
Next to that, there is also the option to implement a custom jwtAuthenticationConverter in your security configuration:
http
..
.oauth2ResourceServer()
.jwt(jwt -> jwt.jwtAuthenticationConverter(new JwtAuthenticationConverter() {
#Override
protected Collection<GrantedAuthority> extractAuthorities(final Jwt jwt) {
// implementation
}
})
But all of these things seems not to work with the Okta Starter, because the extractors are never called when a JWT token is send to the server. Does someone know how to implement it for Okta?
TL;DR
It's not possible with the standard Okta Starter at the moment.
Explanation
As Okta uses its own configurer to setup the oauth2ResourceServer, you cannot use a custom JwtAuthenticationConverter as it will be overridden by the one Okta defines. Neither can you use the AuthoritiesExtractor, as the interface is not even packaged with the Okta Starter.
To overcome this issue, Okta introduced the concept of the AuthoritiesProvider interface. By defining a custom bean, you can add you own authorities to the ones already set by Okta :
#Bean
AuthoritiesProvider myCustomAuthoritiesProvider() {
return (user, userRequest) -> lookupExtraAuthoritesByName(user.getAttributes().get("email"));
}
Sadly enough, the AuthoritiesProvider interface only supports authorization code flow and not resource servers. As long as #160 is not resolved, there will be no native suport for Opaque and JWT.
Related
I am having a hard time learning OAuth2 and OpenID Connect.
Every time I think I understand everything now, I come across another gimmick.
This time it's Spring Securities OAuth2LoginAuthenticationProvider.
The OAuth2LoginAuthenticationProvider is used instead of OidcAuthorizationCodeAuthenticationProvider if scope openid is not contained in requested scopes.
if (loginAuthenticationToken.getAuthorizationExchange().getAuthorizationRequest().getScopes().contains("openid")) {
// This is an OpenID Connect Authentication Request so return null
// and let OidcAuthorizationCodeAuthenticationProvider handle it instead
return null;
}
But why does this AuthenticationProvider load an OAuth2User via UserInfoEndpoint?
Isn't UserInfoEndpoint OIDC specific?
You are correct that OIDC defines a User-Info endpoint.
When using OAuth 2.0 to authenticate, there is still a need to figure out the user's information and so Spring Security publishes an interface (OAuth2UserService) that does that.
For non-OIDC implementations, I'd imagine that the OAuth2UserService is not pointed at an OIDC-compliant User-Info endpoint.
I have a hybrid application that uses Spring Security for roles validation but not for login.
PreAuthorize fails with "An Authentication object was not found in the SecurityContext"
I tried to add this to my Login controller but it doesn't seem to work:
SecurityContextHolder.getContext().setAuthentication(makeAuthentication(op));
The configuration is as follow:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebConfig extends WebSecurityConfigurerAdapter {}
Am I forced to use an authentication provider? How would it work in this case? The user roles are stored in the Operator object.
EDIT: It looks like the SecurityContext gets cleared between requests. How do I make it persistent?
I tried adding the security context to the session as SPRING_SECURITY_CONTEXT_KEY attribute but still no luck.
In the end, I made it work by restoring the context in an interceptor. Not sure this is the best way though.
Make sure your client is sending subsequent requests on the same session as your custom login request
If that is ensured, check if your custom login url goes through spring security but as an unsecured url. If it is not the case, you have to add the following to your custom login success stage.
SecurityContextHolder.getContext().setAuthentication(makeAuthentication(op));
HttpSession session = req.getSession(true);
session.setAttribute(SPRING_SECURITY_CONTEXT_KEY,
SecurityContextHolder.getContext());
Once these steps are in place, Spring security's SecurityContextPersistenceFilter will ensure SecurityContextHolder.getContext() is populated on subsequent requests.
I'd like to provide two ways to authenticate in my application, one is basic auth (users), and the other is some kind of token based (technical users). I understand that I need a custom ReactiveAuthenticationManager but I can't find clues on the big picture. (Actually, there are a very few insights for MVC, and none for WebFlux.)
1) How do I populate the Authentication's name and credentials in the token based approach? If I configure Spring Security to use httpBasic it's already populated. Some kind of filter needed?
2) How do I distinguish in the authentication manager where the credentials are coming from? Do I have to lookup in the userRepository and (if not found) in the technicalUserRepository too?
3) Do I have to override the SecurityContextRepository? All the tutorials do it but I don't see any reason to do so. What is it exactly? This source states that "SecurityContextRepository is similar to userDetailsService provided in regular spring security that compares the username and password of the user." but I think he means ReactiveUserDetailsService (neither UserDetailsService nor ReactiveUserDetailsService does that by the way, it's just for user lookup).
Since i am decent at Webflux and i have worked a lot with oauth2 i'll try and answer some of your questions.
1) How do I populate the Authentication's name and credentials in the
token based approach? If I configure Spring Security to use httpBasic
it's already populated. Some kind of filter needed?
A token never contains credentials. A token is something you get issued after an authentication has been done. So usually you authenticate against an issuing service. After you have authenticated yourself against that service you will be issued a token.
If its an oauth2 token the token itself is just a random string. It contains no data about the user itself. When this token is sent (using the appropriate header) to a service using spring security. Spring security has a token filter that will basically check that the token is valid, usually by sending the token to the issuer and asking "is this token valid?".
If using a jwt, its different, the jwt must contain some information like issuer, scopes, subject etc. etc. but its basically the same thing, there is a built in filter that will validate the jwt by sending it to the issuer (or using a jwk that the service fetches from the issuer so it can verify the integrity of the jwt without doing an extra request).
2) How do I distinguish in the authentication manager where the credentials are coming from? Do I have to lookup in the userRepository and (if not found) in the technicalUserRepository too?
You don't You usually define multiple SecurityWebFilterChains for different url paths. I have not done this in Webflux Spring Security, but thats how you do it in regular Spring Applications, and i don't see any difference here. Unless you are doing something crazy custom.
3) Do I have to override the SecurityContextRepository? All the tutorials do it but I don't see any reason to do so. What is it exactly? This source states that "SecurityContextRepository is similar to userDetailsService provided in regular spring security that compares the username and password of the user." but I think he means ReactiveUserDetailsService (neither UserDetailsService nor ReactiveUserDetailsService does that by the way, it's just for user lookup).
The answer here is probably no. You see Spring security 4 had very bad support for oauth2 and especially JWT. So people got accustomed to writing their own JWT parsers. When spring Security 5 came, Spring implemented a jwt filter that you can configure and use built in. But there are a lot of outdated Spring Security tutorials out there and foremost there are a lot of developers that don't read the official documentation.
They mostly google tutorials and get the wrong information and then work on that.
But easy explained:
SecurityContextRepository
If you have session based authentication (server establishes a session with a client) it will store the SecurityContext (session) in ThreadLocal during a request. But as soon as the request ends, the session will go lost unless we store it somewhere. The SecurityContextPersistenceFilter will use the SecurityContextRepository to extract the session from ThreadLocal and store it, most common is to store it in the HttpSession.
AuthenticationManager
Override this if you want to do a custom authentication process. Example if you want to validate something, call a custom LDAP, database, etc etc. It\s here you perform you authentication. But remember, most standard logins (like ldap, sql-servers, basic login etc.) already have prebuilt configurable managers implemented, when you select what login type like .httpBasic() you will get a pre-implemented AuthenticationManager.
UserDetailsManager
You override this when you want create a custom UserDetails object (also usually called Principal) In the UserDetailsManager you do you database lookup and fetch the user and then build and return a UserDetails object.
Those two interfaces are the most regular custom implementations, and are used if you need to to basic authentication/session based authentication.
If you wish to do token, you have to think about, who is the token issuer? usually the issuer is separate and all services just get tokens and validate them against the issuer.
I hope this explains some of the questions. I have written this on the bus so some things are probably wrong and not 100% correct etc. etc.
I am trying to use 2 authorization mechanisms simultaneously: LDAP and DAO.
They work in order of configuration. This is not appropriate if the user credentials match in both mechanisms.
(For example, in LDAP there is a user m.smith with a password qwerty and in the database there is a user m.smith with a password qwerty)
Is it possible to somehow convey the flag which mechanism to use at the current moment? Аnother mechanism in this case should be ignored.
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.contextSource(contextSource)
.userSearchFilter("(sAMAccountName={0})")
.userDetailsContextMapper(userDetailsContextMapper);
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
If you have two mechanisms that are completely disjoint then you should probably fix it on upper levels (starting from UI). For instance if user uses LDAP authentication you would send a flag (for instance a HTTP header) that would indicate what auth type to use. Then you can implement custom filter that would perform specific type of authentication (programmatically).
I am not aware of any other solution that could do what you want in runtime.
I have a pretty simple requirement (I use Spring-Security 4.0.1) but I can't find any examples on the web except what is been told on this page: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-server-handler
It is relatively simple to integrate a WebSocketHandler into other
HTTP serving environments with the help of
WebSocketHttpRequestHandler.
What I have: An implementation of WebSocketHandler that does the job and an HTTP serving environments using a Basic Authentication. My WebApplicationInitializer looks like this:
public class MyWebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
...
// WebSocket support - Handshake
Dynamic ws = servletContext.addServlet("webSocketHttpRequestHandler", new HttpRequestHandlerServlet());
ws.addMapping("/streaming/*");
// Spring Security Filter
FilterRegistration.Dynamic springSecurity = servletContext.addFilter("springSecurityFilterChain", new DelegatingFilterProxy());
springSecurity.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
}
}
This is how I plugged my websocket endpoint to my existing web application.
My WebSocket configuration class looks like (very simplified) this:
#Configuration
public class WebSocketServicesConfig{
#Bean
public WebSocketHttpRequestHandler webSocketHttpRequestHandler() {
return new WebSocketHttpRequestHandler(new StreamingWebSocketHandler());
}
}
StreamingWebSocketHandler implements WebSocketHandler.
I also have a RESTful Web Service (in the same server) that uses the configured Basic Authentication.
What is working: My RESTful Web Service is working with any web browsers. I can do some authenticated queries (credentials can be sent in the HTTP headers).
WebSocket queries are working and ask for authentication the first time I try to do some (under FireFox, a popup appears asking for credentials, once I enter them, client and server are able to communicate via WebSocket messages).
In my WebSocketHandler, the Spring object: WebSocketSession that contains informations about the authenticated user is correct (#getPrincipal() method returns a Authentication containing the right granted Authorities, details and so on...).
Note that once the websocket is authenticated, I can relaunch the query without re-enter them.
What I want: On a user point of view, this is bad because the credentials are required twice:
First for RESTful queries
Second for WebSocket queries
How can I bypass the second authentication assuming the first one succeeded? Is there a way to detect the client has been authenticated and not ask for credentials?
What I don't want: I don't want to use neither Stomp over websocket nor SockJs (I don't need to support old web browsers).