The Authorization Server in OAuth has the task of issuing an access token that allows the Client Application to use this access token to request the resource it needs to use. The Resource Server will validate this access token with the Authorization Server every time the Client Application requests to resource to decide whether to allow the Client Application access to this resource. You can use many other open sources such as Keycloak, Spring Security OAuth (deprecated), or a new Spring project called Spring Authorization Server to implement this Authorization Server. In this tutorial, I will show you how to use Spring Authorization Server to implement OAuth Authorization Server!
First, I will create a new Spring Boot project with Spring Web Starter, Spring Security Starter and OAuth2 Authorization Server Starter:
to make an example.
Result:
With the auto-configuration mechanism, Spring Boot currently supports the Spring Authorization Server, so you only need to configure some required information such as Client Application information and user information to be able to up and run an Authorization Server with Spring Authorization Server.
In this tutorial, I will not use this auto-configuration mechanism of Spring Boot!
Authorization Server configuration
First, I will create a new AuthorizationServerConfiguration class to configure the Authorization Server.
By default, the Spring Authorization Server supports the OAuth2AuthorizationServerConfiguration class with default configurations for an Authorization Server. If you take a look at the code of this class, you will see that it defines an applyDefaultSecurity() method that initializes the OAuth2AuthorizationServerConfigurer object, to apply the default configurations that this OAuth2AuthorizationServerConfigurer class defines:
1 2 3 4 5 6 7 8 9 10 11 12 |
public static void applyDefaultSecurity(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer(); RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher(); http .securityMatcher(endpointsMatcher) .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated() ) .csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher)) .apply(authorizationServerConfigurer); } |
As you can see, the applyDefaultSecurity() method defines security for the default endpoints of an Authorization Server.
Class OAuth2AuthorizationServerConfiguration also defines a bean for the SecurityFilterChain class that calls the applyDefaultSecurity() method to register these default configurations with Spring Security of Authorization Server.
You can import this OAuth2AuthorizationServerConfiguration class using Spring’s @Import annotation to use these default configurations:
1 2 3 4 5 6 7 8 9 10 11 |
package com.huongdanjava.springauthorizationserver; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; @Configuration @Import(OAuth2AuthorizationServerConfiguration.class) public class AuthorizationServerConfiguration { } |
or if you want to add something custom code, then declare a bean for the SecurityFilterChain class and call the applyDefaultSecurity() method as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
package com.huongdanjava.springauthorizationserver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; import org.springframework.security.web.SecurityFilterChain; @Configuration public class AuthorizationServerConfiguration { @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); return http.formLogin(Customizer.withDefaults()).build(); } } |
Here, I added more code so that if the user does not have permission to request the default endpoints of an Authorization Server, the Authorization Server will redirect to the login page.
With an Authorization Server, an important thing that we need to do is define the JSON Web Key to verify the information in the access token that the user requested to the Resource Server, issued by the Authorization Server. A JwtDecoder bean with an object of the JWKSource class is required to complete the configuration of this Authorization Server. We can define beans for these objects as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
@Bean public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); } @Bean public JWKSource<SecurityContext> jwkSource() throws NoSuchAlgorithmException { RSAKey rsaKey = generateRsa(); JWKSet jwkSet = new JWKSet(rsaKey); return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); } private static RSAKey generateRsa() throws NoSuchAlgorithmException { KeyPair keyPair = generateRsaKey(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); return new RSAKey.Builder(publicKey) .privateKey(privateKey) .keyID(UUID.randomUUID().toString()) .build(); } private static KeyPair generateRsaKey() throws NoSuchAlgorithmException { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); return keyPairGenerator.generateKeyPair(); } |
You also need to declare an additional bean of the AuthorizationServerSettings class to complete the configuration information for the Authorization Server.
The AuthorizationServerSettings class allows us to customize the default Spring Authorization Server configurations related to the Issuer Identifier, the JWK Set endpoint, and some other settings. You can configure the bean for the AuthorizationServerSettings class as follows:
1 2 3 4 5 6 7 |
@Bean public AuthorizationServerSettings authorizationServerSettings() { // @formatter:off return AuthorizationServerSettings.builder() .build(); // @formatter:on } |
Spring Security configuration
When the Authorization Server redirects to the login page because the user is not authenticated, we need to define another SecurityFilterChain to handle this request and all other requests of the Authorization Server. Because the OAuth2AuthorizationServerConfiguration class only defines security for the default endpoints of the Authorization Server.
We can define this SecurityFilterChain as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package com.huongdanjava.springauthorizationserver; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SpringSecurityConfiguration { @Bean SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { // @formatter:off http .authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated() ) .formLogin(Customizer.withDefaults()); // @formatter:on return http.build(); } } |
At this point, the login page will display if the user is not logged in.
Register client with Authorization Server
Spring Authorization Server uses the RegisteredClient class to declare the information of a client registered with the Authorization Server and uses the implementation of the RegisteredClientRepository interface to store the information of all these clients.
We can declare client information using memory or a certain database:
For simplicity, I will use memory as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Bean public RegisteredClientRepository registeredClientRepository() { // @formatter:off RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("huongdanjava") .clientSecret("{noop}123456") .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUri("https://oidcdebugger.com/debug") .scope("test") .build(); // @formatter:on return new InMemoryRegisteredClientRepository(registeredClient); } |
There are several important properties that a client must have: client ID and authorization grant type enabled for this client ID.
Client ID, I don’t need to explain, right? For authorization grant type, Spring Authorization Server supports all grant types of OAuth 2.
Client secret depends on the client type we want to define, if our client is confidential, see also Client types in OAuth 2.0, Client secret is mandatory. Here, you need to declare how to encrypt the client secret with PasswordEncoder, if you don’t want to encrypt it for testing purposes, we can use NoOpPasswordEncoder by declaring “{noop}” at the beginning of the client secret as I did above. Remember this is for testing purposes only!
The Client Authentication method is also required if our client is confidential, declared to define how we authenticate with some endpoints using client secret.
Depending on the grant type of the client you are defining, some other required information that we need to declare. For example, in my case, I am defining a client with grant type authorization_code, so I have to define a redirect_uri. Here, I will use the tool https://oidcdebugger.com/ to get the authorization code, so I define the redirect_uri with the value https://oidcdebugger.com/debug as you can see.
Depending on your needs, let’s define client information accordingly.
Register user with Authorization Server
User information logged into the Authorization Server, I use memory with the following declaration:
1 2 3 4 5 6 7 8 9 10 |
@Bean public UserDetailsService users() { UserDetails user = User.withDefaultPasswordEncoder() .username("admin") .password("password") .roles("ADMIN") .build(); return new InMemoryUserDetailsManager(user); } |
OK, at this point, we have completed the basic configuration for the Authorization Server.
To check the results, I will use the tool https://oidcdebugger.com/ as I mentioned above, with the following declaration:
Click Send Request on this page, you will see the Authorization Server login page displayed as follows:
Log in with the information we have declared above, you will see the following results:
Using this authorization code along with the client secret that we have declared, you can get the access token for this client as follows:
Gordon Ko
Hi, This article is great and solve many puzzles. I still have a questions that hope you can answer
How the client ID huongdanjava1 correlating to the user admin/password with role ADMIN. In case the auth server need to support more than one client ID
Khanh Nguyen
You can define other client IDs if you want. A user can associate with many client IDs in Authorization Server.
Sahil
Can we catch 401 unauthenticated error message in springboot code? As it is required in our usecase because we can to take some action if request is found to be unauthenticated (error code 401).
Khanh Nguyen
Yes, we can. How did you authenticate with Authorization Server?
Sahil
I had followed https://thomasandolf.medium.com/spring-security-jwts-getting-started-ebdb4e4f1dd1 article to setup springboot security. The authorization server I am using is AWS cognito.
Everything is working fine w.r.t authentication and authorization working. But as I said I have additional requirement to fetch 401 error message for unauthenticated users and do some action. Bottleneck is that I am unable to catch 401 error at springboot level using above stated article implementation.
Sahil
I have followed https://thomasandolf.medium.com/spring-security-jwts-getting-started-ebdb4e4f1dd1 article.
Authentication and authorization are working fine. As I said I have additional requirement to catch 401 unauthenticated error at springboot code level, which I am unable to do so. Need some assistance in same.
BTW Thanks Khanh for quick reponse 🙂
Khanh Nguyen
In case of an authentication error, what did you get from the Authorization Server @Sahil?
Fevzi
Hi,
I would like to ask if you have got any experience with an angular app as the client. I am trying the “angular-oidc.js” library to implement Authorization Code Flow +PKCE on the client side to make things simpler but somehow my frontend is blocked by the authorization server due to the CORS issue. In your configuration classes, I see no “CORS policy” code. How do you think one could configure the CORS policy in the configuration classes?. Thank you very much for your help in advance.
Khanh Nguyen
Yes, you should add the configuration for the CORS policy to allow the Angular application to call your BE.
Ricardo Legorreta
How can I add an extra endpoint to the Authorization Server. I tried to use the securityFilterChain with no success. It sends me an exception like: org.thymeleaf.exceptions.TemplateInputException: Error resolving template [myendpoint], template might not exist or might not be accessible by any of the configured Template Resolvers.
What I am doing wrong?
Khanh Nguyen
The error is related to your configuration for Thymeleaf. Not sure what you did to add an extra endpoint. If you can, please add more details.
Akin
This example doesn’t work anymore with authorization server 1.0.
Also, even using the authorization server version, the postman test never works. It keeps bringing up a HTML printout of a sign-in page, which the example stated here never addresses.
ash200
I am trying spring auth server 1.0.0 and really facing issues. I tried it before with 0.2.0 and the examples worked out of the bax.
Now I am trying federated spring auth server 1.0.0 samples with messages-client from https://github.com/spring-projects/spring-authorization-server and gives following error
{“exception”:”[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: Could not extract response: no suitable HttpMessageConverter found for response type [class org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse] and content type [text/html;charset=utf-8]”,”timestamp”:1674107981336}
This I am getting with local server as well as with google as a federated oauth server. HAs anybody got it working for 1.0.0?
Khanh Nguyen
Hello, my book already covered version 1.0.0 of Spring Authorization Server. Please check it at https://huongdanjava.com/spring-authorization-server-from-the-basics-ebook-2.html.
froil
hi, can the generated access token be not a JWT?
In old spring-oauth-autoconfig2, the access token is not encoded in JWT
Elgsylvain85
Good guide Thank you,
My question is : if you are already Register user with Authorization Server so Is it important to Register client with Authorization Server again ? and what is the difference ?
Pradeep
Hi..
Looking for suggestions on implementing security on spring-boot microservices integrated with angular UI. I have an external identity provider(Ping Federate) to support SSO and all user roles/authorities are maintained in the application database.
What is the best approach to secure APIs? If Oauth is recommended way, how to implement it(Stateless).
Should the Authorization Server be customized to connect to the
identity provider Authorization Server and generate tokens from the custom Authorization Server?
or
Oauth2 client should generate tokens by loading user details from the database after successful authentication with the identity provider?
Any code samples along with suggestions will be appreciated.
Peter
Hi Khanh,
Thanks for your tutorial. I cant seem to get the code to work 🙁
When I am prompted to login I use “admin” & “password” as the credentials but I keep getting Bad Credentials… In my code I have this bean as you described:
But In the debug log I get:
2022-05-23 11:49:19.561 DEBUG 44060 — [nio-8000-exec-1] o.a.t.util.http.Rfc6265CookieProcessor : Cookies: Parsing b[]: JSESSIONID=61B94763D6232EC0BC149860811BFE45
2022-05-23 11:49:19.561 DEBUG 44060 — [nio-8000-exec-1] o.a.catalina.connector.CoyoteAdapter : Requested cookie session id is 61B94763D6232EC0BC149860811BFE45
2022-05-23 11:49:19.561 DEBUG 44060 — [nio-8000-exec-1] o.a.c.authenticator.AuthenticatorBase : Security checking request POST /login
2022-05-23 11:49:19.561 DEBUG 44060 — [nio-8000-exec-1] org.apache.catalina.realm.RealmBase : No applicable constraints defined
2022-05-23 11:49:19.561 DEBUG 44060 — [nio-8000-exec-1] o.a.c.authenticator.AuthenticatorBase : Not subject to any constraint
2022-05-23 11:49:19.562 DEBUG 44060 — [nio-8000-exec-1] o.s.security.web.FilterChainProxy : Securing POST /login
2022-05-23 11:49:19.562 DEBUG 44060 — [nio-8000-exec-1] s.s.w.c.SecurityContextPersistenceFilter : Set SecurityContextHolder to empty SecurityContext
2022-05-23 11:49:19.562 DEBUG 44060 — [nio-8000-exec-1] org.apache.tomcat.util.http.Parameters : Set encoding to UTF-8
2022-05-23 11:49:19.562 DEBUG 44060 — [nio-8000-exec-1] org.apache.tomcat.util.http.Parameters : Start processing with input [username=user1&password=password&_csrf=77f2d76f-52d5-402d-9dd1-3d9493b5e794]
2022-05-23 11:49:19.617 DEBUG 44060 — [nio-8000-exec-1] o.s.s.a.dao.DaoAuthenticationProvider : Failed to find user ‘user1’
2022-05-23 11:49:19.618 DEBUG 44060 — [nio-8000-exec-1] o.s.s.web.DefaultRedirectStrategy : Redirecting to /login?error
2022-05-23 11:49:19.618 DEBUG 44060 — [nio-8000-exec-1] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2022-05-23 11:49:19.618 DEBUG 44060 — [nio-8000-exec-1] w.c.HttpSessionSecurityContextRepository : Did not store empty SecurityContext
2022-05-23 11:49:19.618 DEBUG 44060 — [nio-8000-exec-1] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
Do you know why it cant find user1?
Thanks in advance!
Peter
Richard
Greetings. Could you answer why I have cors problems when I make a request from the frontend server to oauth2/token?
Ebube
Yeah I also get this error, even when I have enable cors on the server.
Alessandro
Hi, did you get a solution? Best Regards.
Puscas SEBASTIAN
can this authorization server also be an oauth2 client and a resource server? I am not sure how to plug in an javascript client app with multiple logins options like this server and google at the same time?
Victor van den Hoven
Hello Khanh Nguyen,
Since the “Spring Security OAuth” is deprecated now, I am trying to migrate my old AuthorizationServerConfigurerAdapter-based application to the new Spring Authorization Server.
Your blog is helping me a lot, but the following is unclear to me.
Would it be possible to get an access-token from the authorization-server without a login-form that pops-up for username password?
In my old “Spring Security OAuth”-implementation the oauth2-client that wants to get an access-token, just posts a request with the following multiValueMap:
MultiValueMap map = new LinkedMultiValueMap();
map.add(“client_id”, ….);
map.add(“client_secret”, ….);
map.add(“scope”, “session-type:Analyst”);
map.add(“grant_type”, “password”);
map.add(“username”, restApiUserName);
map.add(“password”, restApiUserPassword);
and the authorization-server figured-out by means of a UserDetailsService-bean that the credentials were ok and returned the access-token, without asking for it.
I can not get the Spring authorization-server to work like this.
Khanh Nguyen
Grant type “password” was deprecated and will not supported anymore @Victor
Victor van den Hoven
Hi, thanks very much for your answer!
I understand that password grant type is deprecated.
My client is a machine so it can not enter any password.
Would it be possible to work with authorization code grant type without out a login form?
Khanh Nguyen
You can use offline token in this case
Victor van den Hoven
OK, thanks again.
What do you mean exactly with offline token?
How would that work?
Khanh Nguyen
This is the article about offline token in Keycloak https://huongdanjava.com/offline-token-with-keycloak.html.
For Spring Authorization Server, looks like it does not support for offline token now. I will give you an update if any.
Micky
@victor
You may find password support for spring authorization server here: https://github.com/Basit-Mahmood/spring-authorization-server-password-grant-type-support
Vijay Kumar
Hi, Thanks for this article It helped me alot to understand the things.
When I am hitting url for authorization code it’s showing me login page but after entering the user details it is redirecting me to 401.
Stanley Pham
Hi Nguyen
Do you know how to map roles/authorities into access_token?
I remember that when I use Spring Cloud OAuth2, I used AuthorizationServerEndpointsConfigurer similar to this link below
https://stackoverflow.com/questions/31345466/mapping-user-roles-to-oauth2-scopes-authorities
Dekso
Hi, Can I get the code from /authorize endpoint automatically using api without the login page? I wanted to replicate the password grant flow by using the authorization code flow since it is not anymore supported. Thanks in advance.
Khanh Nguyen
You should use client_credentials grant type @Dekso.
Carles
How can aggregate AuthenticationManagerBuilder with WebSecurityConfigurerAdapter to?
I have:
public class SecurityConfig extends WebSecurityConfigurerAdapter
but extends WebSecurityConfigurerAdapter generate conflicts with:
public SecurityFilterChain defaultSecurityFilterChain
and i need AuthenticationManagerBuilder for save authentication session and add user details service for more information in token jwt.
Erick
Hi, thanks for the article, it was very helpful. I have set up my authorization server successfully and am able to retrieve the token but when I use the bearer token to make a request I get this error
{
"timestamp": "2021-12-06T09:41:03.911+00:00",
"status": 403,
"error": "Forbidden",
"path": "/auth/user"
}
Erick
Here is my security config
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/actuator/**").permitAll()
.antMatchers("/routes/**").permitAll()
.antMatchers("/h2-console/**").permitAll()
.antMatchers("/oauth2/**").permitAll()
.antMatchers(HttpMethod.POST, "/auth/user").permitAll()
// .antMatchers(HttpMethod.GET, "/auth/user").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers(HttpMethod.GET, "/auth/user").hasAuthority("SCOPE_read")
// .access("hasAuthority('SCOPE_read')")
// .access("hasAuthority('" + AuthoritiesConstants.ADMIN + "')")
// .access("hasAuthority('SCOPE_read') and hasAuthority('" + AuthoritiesConstants.ADMIN + "')")
.anyRequest().authenticated()
.and()
.csrf().disable()
.headers().frameOptions().disable()
.and()
.formLogin(withDefaults());
return http.build();
}
// client configs
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(Customizer.withDefaults()).build();
}
private RegisteredClient webClient() {
return RegisteredClient.withId("98a9104c-a9c7-4d7c-ad03-ec61bcfeab36")
.clientId(authProps.getClientId())
.clientName(authProps.getClientName())
.clientSecret(authProps.getClientSecret())
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://127.0.0.1:8080/authorized")
.scope("create").scope("read").scope("write").scope("update").scope("delete")
.tokenSettings(tokenSettings)
.build();
}
Sergey
Hmm… It does work well with “https://oidcdebugger.com/debug” but does not work if redirect_uri is “http://localhost:8080/authorize”. 🤔
If I update the code to use .redirectUri(“http://localhost:8080/authorize”) then it the authorization server should redirect to http://localhost:8080/authorize with the authorization code attached. But it does not redirect… Instead, it displays the /error page with the following message.
“There was an unexpected error (type=None, status=999).”
Khanh Nguyen
You can enable DEBUG log to see the problem @Sergey.
SK
hmm… The Debug mode is informative but it does not tell why it is redirecting to /error..
Start processing with input [username=test&password=12345&_csrf=19bb6ea1-4b74-4daf-b640-2adecfe5bdd7]
Authenticated user
Changed session id from 84DEA2449BF8248B2CDCA1F001502B1A
Replaced CSRF Token
Set SecurityContextHolder to UsernamePasswordAuthenticationToken [Principal=org.springframework.security.core.userdetails.User [Username=test, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ROLE_ADMIN]], Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=84DEA2449BF8248B2CDCA1F001502B1A], Granted Authorities=[ROLE_ADMIN]]
Redirecting to http://localhost:8081/error?client_id=client1&redirect_uri=http://localhost:8080/authorize&scope=openid&response_type=code
Strange… if redirect_uri is set to https://oidcdebugger.com/debug then it works just fine…
Sandeep Yandra
Exactly i too had the same problem… only for localhost , we have this problem… i face this problem when using swagger for the moment
only if i login via 127.0.0.1:8080/swagger-ui.html .. it redirects with http://127.0.0.1:9500/swagger-ui/oauth2-redirect.html other wise it goes to localhost redirection and failing with error
if you find any solution .. please let me know as well
SK
Interesting… If I use anything other than http://localhost for a redirect_uri then it works well.
For example, if I set http://myserver.com as redirect_uri then I get redirected and the authorization code attached.
But if localhost is used. They it redirects to /error…
Carles
how to obtains token jwt without code?
directly call /oauth2/token with clientId and clientSecret
Khanh Nguyen
You can define a RegisteredClient with client_credentials grant type.
Leandro
Hi Khanh! Can client_credential return refresh token ?
Khanh Nguyen
No @Leandro. You should use the refresh_token grant type. I mention it in my book https://huongdanjava.com/spring-authorization-server-from-the-basics-ebook-2.html
Manikandan TK
Great Article !! Thanks a lot
Can we have our own custom authentication for validating user credentials as our organization has different method for password encryption
sathish
I had implemented the custom user detail service injection point is working and retrieving the user from database but it does not authenticate here is my code. Can you guide me on what I am doing wrong here? you can see the stack trace also
import com.huongdanjava.dto.UserPrincipal;
import com.huongdanjava.model.User;
import com.huongdanjava.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.transaction.annotation.Transactional;
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// TODO Auto-generated method stub
System.out.println(“UserName===>”+username);
User user = userRepository.findByUsername(username)
.orElseThrow(() ->
new UsernameNotFoundException(“User not found with username or email : ” + username)
);
System.out.println(“user=====>”+user);
return UserPrincipal.create(user);
}
}
2021-11-18 12:54:25.865 TRACE 18228 — [nio-8080-exec-5] o.s.s.w.a.AnonymousAuthenticationFilter : Set SecurityContextHolder to AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=BB05EE690F46417798A83E5CE1C21406], Granted Authorities=[ROLE_ANONYMOUS]]
2021-11-18 12:54:25.868 TRACE 18228 — [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Invoking SessionManagementFilter (10/12)
2021-11-18 12:54:25.872 TRACE 18228 — [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Invoking ExceptionTranslationFilter (11/12)
2021-11-18 12:54:25.873 TRACE 18228 — [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Invoking FilterSecurityInterceptor (12/12)
2021-11-18 12:54:25.876 TRACE 18228 — [nio-8080-exec-5] o.s.s.w.a.i.FilterSecurityInterceptor : Did not re-authenticate AnonymousAuthenticationToken [Principal=anonymousUser, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=BB05EE690F46417798A83E5CE1C21406], Granted Authorities=[ROLE_ANONYMOUS]] before authorizing
2021-11-18 12:54:25.878 TRACE 18228 — [nio-8080-exec-5] o.s.s.w.a.i.FilterSecurityInterceptor : Authorizing filter invocation [GET /custom-login] with attributes [permitAll]
2021-11-18 12:54:25.884 DEBUG 18228 — [nio-8080-exec-5] o.s.s.w.a.i.FilterSecurityInterceptor : Authorized filter invocation [GET /custom-login] with attributes [permitAll]
2021-11-18 12:54:25.891 TRACE 18228 — [nio-8080-exec-5] o.s.s.w.a.i.FilterSecurityInterceptor : Did not switch RunAs authentication since RunAsManager returned null
2021-11-18 12:54:25.893 DEBUG 18228 — [nio-8080-exec-5] o.s.security.web.FilterChainProxy : Secured GET /custom-login
2021-11-18 12:54:25.905 TRACE 18228 — [nio-8080-exec-5] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match request to [Is Secure]
2021-11-18 12:54:25.915 DEBUG 18228 — [nio-8080-exec-5] w.c.HttpSessionSecurityContextRepository : Did not store anonymous SecurityContext
2021-11-18 12:54:25.924 DEBUG 18228 — [nio-8080-exec-5] w.c.HttpSessionSecurityContextRepository : Did not store anonymous SecurityContext
2021-11-18 12:54:25.927 DEBUG 18228 — [nio-8080-exec-5] s.s.w.c.SecurityContextPersistenceFilter : Cleared SecurityContextHolder to complete request
Juan Luis
Hi! Great tutorial, thanks for share. I’ve tried to add a custom login page following https://huongdanjava.com/custom-login-page-using-bootstrap-and-thymeleaf-in-spring-security.html
The problem is: once authenticated, the /login endpoint does not redirect to /oauth2/authorize anymore but to /login again. This is my commit with the changes: https://github.com/supertorpe/huongdanjava.com/commit/4c431fc9c404603a8f08e52588d67443b1125ee9
Khanh Nguyen
You also need change the configuration for authorizationServerSecurityFilterChain in the class AuthorizationServerConfiguration as follows:
Juan Luis
Thank you so much! You saved my day.
Manikandan TK
I have successfully setup my own login page and client application. But after successful login it does not redirect to client application its again redirect to login page itself.
Am I missing anything
Mihajlo Stankov
Hi guys! Do you able to find a solution for this because I’m having the same issue?
Khanh Nguyen
Could you share your project? I can have a debug.
Mihajlo Stankov
Tnx a lot Khanh, but I already fixed this.
I have another question 🙂 Do you maybe have or know some tutorial for implementing authorization code flow with PKCE in the spring authorization server?
Tnx in advance!
Suraj Patil
Hey, i have a login form available at port 3000, but if i try to set the value of loginPage() as
(form.loginPage(“http://localhost:3000/login”)), I get a CORS error. I’m calling the “https://localhost:8081/login” endpoint from the UI. Do you have any suggestion?
Jack
Hi!
Thanks for this very good article!
How can customize userinfo endpoint?
Khanh Nguyen
I will have a tutorial for this.
Roman
I’m also very interested in this. When we can expect this?
Mohamed Farook
When can we expect your new tutorial?
Sandeep Yandra
looking for this .. any idea by when we can expect this ?
Jhon
Hi , thanks for the awesome example, i make it work in that way.
I got a doubt , i got this postman request
=================================================
POST /oauth2/token HTTP/1.1
Host: localhost:2005
Authorization: Basic VERTQWRtaW46VERTQWRtaW4=
Content-Type: application/x-www-form-urlencoded
Cookie: JSESSIONID=0397FD3D6FAD1C2282DB26CC8151BD41
Content-Length: 29
grant_type=client_credentials
==================================================
As you can see the request is similar to yours, the only difference is that in the headers i am sending a basic authorization coded, i want to make it work with your code, could you tell me what parts of the code need to be changed ??
Regards.
Khanh Nguyen
You should read about the client_credentails grant type in OAuth 2. We don’t have Basic authentication for this grant type, we need pair of client Id and client secret with this grant type. See also at https://huongdanjava.com/grant-types-in-oauth-2-0.html.
Dan
Can you explain why you declare the login form in 2 SecurityFilterChain?
Khanh Nguyen
I did explain in the tutorial. Can you please read it again and let me know if you still do not understand it?
Dan
I’ve already understood but I would like to clarify.
1 – SecurityFilterChain to choose auth server
2 SecurityFilterChain to login the user credentials
yes?
Khanh Nguyen
The SecurityFilterChain in AuthorizationServerConfiguration class in my example is for defining security for the default endpoints of an Authorization Server. If you take a look at OAuth2AuthorizationServerConfigurer class, you will see the definition for endpointsMatcher which define all the default endpoints of an Authorization Server.
The SecurityFilterChain in SpringSecurityConfiguration class for other endpoints of your application including the /login endpoint.
I hope it is clear for you now.
Dan
Hi!
Maybe I am doing something wrong but the last action to get the token does not work. There is a redirect to the login page
Khanh Nguyen
You can clone the repo https://github.com/khanhnguyenj/huongdanjava.com and this project https://github.com/khanhnguyenj/huongdanjava.com/tree/master/spring-authorization-server-example. Compare with yours to see what was the problem.
Dan
Sorry, it works correct. I forgot to write {noop}
Ashok
Hi Khanh
It is really a good example.
Could you tell me how we can retrieve user details from db (I am using cassandra db) to authenticate an user and client details instead of using inmemory.
Khanh Nguyen
For RegisteredClient in the database, please refer to this tutorial @Ashok https://huongdanjava.com/store-registeredclient-to-database-in-spring-authorization-server.html.
I will have a tutorial for retrieving the user information soon.
Thanks,
Ashok
Thanks for the reply.
I tried to implement both the approaches, for registeredClient in my scenario I am having few issues wrt cassandra db to implement this, which I will debug more.
For retrieving user details from db, I injected UserDetailsService interface by autowiring in webconfiguration class and provided separate class implementation of the UserDetailsService loadUserByUsername(String username) method. This worked for me with noOpPasswordEncoder. But facing issue with BcryptPasswordEncoder. Bcrypted (12 folds) Password is stored in my db for the user and while validating it is throwing error similar to “it does not look like Bcrypt password”. The same password is getting validated with old auth server.
So if possible in your next article kindly include these scenarios.
Khanh Nguyen
Yes, I will.
Hammad Naeem
Nice article.
How can we change this for grant_type password.?
Ashok
Not supported, as this grant_type is deprecated.
Hammad Naeem
Thanks for the reply. Then what will be a good way to secure the APIs that are used by your mobile Apps.?
Ashok
client_credentials can be the alternative.
John Hoang
Thanks Khanh for the good article! However, I tried to enable https for the auth server but getting 401 when accessing a protected api from a resource server (spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://127.0.0.1:9000/oauth2/jwks). Can you please show me how to config it correctly?
Khanh Nguyen
Have you take a look this tutorial https://huongdanjava.com/implement-oauth-resource-server-using-spring-security-oauth2-resource-server.html?
John Hoang
Yes, I did. But in that tutorial the setting is spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080. I’d like to set it point to https like this:
spring.security.oauth2.resourceserver.jwt.issuer-uri=https://localhost:8080
Khanh Nguyen
Are you able to share your project? Then I can figure out your problem.
Tuyen Kieu
Hi Khanh Nguyen,
where is the oauth2/token api come from?
Khanh Nguyen
From the code of Spring Authorization Server @Kieu.
You can take a look on https://github.com/spring-projects/spring-authorization-server/blob/main/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2TokenEndpointFilter.java
Tuyen Kieu
I got it. Thank you so much.
Phuong D. Pham
Hi Khanh Nguyen,
@Bean
public UserDetailsService users() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("admin")
.password("password")
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user);
}
Can you explain detail more about create UserDetailsService jdbc connection? Thanks
Khanh Nguyen
I will have another tutorial for this.
Phuong Pham
Thank you.
Ramana Chandaka
Hello, how can I load customized page instead of default login page provided by Spring.
Please guide me on this, here is the code snippet:
—Client end configuration ——-
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.oauth2Login(oauth2Login ->
oauth2Login.loginPage(“/oauth2/authorization/apicenter-client-oidc”))
.oauth2Client(withDefaults());
return http.build();
}
}
@EnableWebSecurity
public class DefaultSecurityConfig {
—- Oauth Server end configuration ————————–
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
try {
http.authorizeRequests(
authorizeRequests -> authorizeRequests
.anyRequest().authenticated())
.formLogin(Customizer.withDefaults());
return http.build();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Khanh Nguyen
Did you read this tutorial https://huongdanjava.com/custom-login-page-using-bootstrap-and-thymeleaf-in-spring-security.html @Ramana
Ramana Chandaka
Hello, how can I load customized page instead of default login page provided by Spring.
Please guide me on this, here is the code snippet:
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.oauth2Login(oauth2Login ->
oauth2Login.loginPage(“/oauth2/authorization/apicenter-client-oidc”))
.oauth2Client(withDefaults());
return http.build();
}
}
Beatriz
Hello, how can I sign the token with a public key, using the HS256 algorithm
Niall
hey Khanh Nguyen, me again. Where/how can I generate my own pfx file with a custom password?
Khanh Nguyen
Hi Niall, please see this tutorial https://huongdanjava.com/define-json-web-key-set-for-authorization-server-using-spring-authorization-server-and-pkcs12-key-store-file.html
Niall Smith
hey Khanh Nguyen, me again. Where/how can I generate my own pfx file with a custom password?
Abbas MG
and how i can get jwt access_token by userId or email? for login to users panel from admin dashboard
Khanh Nguyen
I think you can declare a RegisteredClient with authorization_code grant type. Example:
Abbas MG
i use
Spring Security SwitchUserFilter
for handling this feature. and work for meAbbas MG
Hi, thanks for this very good article it helps me a lot
how i can add more information to jwt access_token (jwt Claims)?
Khanh Nguyen
Looks like, currently, Spring Authorization Server does not support to add more claims in an access token.
Abbas MG
i finally find official solution
@Bean
OAuth2TokenCustomizer jwtCustomizer() {
return context -> {
context.getClaims().claim("key", "value");
};
}
Khanh Nguyen
Great, I will have a tutorial about this. Thanks, @Abbas.
Abbas MG
you’re welcome 🙂
ashok
@Bean
OAuth2TokenCustomizer jwtCustomizer() {
return context -> {
Authentication principal = context.getPrincipal();
if (context.getTokenType().getValue().equals(“access_token”) && principal instanceof UsernamePasswordAuthenticationToken) {
Set authorities = principal.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet());
User user = (User) principal.getPrincipal();
context.getClaims().claim(“authorities”, authorities);
context.getClaims().claim(“userId”, user.getUserId());
}
};
}
inside if condition statements can be modified as per your requirement.
Abbas MG
thank you so much ashok
Colin
Great article. Thanks
I’m looking to obtain access code without using the login form. Can you tell me the configuration I need for that? Also, is it only supporting POST to /oauth2/authorize – or can that be customised?
Thanks
Khanh Nguyen
You can declare a RegisteredClient with client_credentials as follows:
Niall Smith
Hey, thank you for the article. It is the best one available! On the subject of accessing a token without the login form, I have downloaded your code from git (linked further down in comments) but I cannot get my postman request to work. Would you be able to share the CURL for obtaining a token without the login? thanks again 🙂
Khanh Nguyen
Here is it @Niall:
Niall Smith
thank you!
Carles
How to apply this process with front-end framework and RESTful api?
Carles Saumell
The process to obtain code with /oauth2/authorize? and parameters without server form login.
Khanh Nguyen
You can add a RegisteredClient support authorization code grant type with PKCE.
Juan Luis
Hi! I have a spring-boot-starter-oauth2-client working with Authorization Code grant with PKCE (keycloak auth server). To do so, I’ve configured the client with:
authorization-grant-type: authorization_code
client-authentication-method: none
And it’s working fine (create the code_challenge, etc.)
What I’m trying to now is make this client working with the Spring OAuth Server, but I cannot find how to add a RegisteredClient with Authorization Code grant with PKCE,
The class AuthorizationGrantType (spring-security-oauth2-core-5.5.3) does not have a constant for PKCE
Khanh Nguyen
You can declare a RegisteredClient like below:
Ansou
Hi, thanks ,
But how to add custom Authentication provider ?
Santosh Keleti
Can you give one Example on token introspection endpoint
Nguyen Thuan
E có thử dùng password grant_type nhưng khi gửi request để lấy access token thì lại báo là ‘unsupported_grant_type’. Mình có cần config thêm gì không ạ?
Khanh Nguyen
Can you give me the code of your RegisteredClient?
Khanh Nguyen
BTW, we can discuss your problem here https://javavietnamforum.org/
Nguyen Thuan
I added 2 line into RegisteredClient
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
authorization_code grant type worked fine but the password grant type didn’t
And this is my declaration
RegisteredClient registeredClient1 =
RegisteredClient.withId(UUID.randomUUID().toString())
.clientId(“hb_client_id_1”)
.clientSecret(passwordEncoder.encode(“hb_client_secret_1”))
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUris(redirectUris -> {
redirectUris.add(“https://oauth.pstmn.io/v1/callback”);
redirectUris.add(“http://localhost:8080/login/oauth2/code/hb-gateway”);
redirectUris.add(“http://localhost:8080/authorized”);
})
.scope(OidcScopes.OPENID)
.scope(“notification.read”)
.tokenSettings(TokenSettings.builder()
.refreshTokenTimeToLive(Duration.ofDays(7))
.accessTokenTimeToLive(Duration.ofMinutes(5))
.build())
.build();
Khanh Nguyen
The implicit and password grant types will be not supported anymore due to the security perspective https://huongdanjava.com/vi/cac-loai-grant-types-trong-oauth-2-0.html.
You can refer to the feature list of Spring Authorization Server at https://github.com/spring-projects/spring-authorization-server/wiki/Feature-List.
In the code, you can check the class OAuth2TokenEndpointFilter, only 3 authentication converters are supported:
Nguyen Thuan
Thank you so much.
Sri Ram
Good and much needed one. What is the URL for validating the token /oauth/check_token is not working. i have custom flow to validate the token.
Khanh Nguyen
I think you can use the token introspection endpoint to do this.
Sri Ram
i have tried with introspection end point but gettin 401 even if pass valid token.
Khanh Nguyen
Please refer to https://huongdanjava.com/token-introspection-with-spring-authorization-server.html
Santosh Keleti
Thanks, much needed article. Can you explain how can i use grant_type=client_credentials.
Khanh Nguyen
You can declare a RegisteredClient as follows:
Santosh Keleti
Thanks a lot. it worked.
Santosh Keleti
Thanks. How can i change token expiration time and how can i invalidate the token forcefully.
Khanh Nguyen
You can read this tutorial https://huongdanjava.com/configure-expiration-time-for-access-tokens-in-spring-authorization-server.html for the expiration time.
I will have another tutorial for token revocation soon.
Santosh Keleti
Thanks. It worked for me.
Bassem
do you have a git repository for your code? can you share it to have full view of solution?
Khanh Nguyen
Yes, here is it https://github.com/huongdanjavacom/huongdanjava.com/tree/master/spring-authorization-server-example
Soren
Any pointers on how to use Spring-Authorization-Server project to connect to an identity provider (eg. Auth0) – and receive id-token and access-token and return own Spring-Authorization-Server access-token?
Khanh Nguyen
I don’t think Spring Authorization Server supporting identity provider like Keyloak with the current version.
Fernando
Hi, thanks for this very good article it helps me a lot… a have trouble in the last step when i want to POST /oauth2/token with code im getting a 401 from authorization server, i see in other guides that you have to pass a header “Authorization: Basic xxxxx”, dependes on your clientAuthenticationMethod (i suposed) but i dont know how to complete it
Khanh Nguyen
Can you give me the URL to those guides @Fernando? In OAuth 2, we have some grant types and different ways to get the access token depending on the grant type.