The labs on this site need JavaScript to run. The same content is written out below.
Fundamentals › Security
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
@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";
}
}
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
Struck-through text was removed from the first version of the code; highlighted text was added.