Authorization

In the Authentication section, we showed how user-provided credentials (username/password) are compared with application-stored ones, and if they match, the user is authenticated.

To boost security, we can limit the user's access to application resources. This is where authorization comes into the picture—the question of who should access which application's resources.

Spring Security provides very comprehensive authorization features. We can categorize these features into these three authorization groups:

  • Web request (who can access which application URL?)
  • Method invoking (who can call a method?)
  • Domain object access (who can see which data?)

    For example, a customer should be able to see his own order and profile data, whereas an admin should be able to see all the customers' orders plus the data that is not visible to any customer.

Since version 3.0 of Spring Security, Spring has added Spring EL expressions to its authorization features. Spring EL lets you convert complex authorization logic into simple expressions. In this section, we use Spring EL for authorization.

GrandAuthority in Spring Security is the object for including a string value that is interchangeably called an authority, right, or permission (refer to the Authentication section, where the AuthenticationProvider interface is explained, to see how GrandAuthority is created). By default, if this string value starts with the prefix ROLE_ (for example, ROLE_ADMIN), it will be considered as a user's role. So, it is also flexible enough to be used as a permission if it does not start with the prefix. Spring Security uses this object for web, method, and domain object authorization.

For web request authorization, we can limit user access based on the user's role in Spring Security, as follows (we will see later in this section how to do this in a controller):

public void configure(HttpSecurity http) throws Exception {
   http.authorizeRequests()
      .antMatchers("*.jsp").denyAll()
      .antMatchers("/", "/login").permitAll()
      .antMatchers("/user*//**").access("hasRole('USER') or hasRole('ADMIN')")
      .antMatchers("/admin*//**").access("hasRole('ADMIN')")
      .antMatchers("/accountant*//**").access("hasRole('ADMIN') or hasRole('ACCOUNTANT')")
      .failureUrl("/nonAuthorized")
      ...
      .permitAll();
}

