I am just a beginner in Spring Security Oauth2.
I try to make Authorization Server and Resource Server (separated and connect to JDBC) and the purpose is to make Single Sign-On.
My flow success to get accesstoken and refreshtoken from Authorization Server. My accesstoken always used as parameter to access Resouce Server, and this is give a response back.
for example http://127.0.0.1:8080/Resource/res/staff?access_token=xxxxxxxxxxxxxxxxxxxxx
My problem, if the accesstoken expired, the spring security will prevent to access the page and give the error exception. When I must use the refreshtoken to get new token?
Or is my flow wrong? Is there other flow to renew accesstoken?
Thanks
Edited:
FYI:
I want to make SSO using Spring Security Oauth2. I have several Apps Server (use Spring Framework) and I want to make one server that responsible to manage the login. And I want to make the Apps Server become Resource Server (also the Client)
So I make one Authorization Server with Spring Security Oauth2. The user who wants to access the protected Resource Server must login to Authorization Server (the Resource Server authorize to Authorization Server). It will get a code and then the Resource Server will exchange this code with accessToken and refreshToken. This flow is success.
I can also request the new accessToken using the refreshToken that given by Authorization Server.
But I cannot call this procedure because if I access the url mapping, previously the spring security was blocking the access and give the invalid token error return.
How can I solve the missing link?
Updated:
This is my Authorization Server Configuration:
#Configuration
public class Oauth2AuthorizationServer {
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends
AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
DataSource dataSource;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.tokenStore(new JdbcTokenStore(dataSource))
.authenticationManager(authenticationManager);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("isAnonymous() || permitAll()").checkTokenAccess("permitAll()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.jdbc(dataSource);
}
}
}
And this is my Resource Server Configuration (as Client too)
#Configuration
public class Oauth2ResourceServer {
private static final String RESOURCE_ID = "test";
#Configuration #Order(10)
protected static class NonOauthResources extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/halo").permitAll()
.antMatchers("/api/state/**").permitAll()
.antMatchers("/**").permitAll()
.and().anonymous();
}
}
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends
ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setClientId("jsclient");
tokenService.setClientSecret("jspasswd");
tokenService.setCheckTokenEndpointUrl("http://localhost:8084/Server2Auth/oauth/check_token");
resources
.resourceId(RESOURCE_ID)
.tokenServices(tokenService);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.filterSecurityInterceptorOncePerRequest(true)
.antMatchers("/res/staff").hasRole("STAFF")
.antMatchers("/res/client").access("#oauth2.hasScope('trust')")
.antMatchers("/res/admin").hasRole("ADMIN")
.and()
.exceptionHandling().accessDeniedPage("/403");
}
}
}
Resource (as Client too) request to authorize:
curl -X POST -vu clientauthcode:123456 http://localhost:10000/auth-server/oauth/token -d "client_id=clientauthcode&grant_type=refresh_token&refresh_token=436761f1-2f26-412b-ab0f-bbf2cd7459c4"
Feedback from Authorize Server:
http://localhost:10001/resource-server/api/state/new?code=8OppiR
Resource (as Client) exchange the code to Authorize Server:
curl -X POST -vu clientauthcode:123456 http://localhost:10000/auth-server/oauth/token -H "Accept: application/json" -d "grant_type=authorization_code&code=iMAtdP&redirect_uri=http://localhost:10001/resource-server/api/state/new"
Feedback from Authorize Server:
{
"access_token":"08664d93-41e3-473c-b5d2-f2b30afe7053",
"token_type":"bearer",
"refresh_token":"436761f1-2f26-412b-ab0f-bbf2cd7459c4",
"expires_in":43199,
"scope":"write read"
}
Resource (as Client) access the url itself
curl http://localhost:10001/resource-server/api/admin?access_token=08664d93-41e3-473c-b5d2-f2b30afe7053
Request new access toke using refresh token
curl -X POST -vu clientauthcode:123456 http://localhost:10000/auth-server/oauth/token -d "client_id=clientauthcode&grant_type=refresh_token&refresh_token=436761f1-2f26-412b-ab0f-bbf2cd7459c4"
The OAuth2 Spec has a section on refreshing access tokens. It's implemented in a pretty standard way in Spring OAuth (you just post the refresh token to the /token endpoint).
BTW for SSO you don't normally need a Resource Server. But that's a different question.
Related
I'm trying to develop resource server that provides APIs that will be consumed by various applications.
I'm using java spring boot oauth2.0 framework. The applications are web applications that are typical OpenID Connect compliant web applications that will go through the /oauth/authorize endpoint using the authorization code flow. When the authorization is granted, the authorization server returns an access token to the application. The application then uses the access token to access a protected resource (like an API).
My focus is on the Resource Server itself.The Authorization Server is external elsewhere in the cloud.
From APIs standpoint these are REST APIs that are exposed using #RestController annotation like below -
#RestController
#RequestMapping("/myapi")
Below are the properties I'm having in the application.properties in my resource server code -
spring.security.oauth2.resourceserver.jwt.jws-algorithm=RS256
spring.security.oauth2.resourceserver.jwt.issuer-uri=
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=
The main class is as follows -
SpringBootApplication
#EnableResourceServer
public class myApplication {
public static void main(String[] args) {
SpringApplication.run(myApplication .class, args);
}
}
and the class that extends ResourceServerConfigurerAdapter
#Configuration
public class OAuth2ResourceServer extends ResourceServerConfigurerAdapter
{
private static final String RESOURCE_ID = "resource-server-rest-api";
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/myapi/**").authenticated();
}
}
Whenever I'm calling the API using POSTMAN while passing AccessToken in the Authorization header, I'm getting invalid_token error.
Below is the snippet of the log -
DEBUG 25536 --- [nio-8080-exec-1] p.a.OAuth2AuthenticationProcessingFilter : Authentication request failed: error="invalid_token"
I have verified jwt token, it has correct claims and scopes.
Now question is
Is something missing in the resource server from code standpoint?
Are the application properties correct or anything more need to be added?
Does Resource Server need to interact with Authorization server at run-time? If yes, where is that specified?
I have app on spring-boot
added
endpoints.shutdown.enabled=true
endpoints.shutdown.sensitive=false
to application.properties
and try to call:
curl -i -X POST devapp583.netcracker.com:26810/shutdown
But requres Auth header instead just shutting down:
{"errorCode":403,"userMessage":"No 'Authorization' header","stacktrace":null,"remoteMessage":null}
It seems like a bug, take a look here. (If you use #EnableResourceServer)
According to the docs for 1.3 for health access restrictions, a
non-sensitive health endpoint should allow anonymous access. However,
this stops working if the #EnableResourceServer annotation is found.
When the OAuth2 resource server is enabled, even non-sensitive
endpoints require full authentication.
Also there you can find a workaround:
I did manage to work around the problem for the health endpoint by
adding the following bean definition.
#Bean
ResourceServerConfigurer resourceServerConfigurer() {
new ResourceServerConfigurerAdapter() {
#Override
void configure(ResourceServerSecurityConfigurer resources)
throws Exception {
resources.resourceId('blah')
}
#Override
void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// allow anonymous access to health check endpoint
.antMatchers("/manage/health").permitAll()
// everything else requires authentication
.anyRequest().authenticated()
}
}
}
However I have not tried it myself
I have a spring-security-oauth2 project running smoothly with a class as Authorization server.
The client-ids, user-tokens, refresh-tokens are all managed by the database.
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private static String REALM = "MY_OAUTH_REALM";
...
#Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.realm(REALM + "/client");
}
}
Everything is working fine except that i don't have any idea what the configure method is doing. Even if i remove the complete method the oauth2 process still works fine.
What is the main use of configure method in this context and what realm is it setting here?
Please help me in understanding it.
Thanks.
Purpose of configure method
AuthorizationServerConfigurerAdapter has three configure(...) methods and all three could be overridden and those serve different purposes.
In your question, you have quoted only one.
Their purpose is to provide your custom settings for Authorization Server end points, clients & security. So its up to you as how many you wish to override as there are some predefined default settings.
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// This can be used to configure security of your authorization server itself
// i.e. which user can generate tokens , changing default realm etc.
// Sample code below.
// We're allowing access to the token only for clients with 'ROLE_TRUSTED_CLIENT' authority.
// There are few more configurations and changing default realm is one of those
oauthServer
.tokenKeyAccess("hasAuthority('ROLE_TRUSTED_CLIENT')")
.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// Here you will specify about `ClientDetailsService`
// i.e. information about OAuth2 clients & where their info is located - memory , DB , LDAP etc.
// Sample code below.
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// This can be used to configure security of your authorization server itself
// i.e. which user can generate tokens , changing default realm etc - Sample code below.
// we're allowing access to the token only for clients with 'ROLE_TRUSTED_CLIENT' authority.
// There are few more configurations and changing default realm is one of those
oauthServer
.tokenKeyAccess("hasAuthority('ROLE_TRUSTED_CLIENT')")
.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// Here you will specify about `ClientDetailsService` i.e.
// information about OAuth2 clients & where their info is located - memory , DB , LDAP etc.
// Sample code below
clients.inMemory()
.withClient("trusted-app")
.authorizedGrantTypes("client_credentials", "password", "refresh_token")
.authorities("ROLE_TRUSTED_CLIENT")
.scopes("read", "write")
.resourceIds("oauth2_id")
.accessTokenValiditySeconds(10000)
.refreshTokenValiditySeconds(20000)
.secret("secret");
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// Here you will do non-security configs for end points associated with your Authorization Server
// and can specify details about authentication manager, token generation etc. Sample code below
endpoints
.authenticationManager(this.authenticationManager)
.tokenServices(tokenServices())
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter());
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("abcd");
return converter;
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setTokenEnhancer(accessTokenConverter());
return defaultTokenServices;
}
Purpose of #EnableAuthorizationServer
A javadoc explanation is already provided in previous answer.
In layman's language, this is to set up your token generation end point i.e.
if you provide the properties security.oauth2.client.client-id and security.oauth2.client.client-secret, Spring will give you an authentication server, providing standard Oauth2 tokens at the endpoint /oauth/token
In practical scenario, what this means is that you are setting up a token generation web-application ( Layer-7 ) on top of your enterprise User LDAP or User Database and is usually a separate application from your consumer side apps ( APIs etc ).
If you have a look at the JavaDoc comment for #EnableAuthorizationServer you can see that it says the following;
Convenience annotation for enabling an Authorization Server (i.e. an
AuthorizationEndpoint and a TokenEndpoint in the current application
context, which must be a DispatcherServlet context. Many features of
the server can be customized using #Beans of type
AuthorizationServerConfigurer (e.g. by extending
AuthorizationServerConfigurerAdapter. The user is responsible for
securing the Authorization Endpoint (/oauth/authorize) using normal
Spring Security features (EnableWebSecurity #EnableWebSecurity
etc.), but the Token Endpoint (/oauth/token) will be automatically
secured using HTTP Basic authentication on the client's credentials.
Clients must be registered by providing a ClientDetailsService through
one or more AuthorizationServerConfigurers.
Extending AuthorizationServerConfigurerAdapter is just used for customization of the Authorization Server. You can easily set up a functioning Authorization Server within Spring Security by Just Annotating a Bean class with #EnableAuthorizationServer
I have 2 apps running, one is resource server where I have the info that needs authentication to view the text. Then I have authorization server that gives tokens. Right now I can use postman or Insomnia, add the auth_url, token_url, client_id, client_secret and I get the token. I add the token to header and i get do a get request to my resource server using header, and it works just fine.
Now i have no idea how to implement redirection from my resource server directly. Like when I go to
localhost:9000/home
I'd like to get redirected to:
localhost:9001/login
where I login with my inmemory user then it redirects me back to localhost:9000/home and I see the message.
What would be the best way to implement a way for user to access information on localhost:9000/home. You go to localhost:9000/home, it goes to authorization server on localhost:9001, you log in with username and password. Approve the grant, and it puts you back to localhost:9000/home and then you can see the text, what was previously protected, because you didn't have token to access it.
ResourceServer.java
#SpringBootApplication
#RestController
#EnableResourceServer
#EnableOAuth2Client
public class SampleResourceApplication extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/login**").hasRole("user")
.anyRequest().authenticated();
}
#Bean
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
public static void main(String[] args) {
SpringApplication.run(SampleResourceApplication.class, args);
}
#RequestMapping("/home")
public String home() {
return "this is home";
}
}
and my properties looks like:
server:
port: 900
security:
oauth2:
client:
client-id: foo
client-secret: foosecret
access-token-uri: http://localhost:9001/auth/oauth/token
user-authorization-uri: http://localhost:9001/auth/oauth/authorize
grant-type: USER
auto-approve-scopes: true
resource:
user-info-uri: http://localhost:9001/auth/user
Let's separate the agents: You have the user (i.e. you, also know as the resource owner), the authorization server, the resource server and the client (the application that access your urls, i.e. your browser).
Normally, this happens in your situation:
When your client access the resource server, it receives a 401. Depending of your implementation, you could also directly redirect the client to your AS (using a simple redirect response).
Your AS prompts you for credentials. After validating them, it issues a token for you. You can then use this token to access the RS.
What you're trying to get (if I understand correctly) is to redirect with the token automatically. To achieve this, you can simply pass the url you tried to reach (i.e. localhost:9000/home) when you redirect to your AS at the end of step 1. Your AS hten prompts the user for credentials, generate the token, stores it as a cookie (in the case of a browser), and redirects you to the url he received (localhost:9000/home).
EDIT: what's the resulting code for the redirection.
When you get to the configure, you first check if the user is authenticated. If he is, then all's fine, but if he isn't, you must catch this event and start your redirection. This can be done using the exceptionHandling method of the chaining http:
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/login**").hasRole("user")
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint());
}
private AuthenticationEntryPoint authenticationEntryPoint() {
return new AuthenticationEntryPoint() {
// You can use a lambda here
#Override
public void commence(HttpServletRequest aRequest, HttpServletResponse aResponse,
AuthenticationException aAuthException) throws IOException, ServletException {
aResponse.sendRedirect(MY_AS_URL + "?redirect_uri=localhost:9001/home");
}
};
}
Unfortunately, I am not familiar with the Spring framework, but hopefully this helps anyways:
OAuth is an authorization protocol. It does not handle authentication (see also: "What is the difference between authentication and authorization?" on ServerFault).
If I understand you correctly, you want users to be redirected to /login when they go to /home and aren't already logged-in. This step has nothing to do with OAuth, it must be part of your application's security / firewall setup.
Please also note that there is a difference between logging in (authenticating) on the authorization server and actually granting the application the right to access your resources (authorization). First you have to prove who you are and only then can you give access to your stuff. These are two separate steps.
How do you configure Spring 4.0 and Spring Security (3.2.0) for digest authentication exclusively using javaconfig (no XML)? I am using the below configuration class, however all requests are getting denied with HTTP 401 and "Nonce should have yielded two tokens but was (... message just stops there)".
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfigurationDigest extends WebSecurityConfigurerAdapter
{
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception
{
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.authorizeRequests().antMatchers("/**").authenticated().and().addFilter(digestAuthenticationFilter(digestEntryPoint()));
}
#Override
#Bean
public UserDetailsService userDetailsServiceBean() throws Exception
{
return super.userDetailsServiceBean();
}
public DigestAuthenticationFilter digestAuthenticationFilter(DigestAuthenticationEntryPoint digestAuthenticationEntryPoint) throws Exception
{
DigestAuthenticationFilter digestAuthenticationFilter = new DigestAuthenticationFilter();
digestAuthenticationFilter.setAuthenticationEntryPoint(digestEntryPoint());
digestAuthenticationFilter.setUserDetailsService(userDetailsServiceBean());
return digestAuthenticationFilter;
}
#Bean
public DigestAuthenticationEntryPoint digestEntryPoint()
{
DigestAuthenticationEntryPoint digestAuthenticationEntryPoint = new DigestAuthenticationEntryPoint();
digestAuthenticationEntryPoint.setKey("mykey");
digestAuthenticationEntryPoint.setRealmName("myrealm");
return digestAuthenticationEntryPoint;
}
}
I am attempting to authorize on the client side by including the header:
Authorization: Digest username="user", realm="myrealm", nonce="", uri="/service?param=98", response="fcd46faf42a583499d4e7f0371171ef2", opaque=""
I am able to access the intended services if I revert this class to a HttpBasic based configuration. Is the problem with my config or with my request? Most of the above code was borrowed from another post, however I cannot get things working in this context. All of this is running within Spring Boot 0.5.0M7.
Thanks.
The request seems to be incomplete. The noonce parameter should contain a base64 encoded value according to the digest processing filter reference.
Central to Digest Authentication is a "nonce". This is a value the server generates. Spring Security’s nonce adopts the following format:
base64(expirationTime + ":" + md5Hex(expirationTime + ":" + key))
expirationTime: The date and time when the nonce expires, expressed in milliseconds
key: A private key to prevent modification of the nonce token
Spring and Patrick both describe a flow where a request is made, if nothing else to get a nonce from the server , the server provides this header
"WWW-Authenticate: Digest realm="realm", nonce="IVjZjc3Yg==", qop="auth"
in its 401 response saying "hey who are you" to the client. Using the nonce and other stuff a md5 hash is created and sent to the server. Server is now happy and processes the request. Look on the bright side you made it to step 1 and check the links for a better explaination