Basic role-based authorization
We can expand on the Spring Security configuration from Hello Spring Security to vary the access controls by URL. In this section, you will find a configuration that allows more granular control over how resources can be accessed. In the configuration, Spring Security does the following tasks:
- It completely ignores any request that starts with /resources/. This is beneficial since our images, CSS, and JavaScript do not need to use Spring Security.
- It allows anonymous users to access the Welcome, Login, and Logout pages.
- It only allows administrators access to the All Events page.
- It adds an administrator that can access the All Events page.
Take a look at the following code snippet:
//src/main/java/com/packtpub/springsecurity/configuration/
SecurityConfig.java
http.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/").hasAnyRole("ANONYMOUS", "USER")
.antMatchers("/login/*").hasAnyRole("ANONYMOUS", "USER")
.antMatchers("/logout/*").hasAnyRole("ANONYMOUS", "USER")
.antMatchers("/admin/*").hasRole("ADMIN")
.antMatchers("/events/").hasRole("ADMIN")
.antMatchers("/**").hasRole("USER")
...
@Override
public void configure(final AuthenticationManagerBuilder auth)
throws Exception{
auth.inMemoryAuthentication()
.withUser("user1@example.com").password("user1").roles("USER")
.and().withUser("admin1@example.com").password("admin1").
roles("USER", "ADMIN");
}
Notice that we do not include /calendar, the application's context root, in the Spring Security configuration, because Spring Security takes care of the context root transparently for us. In this way, we do not need to update our configuration if we decide to deploy it to a different context root.
In Spring Security 4,2, you can specify multiple RequestMatcher entries using a builder pattern that allows you to have greater control over how security is applied to different portions of your application. The first antMatchers() method states that Spring Security should ignore any URL that starts with /resources/, and the second antMatchers() method states that any other request will be processed by it. There are a few important things to note about using multiple antMatchers methods, as follows:
- If no path attribute is specified, it is the equivalent of using a path of /**, which matches all requests.
- Each antMatchers() method is considered in order, and only the first match is applied. So, the order in which they appear in your configuration file is important. The implication is that only the last antMatchers() method can use a path that matches every request. If you do not follow this rule, Spring Security will produce an error. The following is invalid because the first matcher matches every request and will never get to the second mapping:
http.authorizeRequests()
.antMatchers("/**").hasRole("USER")
.antMatchers("/admin/**").hasRole("ADMIN")
- The default pattern is backed by o.s.s.web.util.AntPathRequestMatcher, which will compare the specified pattern to an ant pattern to determine whether it matches the servletPath and pathInfo methods of HttpServletRequest. Note that query strings are ignored when determining whether a request is a match. Internally, Spring Security uses o.s.u.AntPathMatcher to do all the work. A summary of the rules is listed as follows:
? matches a single character.
* matches zero or more characters, excluding /.
** matches zero or more directories in a path.
The pattern "/events/**" matches "/events", "/events/",
"/events/1", and "/events/1/form?test=1"; it does not
match "/events123".
The pattern "/events*" matches "/events", and "/events123";
it does not match "/events/" or "/events/1".
The pattern "/events*/**" matches "/events", "/events/",
"/events/1","/events123", "/events123/456", and
"/events/1/form?test=1".
- The path attribute on the antMatchers() method further refines the filtering of the request and allows access control to be applied. You can see that the updated configuration allows different types of access, depending on the URL pattern. The role ANONYMOUS is of particular interest since we have not defined it anywhere in SecurityConfig.java. This is the default authority assigned to a user that is not logged in. The following line, from the updates to our SecurityConfig.java file, is what allows anonymous (unauthenticated) users and users with the role USER authority to access the Login page. We will cover access control options in more detail in the second half of the book:
.antMatchers("/login/*").hasAnyRole("ANONYMOUS", "USER")
When defining the antMatchers() methods, there are a number of things to keep in mind, including the following:
-
- Just as each http method is considered from top to bottom, so are the antMatchers() methods. This means it is important to specify the most specific elements first. The following example illustrates a configuration that does not specify the more specific pattern first, which will result in warnings from Spring Security at startup:
http.authorizeRequests()
…
// matches every request, so it will not continue
.antMatchers("/**").hasRole("USER")
// below will never match
.antMatchers("/login/form").hasAnyRole("ANONYMOUS", "USER")
- It is important to note that if http.authorizeRequests() is marked anyRequest(), there can be no child antMatchers() method defined. This is because anyRequest() will match all requests that match this http.authorizeRequests() tag. Defining an antMatchers() child method with anyRequest() contradicts the antMatchers() declaration. An example is as follows:
http.authorizeRequests().anyRequest().permitAll()
// This matcher will never be executed
// and not produce an error.
.antMatchers("/admin/*").hasRole("ADMIN")
- The path attribute of the antMatchers() element is independent and is not aware of the anyRequest() attribute of the http method.
If you have not done so already, restart the application and visit http://localhost:8080. Experiment with the application to see all the updates you have made, as follows:
- Select a link that requires authentication and observes the new login page.
- Try typing an invalid username/password and view the error message.
- Try logging in as an admin (admin1@example.com/admin1), and view all of the events. Note that we are able to view all the events.
- Try logging out and view the logout success message.
- Try logging in as a regular user (user1@example.com/user1), and view all of the events. Note that we get an Access Denied page.
Your code should now look like chapter02.03-calendar.