Since we use spring MVC, we deny all URLs that end with .jsp (*.jsp) and let MVC map the URL to the JSP page. We permit anybody to have access to the login page using (.antMatchers("/", /login").permitAll()).

We limit user access to accountant resources to the admin and accountant roles (for example, antMatchers("/accountant*//**").access("hasRole('ADMIN') or hasRole('ACCOUNTANT')")). We set an error URL and forward a user to it if he fails authentication or tries to access non-authorized resources with failureUrl("/nonAuthorized").

You need to add @EnableGlobalMethodSecurity(prePostEnabled=true) to be able to apply method/domain-level authorization:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
@ComponentScan(basePackages = "com.springessentialsbook.chapter5")
public class MultiWebSecurityConfigurator {

We already described how to limit access to URLs using a configuration file. You can do the same thing in the controller's methods too:

@PreAuthorize("hasRole('ADMIN') or hasRole('ACCOUNTANT')"
@RequestMapping(value = "/accountant", method = RequestMethod.GET)
public String dbaPage(ModelMap model) {
...
}

For method-invoking authorization, you can configure Spring Security at the method level and define who can run a particular method in your application's service layer:

@PreAuthorize("hasRole('ADMIN') or hasRole('ACCOUNTANT')"
)
public void migrateUsers(id){...};

For domain object access, you can apply method-invoking authorization and have a service method to fine-tune who can see which data in the application. For example, in the service layer, you can limit access if the username parameter is equal to the logged-in username or the user has an admin role (refer to bussinessServiceImpl in the code):

@PreAuthorize("@businessServiceImpl.isEligibleToSeeUserData(principal, #username)")
@RequestMapping("/userdata/{username}")
public String getUserPage(@PathVariable String username,ModelMap model) {
  {...}

The OAuth2 Authorization Framework

The OAuth2 Authorization Framework is simply a way to let third-party applications access your protected resources without you sharing your user credentials (username/password). You will have faced this situation when a website such as LinkedIn asks you to share your e-mail contacts, and when you agree, you are forwarded to your mail provider's login page (for example, Yahoo!).

When you log in, the mail provider asks for your permission to share your contacts with LinkedIn. Then, LinkedIn can get the list of your contacts in order to send them an invitation.

OAuth2 relies on the following entities:

  • The resource owner: This is the user with protected resources, for example, a Yahoo! e-mail user
  • The client or third-party application: This is an external application that requires access to the owner's protected resources, for example, LinkedIn
  • The authorization server: This server grants access to the client/third party after authenticating the resource owner and obtaining authorization
  • The resource server: This server hosts the owner's protected resources, for example, the Yahoo! server

Many leading providers (for example, Google and Facebook) have both authorization and resource servers.

This diagram illustrates how the OAuth2 framework works in a simple form:

The OAuth2 Authorization Framework

Spring facilitates the OAuth2 framework by reusing Spring Security concepts for authentication and authorization and includes new features to implement authorization and resource servers. To use Spring OAuth2 in your project, you need the following dependency:

<dependency>
   <groupId>org.springframework.security.oauth</groupId>
   <artifactId>spring-security-oauth2</artifactId>
   <version>2.0.8.RELEASE</version>
</dependency>

What we explained in the Authentication section with respect to validating the user and protecting resources remains the same here. The new things are the authorization and resource server settings.

The OAuth 2.0 service includes authorization and resource servers. Spring Security lets you have separate applications as authorization and resource servers, on which one authorization server could be shared by one or many resource servers, or have both types of servers in a single application. For simplicity, we implement authorization and resource servers within the same application.

In the class MultiOAuth2ResourceAndAuthorizationConfigurator, we define resource and authorization servers. @EnableResourceServer tags the class ResourceServerConfiguration as a resource server, which defines resources with the URL /public as non-protected and ones with the /protected/** URL as secure resources that require a valid token to access.

@EnableAuthorizationServer tags AuthorizationServerConfiguration as an authorization server that grants tokens to third-party clients. TokenStore is a Spring interface; its implementation classes (InMemoryTokenStore, JdbcTokenStore, and JwtTokenStore) keep track of tokens.

JdbcTokenStore uses a database to store tokens and has a Spring-JDBC dependency. JdbcTokenStore is suitable when you want to have a history of tokens, recovery after server failure, or the sharing of tokens among several servers.

JwtTokenStore encodes token-related data into the token itself. JwtTokenStore does not make tokens persistent and requires JwtAccessTokenConverter as a translator between a JWT-encoded token and OAuth authentication information.

For simplicity, we use the InMemoryTokenStore implementation class, but in real applications, using JdbcTokenStore/JwtTokenStore is a better practice.

We reuse the AuthenticationManager class that was detailed in the Authentication section.

The method configure(ClientDetailsServiceConfigurer clients) is the location in which we configure token generation settings, as follows:

  • withClient tells us which client can access resources (this is separate from user authentication)
  • secret is the client's password
  • authorities tells us which user roles are eligible to access the resource
  • authorizedGrantType specifies which grant type the client has (for example, the refresh and access token)
  • accessTokenValiditySeconds sets the token's time to live

The settings are mentioned in the following code:

@Configuration
public class MultiOAuth2ResourceAndAuthorizationConfigurator {
    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                .headers()
                .frameOptions().disable()
                .authorizeRequests()
                .antMatchers("/public/").permitAll()
                .antMatchers("/protected/**").authenticated();
        }
    }
    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter implements EnvironmentAware {
        private static final String  CLIENT_ID = "myClientId";
        private static final String  CLIENT_PASSWORD = "myClientPassword";
        private static final int  TOKEN_TIME_TO_LIVE = 1800;
        private static final String  ROLE_USER = "ROLE_USER";
        private static final String  ROLE_ACCOUNTANT = "ROLE_ACCOUNTANT";
        private static final String  READ_ACCESS = "read";
        private static final String  WRITE_ACCESS = "write";
        private static final String  GRANT_TYPE_PASSWORD = "password";
        private static final String  GRANT_TYPE_REFRESH_TOKEN = "refresh_token";
        @Bean
        public TokenStore tokenStore() {
            return new InMemoryTokenStore();
        }
        @Autowired
        private AuthenticationManager authenticationManager;
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints
                .tokenStore(tokenStore())
                .authenticationManager(authenticationManager);
        }
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients
                .inMemory()
                .withClient(CLIENT_ID)
                .secret(CLIENT_PASSWORD)
                .scopes(READ_ACCESS, WRITE_ACCESS)
                .authorities(ROLE_USER, ROLE_ACCOUNTANT)
                .authorizedGrantTypes(GRANT_TYPE_PASSWORD, GRANT_TYPE_REFRESH_TOKEN)
                .accessTokenValiditySeconds( TOKEN_TIME_TO_LIVE);
        }
        @Override
        public void setEnvironment(Environment environment) {
        }
    }
}

The resources we granted access to using the token are included in a controller. Here, we define a very simple resource:


@RequestMapping(value = "/protected", method = RequestMethod.GET)
@ResponseBody
public String getProtectedResources(ModelMap model) {
   return "this is from getProtectedResources";
}
@RequestMapping(value = "/public", method = RequestMethod.GET)
@ResponseBody
public String getPublicResources(ModelMap model) {
   return  "this is from getPublicResources";
}

You can run the project with the following command, which builds and runs the resource and authorization server:

mvn clean package spring-boot:run -Dserver.contextPath=/myapp -Dserver.port=9090

If you try the following, you can see the resource because this URL is unprotected:

curl -i http://localhost:9090/myapp/public

However, if you try the next command, you get a "non-authorized" error and you need a valid token to access this resource:

curl -i http://localhost:9090/myapp/protected

You need to get a token first to be able to access protected resources. Spring MVC exposes an endpoint, TokenEndpoint, in order to get the token with the /oauth/token URL by default. The following command gives you an authorization token:

curl -X POST -vu myClientId:myClientPassword  'http://localhost:9090/myapp/oauth/token?username=operator&password=password&grant_type=password'

Now, you can provide the token and access the secure resource:

curl -i -H "Authorization: Bearer [access_token]" http://localhost:9090/myapp/protected

Notice that we set a time to live for the token and we need to refresh the token if it expires. The following command renews the token by calling the /oauth/token endpoint and passing refresh_token as the grant_type parameter:

curl  -X POST  -vu  myClientId:myClientPassword  'http://localhost:9090/myapp/oauth/token?grant_type=refresh_token&refresh_token=[refresh_token]'
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset