I have implemented a resource server and authorization server with spring cloud oauth2. When I implemented the OAuth application by using the default approach (without using asymmetric or symmetric approach) then the default token is generated by the authorization server and when I have to communicate with the resource server and the authorization server then the following properties can be used in the resource server config file.
resource-server.yml with default
security:
oauth2:
client:
client-id: web
client-secret: *****
resource:
token-info-uri: "http://localhost:9092/oauth/check_token"
When I use an asymmetric approach to sign the token with the private key in the authorization server and use the public key to validate the token in the resource server with the following property security:jwt:public-key: classpath:public.txt then the following unauthorized response has occurred when I call the resource from the resource server.
resource-server.yml with asymmetric
security:
jwt:
public-key: classpath:public.txt
API Endpoint
http://localhost:9090/user?Authorization=bearer **********
Response
{
"error": "invalid_token",
"error_description": "Cannot convert access token to JSON"
}
Note - when I use client and token-info-uri properties with an asymmetric approach in the resource-server.yml then the following error has occurred.
Method springSecurityFilterChain in org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration required a single bean, but 2 were found:
- tokenServices: defined by method 'tokenServices' in class path resource [com/benz/resource/api/config/ResourceServerConfiguration.class]
- remoteTokenServices: defined by method 'remoteTokenServices' in class path resource [org/springframework/boot/autoconfigure/security/oauth2/resource/ResourceServerTokenServicesConfiguration$RemoteTokenServicesConfiguration$TokenInfoServicesConfiguration.
I have to clarify the following question
Without using token-info-uri and client properties how does the resource server identify the particular authorization server?.
why does the unauthorized error has occurred (which is mentioned in the above)?
ResourceServerConfig class
#Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Value("${security.jwt.public-key}")
private Resource publicKey;
private TokenStore tokenStore;
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore());
}
#Bean
public DefaultTokenServices tokenServices(TokenStore tokenStore)
{
DefaultTokenServices tokenServices=new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore);
return tokenServices;
}
#Bean
public TokenStore tokenStore() throws Exception
{
if(tokenStore==null)
tokenStore=new JwtTokenStore(tokenConverter());
return tokenStore;
}
private JwtAccessTokenConverter tokenConverter() throws Exception
{
JwtAccessTokenConverter converter=new JwtAccessTokenConverter();
converter.setVerifierKey(getPublicKeyAsString());
return converter;
}
private String getPublicKeyAsString() throws Exception
{
return IOUtils.toString(publicKey.getInputStream(),UTF_8);
}
}
Github link for
resource-server-implementation
authorization-server-implemetation
If you need more details please comment it then I can update
I have solved the issue. In my case, tokenConverter method is called at the time of JwtTokenStore bean instance created. I have changed the approach to create the bean instance for JwtAccessTokenConverter separately by adding #Bean annotation. In that case, it is worked without any interruption.
#Bean
public JwtAccessTokenConverter tokenConverter() throws Exception
{
JwtAccessTokenConverter converter=new JwtAccessTokenConverter();
converter.setVerifierKey(getPublicKeyAsString());
return converter;
}
Related
Looking at the new spring-security-oauth2-authorization-server library, I need to implement a custom authorization server for a resource server. Actually, I intend to manually implement the PRIVATE_KEY_JWT Authentication method that is either not yet implemented or lacking examples. I followed one good example that uses this approach (the authorization server issuing the access token to the resource server). The resource server has a simple configuration that points to my issuer (authorization server) as in the code:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://auth-server:9000
but this approach uses client-id and client-secret credentials like:
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(Customizer.withDefaults()).build();
}
#Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("client_id123")
.clientSecret("{noop}secret123")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope("https://purl.imsglobal.org/spec/lti-ags/scope/score")
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
I appreciate if anyone could refer me an example of either a PRIVATE_KEY_JWT implementation or a custom authorization implementation that receives a JWT and issues an access token.
In application.yml set up the location of the certs, and create a config class RsaKeyProperties to load them:
rsa:
privateKey: classpath:certs/private.pem
publicKey: classpath:certs/public.pem
Then in WebSecurityConfig:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
protected SecurityFilterChain configure(
BasicAuthFilter basicAuthFilter,
ActivitiAuthenticationFilter activitiAuthenticationFilter,
HttpSecurity http) throws Exception {
return http
// Disabling CSRF is safe for token-based API's
.csrf().disable()
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeRequests(auth -> {
auth.antMatchers(
"/api/authenticate/**",
"/api/tenants/**").permitAll();
auth.antMatchers("/api/**").authenticated();
// When an exception is thrown, ErrorMvcAutoConfiguration sets stuff up so that /error is called
// internally using an anonymous user. Without this line, the call to /error fails with a 403 error
// because anonymous users would not be able to view the page.
auth.antMatchers("/error").anonymous();
})
.build();
}
#Bean
public JwtDecoder jwtDecoder(RsaKeyProperties rsaKeyProperties) {
return NimbusJwtDecoder.withPublicKey(rsaKeyProperties.publicKey()).build();
}
#Bean
public JwtEncoder jwtEncoder(RsaKeyProperties rsaKeyProperties) {
JWK jwk = new RSAKey.Builder(rsaKeyProperties.publicKey())
.privateKey(rsaKeyProperties.privateKey())
.build();
JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jwks);
}
}
The "SCOPE_" prefix that the identity server library was adding in the JWT didn't work well with spring security as far as I could figure out, so I removed that:
/**
* For some reason the JwtGrantedAuthoritiesConverter defaults to adding the prefix "SCOPE_" to all
* the claims in the token, so we need to provide a JwtGrantedAuthoritiesConverter that doesn't do
* that and just passes them through.
*/
#Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
grantedAuthoritiesConverter.setAuthorityPrefix("");
JwtAuthenticationConverter authConverter = new JwtAuthenticationConverter();
authConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return authConverter;
}
I am in a real problem here and would require your help. I work for a bank and have been assigned a task of implementing an OAuth2 service using Spring Boot, I have been exploring since last one week and have been able to implement a password flow grant type OAuth2 service, but now I have a few questions as my seniors said that password flow is not the right fit for our use case. First I would like to explain the use case:
Step 1: user will hit the application URL of the web app where no login is needed and before the application loads the OAuth2 service will be hit with logged in AD (system) user id.
Step 2. The OAuth2 service should authenticate the user using ldap with the given user id and return back all groups the user is part of along with the access token which will be used to access the API's there after
Now I have the below queries:
Which grant type is best suited for my need, from what I have read authorization code grant type seems to be the right fit? Or is it implicit?
Depending on the answer for question 1 what code changes do I need to make in the below code:
Code snippet for my authorization server:
Oauth2AuthserverApplication.java
#SpringBootApplication
#EnableAuthorizationServer
public class Oauth2AuthserverApplication {
public static void main(String[] args) {
SpringApplication.run(Oauth2AuthserverApplication.class, args);
}
}
OAuth2Congig.java
#Configuration
public class Oauth2Config extends AuthorizationServerConfigurerAdapter {
private String clientId = "client";
private String clientSecret = "secret";
private String privateKey = "private-key";
private String publicKey = "public-key";
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Bean
public JwtAccessTokenConverter tokenEnhancer() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(privateKey);
converter.setVerifierKey(publicKey);
return converter;
}
#Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(tokenEnhancer());
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints.authenticationManager(authenticationManager).
tokenStore(tokenStore())
.accessTokenConverter(tokenEnhancer());
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security)
throws Exception {
security.tokenKeyAccess("permitAll()").
checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws
Exception {
clients.inMemory().withClient(clientId).
secret(clientSecret).scopes("read", "write")
.authorizedGrantTypes("password",
"refresh_token").accessTokenValiditySeconds(20000)
.refreshTokenValiditySeconds(20000);
}
}
SecurityConfiguration.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
CustomDetailsService customDetailsService;
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
#Override
#Autowired
protected void configure(AuthenticationManagerBuilder auth) throws
Exception {
auth.userDetailsService(customDetailsService).
passwordEncoder(encoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().
and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}
}
Not pasting the model, dao, and service code of the authorization server class as they are not relevant for this question.
Code Snippet from resource server project:
OAuth2ResourceserverApplication.java
#SpringBootApplication
#EnableResourceServer
#RestController
public class Oauth2ResourceserverApplication {
public static void main(String[] args) {
SpringApplication.run(Oauth2ResourceserverApplication.class, args);
}
#RequestMapping(value="/api")
public String success() {
return "SUCCESS";
}
}
JwtConverter.java
#Component
public class JwtConverter extends DefaultAccessTokenConverter implements
JwtAccessTokenConverterConfigurer {
#Override
public void configure(JwtAccessTokenConverter converter) {
converter.setAccessTokenConverter(this);
}
}
SecurityConfiguration.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().
and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.NEVER);
}
}
application.yml
server:
port: 8081
security:
oauth2:
resource:
filter-order: 3
jwt:
key-value: private-key
OAuth2 has 4 grant types. To quickly understand the difference between 'Resource Owner Password Credential', 'Authorization Code', 'Implicit', let us compare them side-by-side:
NOTE: The full explanation is available at: https://blog.oauth.io/oauth2-flow-grant-types-in-pictures/.
To answer your question:
Which grant type is best suited for my need, from what I have read authorization code grant type seems to be the right fit? Or is it implicit?
If you compare based on 'Security' the purple bar, 'Authorization Code' is best. However you can see that it has the concept of Guard (backend) that performs access to the User data store on behalf of the App (frontend), i,.e., the App never has access to the Key/Access Token directly, as the Key is retrieved through the exchange of username/password between the User and the OAuth Server, and then passed on to the Guard.
The 'Resource Owner Password Credential' that you implemented is the least secure as the username/password is handed over to the App for the App to do everything that the User can without the User's further consent. However, in your scenario, the App and the User data store both belong to you so that alleviates the security issue.
Depending on the answer for question 1 what code changes do I need to make in the below code:
The full flow of Resource Owner Password Credential grant type you implemented is the left part of the image below and the Authorization Code grant type is the right part. As you can see, there are generally 5 steps. For Resource Owner Password Credential, some steps are not necessary, i.e., marked 'N.A.'.
NOTE:
The 'cloud' represents the App
The 'www' represents the User/Browser
The 'safe' represents the OAuth Server
To get from the left-side to the right-side, changes you required will be:
Step 1. If your OAuth Server is going to support different Apps, then it needs to support App pre-registration to get client id/secret. If you only have one App then you can skip this.
Step 2. Instead of the App prompting for username/password now the App will redirect the User to the OAuth Server to perform username/password authentication
Step 3. After authenticating the User the OAuth Server can prompt the User about what kind of permissions (e.g., read email, update profile, etc.) she wants to grant the App
Step 4. The OAuth Server instead of handing the Key/Access token to the App, it hands a code to User which then passed it to the App
Step 5. The App then exchanges the code for the Key/Access Token with the OAuth Server.
Once you get the Key/Access Token you can call any protected API on a different server which can then validate the Key/Access Token with the OAuth Server before responding to the API request, e.g., return the groups a User belongs to.
I have to integrate my system with third-party provider. This system is made with Spring and Angular.
Keep in mind that I need to create a custom login form instead redirecting to thirdy-party provider form like OAuth2.
He has created following endpoints:
Get token authentication
POST http://example.com/webapi/api/web/token
“username=972.344.780-00&password=123456&grant_type=password”
The response send me a token that I must use during all next requests.
Get user info
Authorization: Bearer V4SQRUucwbtxbt4lP2Ot_LpkpBUUAl5guvxAHXh7oJpyTCGcXVTT-yKbPrPDU9QII43RWt6zKcF5m0HAUSLSlrcyzOuJE7Bjgk48enIoawef5IyGhM_PUkMVmmdMg_1IdIb3Glipx88yZn3AWaneoWPIYI1yqZ9fYaxA-_QGP17Q-H2NZWCn2lfF57aHz8evrRXNt_tpOj_nPwwF5r86crEFoDTewmYhVREMQQjxo80
GET http://example.com/webapi/api/web/userInfo
That said, What I need to implement a custom authentication?
Could I use Spring OAuth2 in this case?
you can use Spring Security. The flow is the following. You authenticate against the Security token service. A cookie containing the authentication token is written to your browser. This token is sent on each subsequent request against the server.
On the rest server you will use Srping Security and more specifily you need to use AbstractPreAuthenticatedProcessingFilter in its implementation you will extract the token and associate it With the Security Context.
Here is example configuration of your spring Security
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
// TODO Auto-generated method stub
return super.authenticationManagerBean();
}
public void configure(WebSecurity web) throws Exception {
// do some configuration here
}
#Override
public void configure(HttpSecurity http) throws Exception {
// configure your Security here
// you can add your implementation of AbstractPreAuthenticatedProcessingFilter here
}
}
Here is your additional configuration
#Configuration
public class ExampleSpringSecurityConfig{
#Bean
public AuthenticationManager authenticationManager() {
return authentication -> authProvider().authenticate(authentication);
}
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> userdetailsService() {
// Construct your AuthenticationUserDetailsService here
}
#Bean
public PreAuthenticatedAuthenticationProvider authProvider() {
PreAuthenticatedAuthenticationProvider authProvider = new PreAuthenticatedAuthenticationProvider();
authProvider.setPreAuthenticatedUserDetailsService(userdetailsService());
return authProvider;
}
}
Yes, you can use Spring Oauth2. You have to implement the Resource Owner Password Credentials Grant Oauth2 flow.
You have to create a login page for end user and your client app will send the user's credentials as well as your client system credentials (use HTTP Basic Authentication for client system credentials) to authorization server to get the token.
There are two ways to implement it-
Using client system id and password - When calling the token endpoint using the this grant type, you need to pass in the client ID and secret (using basic auth).
curl -u 972.344.780-00:123456 "http://example.com/webapi/api/web/token?grant_type=password&username=addEndUserNameHere&password=addEndUserPasswordHere"
Using Client system ID only (no client system password) - Authorization Server should have a client setup to support this flow without any password-
Child class of AuthorizationServerConfigurerAdapter should have below code-
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("clientId")
.authorizedGrantTypes("password")
.authorities("ROLE_CLIENT")
.scopes("read");
}
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.allowFormAuthenticationForClients();
}
Now you can use below-
POST http://example.com/webapi/api/web/token?grant_type=password&client_id=my-trusted-client&scope=trust&username=addEndUserNameHere&password=addEndUserPasswordHere
Note - This flow is less secure than other Oauth2 flows and recommended for trusted client app only because user has to provide credentials to client app.
See here example
Using JWT with Spring Security OAuth2 with Angular
In this tutorial, we’ll discuss how to get our Spring Security OAuth2 implementation to make use of JSON Web Tokens.
http://www.baeldung.com/spring-security-oauth-jwt
#Configuration
#EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
.authenticationManager(authenticationManager);
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("123");
return converter;
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
}
I have a Spring Security AuthorizationServerConfigurerAdapter configuration which is supporting password and refresh_token grant types.
clients
.inMemory()
.authorizedGrantTypes("password", "refresh_token")
...;
The TokenStore i am using is JwtTokenStore, thus the refresh_token and access_token are generated as JWT as i am using the DefaultTokenServices
The question is how can i have the refresh_token generated and managed by JdbcTokenStore while the access_token still get generated and managed by JwtTokenStore?
I have thought about extending DefaultTokenServices or implementing AuthorizationServerTokenServices but i'm not sure if there is not any other way offered by the default spring-secuirty config.
Thanks!
One way, to achieve stored tokens (both access token and refresh token) and have JWT encoded tokens at the same time, is provide token store with tokenEnhancer of type JwtAccessTokenConverter.
#Bean
protected TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Bean
protected JwtAccessTokenConverter jwtTokenEnhancer() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(privateKey);
converter.setVerifierKey(publicKey);
return converter;
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("client_trusted")//...
;
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore())
.tokenEnhancer(jwtTokenEnhancer()) // <- tokens are encoded in JWT
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
With this approach you can easily revoke (or remove) refresh_token. So authorization server will not provide new access token in next refreshing token request. And information in JWT stays self contained and resource server can work without interaction with authorization server.
#Autowired
protected TokenStore tokenStore;
#RequestMapping(method = RequestMethod.POST, value = "/revoke")
public void revokeToken(#RequestParam String token) {
((InMemoryTokenStore)tokenStore).removeRefreshToken(token);
}
Here is complete example of authorization and resource server with js client: https://github.com/pufface/spring-oauth-jwt-demo
I have a REST service that uses OAuth2 authentication and that provides an endpoint to request a token with the client_credentials grant type. The application is based on Spring Boot.
So far I figured out I can request a token with something like:
#SpringBootApplication
#EnableOAuth2Client
public class App extends WebSecurityConfigurerAdapter {
#Autowired
OAuth2ClientContext oauth2ClientContext;
//...
#Override
protected void configure(HttpSecurity http) throws Exception {
// Does nothing - to allow unrestricted access
}
#Bean
protected OAuth2RestTemplate myTemplate() {
ClientCredentialsResourceDetails details = new ClientCredentialsResourceDetails();
details.setAccessTokenUri("http://localhost:8080/oauth/token");
details.setClientId("theClient");
details.setClientSecret("thePassword");
return new OAuth2RestTemplate(details, oauth2ClientContext);
}
}
#RestController
public class TestController {
#Autowired
OAuth2RestTemplate myTemplate;
#RequestMapping("/token")
private String getToken() {
return myTemplate.getAccessToken().getValue();
}
}
And it almost works, but whenever I call the /token endpoint, there's an exception:
org.springframework.security.authentication.InsufficientAuthenticationException: Authentication is required to obtain an access token (anonymous not allowed)
at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:88) ~[spring-security-oauth2-2.0.9.RELEASE.jar:na]
at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221) ~[spring-security-oauth2-2.0.9.RELEASE.jar:na]
at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173) ~[spring-security-oauth2-2.0.9.RELEASE.jar:na]
...
The exception is thrown here, but I'm not sure how I can make Spring use context authentication other than AnonymousAuthenticationToken. In fact, I don't want any authentication from the client, because anonymous is perfectly okay. How can I achieve this?