Dev Learning Lab

The labs on this site need JavaScript to run. The same content is written out below.

Fundamentals › Security

Authentication and authorization

How are authentication and authorization different?

Authentication and authorization are two checks a server makes when it receives a request. Authentication checks who sent the request (whether the username and password are right), and authorization checks whether that person is allowed to do this (whether they have permission). Authentication comes first. These checks are not in the controller's code. Spring Security makes them before the request reaches the controller, and the rules for them are the settings part at the top of the screen. The line .requestMatchers("/admin").hasRole("ADMIN") is an authorization rule that opens /admin only to someone with the ADMIN role, the line .anyRequest().authenticated() is a rule that opens any other address only to someone whose identity is confirmed, and the line .httpBasic(...) is the method that confirms who it is from the username and password carried in the request. In this lab you remove or open the authorization rule and send /admin requests from the administrator admin, the regular member kim, a wrong password, and no username, and you see at which check the request is blocked and what response goes out.

Checked against real execution · Sent HTTP Basic requests for 12 combinations to a real server running Spring Boot 4.1.1 and Spring Security 7.1.1 and checked them

Concepts in this lab

Authentication and authorization
Authentication checks who a user is. Authorization decides whether that verified user has permission to use a specific resource or feature. Authentication happens first, and authorization is decided based on its result. In HTTP status codes, a failed authentication corresponds to 401 and a failed authorization corresponds to 403.
401 Unauthorized and 403 Forbidden
401 and 403 are HTTP status codes that a server uses when it rejects a request, and the reasons for rejecting are different. 401 (Unauthorized) means the request lacks valid authentication credentials for the target resource, and the server must send a WWW-Authenticate header in the response to tell how to authenticate. 403 (Forbidden) means the server understood the request but refuses to handle it, and if the request carried credentials, the client must not automatically repeat the request with the same credentials. When a request whose identity is not confirmed is blocked, Spring Security responds according to the authentication setup (with HTTP Basic it sends a 401, with form login it redirects to the login page), and it sends a 403 when a user whose identity is confirmed is blocked for lack of permission.
Authority (role)
An authority is a label for what an authenticated user is allowed to do, and a role is one use of an authority, such as ADMIN or USER, that stands for the position the user holds. In Spring Security, the authorities a user has are held in the Authentication as GrantedAuthority objects. hasRole("ADMIN") is a shorter form of hasAuthority: it puts the prefix ROLE_ in front of the name and checks whether the user has the authority ROLE_ADMIN. An authority has meaning only after it is confirmed who the user is, so you cannot weigh authorities for a request that has not gone through authentication.
HTTP Basic authentication
HTTP Basic is an HTTP authentication method that confirms who the user is by sending a username and password in the Authorization header of the request. When the server rejects a request that came without credentials for a resource that needs a username and password with a 401 and tells the client about the Basic method in the WWW-Authenticate header, the client sends the request again with an Authorization header. Spring Security turns this method on with the httpBasic setting, and if the username and password in the header match a registered user, it remembers that user as the authenticated user, and if they do not match, it rejects the request with a 401. Because it sends the username and password with every request, it should be used over an encrypted connection (HTTPS) to be safe. A login page, sessions, and tokens are separate methods.
Spring Security and SecurityFilterChain
Spring Security is Spring's security framework for handling authentication and authorization, and it works as a set of Servlet filters. A SecurityFilterChain defines which security filters apply to which requests and in what order. Before a request reaches the controller, the filters run one after another and do work such as checking the authentication information and checking permissions.

Try these

Running with the default settings

  1. A GET /admin request (a request asking the server for the page at the /admin address) arrives at the server. The Authorization header of the request carries the username admin and the right password. The username and password arrive in this header, and the server does the checking.
  2. Before the request reaches the controller, Spring Security receives it first. Spring Security is a line of filters placed in front of the controller, and it checks two things in order to decide whether to pass this request on to the controller. Nothing has reached the admin() method that we wrote yet.
  3. Authentication (who is it?): the username admin and the password in the request are compared with the registered users. They match. The server has confirmed that the sender of this request is admin (administrator), and it remembers this while the request is being handled. This is done by the method that the .httpBasic(...) line turns on.
  4. Authorization (is it allowed?): The rule for /admin is hasRole("ADMIN"), so only someone with the ADMIN role gets through. The role of admin is ADMIN. It passes.
  5. It passed Spring Security, so the request reaches the controller and the admin() method runs. There is not a single line in the admin() we wrote that checks who it is. Spring Security finished the check before the request got here.
  6. The browser receives a 200 response. The body is "Admin page".

Result of this run

Code

@Configuration
public class SecurityConfig {
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(auth -> auth
                // Authorization: /admin only for someone with the ADMIN role
                .requestMatchers("/admin").hasRole("ADMIN")
                // Authorization rule: any other address is allowed only if the identity is confirmed (authenticated)
                .anyRequest().authenticated())
            // Confirm who it is from the username and password in the request
            .httpBasic(Customizer.withDefaults());
        return http.build();
    }
}

@RestController
public class AdminController {
    @GetMapping("/admin")
    public String admin() {
        return "Admin page";
    }
}

Assumptions of this model

These are the results of running Spring Boot 4.1.1 (Spring Framework 7.0.9, embedded Tomcat 11) and Spring Security 7.1.1 on JDK 21. For all twelve combinations, we compiled the same source as the code on the screen (the security configuration SecurityConfig and the controller AdminController, to which we added only a line that counts how many times it runs) in the package app, started a real server as a child Java process, and sent a real HTTP request (GET /admin) to check. The username and password were sent in the Authorization header, and we started a new server for every request. What we left out of the screen are the package and import lines, the start class, and the user registration. The users are a regular member kim (role USER) and an administrator admin (role ADMIN) in an in-memory user store (InMemoryUserDetailsManager), and the passwords carry the {noop} prefix and are unencrypted values for testing (in a real service, passwords are stored encrypted). We added nothing to the security configuration beyond the lines on the screen. CSRF and session settings are the defaults, and because the request is a GET they did not affect the result. "Who the server confirmed" was obtained from which Authentication was recorded in SecurityContextHolder while the request was being handled. We changed the storage strategy of SecurityContextHolder to a recording wrapper around the default one, which wrote the name and the authorities each time it was read or written, so the authentication information of requests that did not reach the controller could also be seen, and we added nothing to the security filter chain. The limit is that we see what happened inside the filters only at the points of reading and writing (we did not see the internal decisions of the filters themselves). What we checked against was the confirmed person obtained from this record (anonymous, or nothing set, when there is none), the status code and body, whether the WWW-Authenticate header was present, the number of times the admin() method ran, the order in which authentication happens before authorization, and whether the error response is forwarded once more to /error. What the documents decide is section 11 HTTP Authentication of RFC 9110 (11.1, 11.6.1), section 15.5.2 (401 means there are no valid authentication credentials, and WWW-Authenticate must be sent), section 15.5.4 (403 means the server understood the request but refuses to handle it), the Spring Security documentation Authorize HttpServletRequests (AuthorizationFilter looks at the rules in order from the top and applies only the first one that matches, hasRole adds the ROLE_ prefix, and AccessDeniedException is thrown when the request is denied), and the Architecture and Basic sections of Authentication (when an unauthenticated request is denied, AuthenticationEntryPoint sends the WWW-Authenticate header, and when authentication fails, SecurityContextHolder is cleared and the same handler is called). We opened the documents with WebFetch and checked them against the summarized results, and we could not read the RFC in full. What follows is not decided by the documents but is behavior we observed in this version. First, when the code on this screen is run as it is, a 403 goes out as it is for the request from the regular member kim. The error response is forwarded once more to /error, and in that second request too kim was recorded as a confirmed person and was not blocked. In the "Security filter chain" lab, which authenticates with a filter we made ourselves without using a session, the 403 at the same step turned into a 401, so it may differ depending on the configuration. Second, a wrong password gets a 401 even if /admin is permitAll(). A request that carries a username and password was rejected at authentication before the authorization rules were looked at, and there was no trace of the authorization step in the record. Third, a request with no username is not blocked at authentication but goes on as an anonymous user to the authorization step, where it gets a 401 if the rule blocks it and reaches the controller if the rule is open. Fourth, the 401 response carries the WWW-Authenticate header (the value is Basic realm="Realm"), and the 403 and 200 responses did not. Fifth, the response body of the 401 was empty and that of the 403 was Spring Boot's error page, and this can differ depending on the Accept header of the request, so the screen deals only with status codes. Sixth, in Spring Security 7.1, along with the ROLE_ authorities, an authority that indicates how the user authenticated (FACTOR_PASSWORD) is also recorded among the authorities of the logged-in user, but this lab does not cover it. We did not cover login methods (a login page, sessions, tokens). The requests in this lab use HTTP Basic, which sends the username and password with every request, and sessions and tokens that keep the logged-in state are covered in the Fundamentals labs "Sessions" and "Tokens" and in the study lab "Security filter chain". The real filter chain has more filters than are written here.

Previous lab: Proxy servers · Next lab: Sessions

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these