0

Spring Security: How to get started

 7 months ago
source link: https://www.pluralsight.com/resources/blog/software-development/spring-security-getting-started
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

How to get started

At the risk of stating the obvious, notice that the endpoint doesn’t require any proof of my identity (authentication) or proof of my authority (authorization). Because of that, this endpoint can’t easily adapt its behavior to or secure its information from different types of users.

Less obviously, calling this from a browser or another REST API is dubious at best. Without further defense, this application’s endpoints may be vulnerable to cross-site request forgery (CSRF), cross-site scripting (XSS), man-in-the-middle attacks (MITM), sensitive data exposure, and on and on.

      HTTP/1.1 401
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Connection: keep-alive
Content-Length: 0
Date: Mon, 27 Nov 2023 23:04:05 GMT
Expires: 0
Keep-Alive: timeout=60
Pragma: no-cache
Set-Cookie: JSESSIONID=4E5A935F1B30EBD82AE96FADD26AD23E; Path=/; HttpOnly
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
WWW-Authenticate: Basic realm="Realm"
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 0
    

That’s a lot to take in! What I hope you take away from it is a realization that security is more than just having a way to log the user in. It’s about ensuring that your application cannot be misused.

More concretely, there are three main differences:

  • The first is that the same endpoint now denies the request and returns a 401. 

  • The second is that several more headers are supplied, each fine-tuned according to security best practice. 

  • The third is that one particular header WWW-Authenticate tells us that the application is now configured to authenticate users using the HTTP Basic authentication scheme.

Notice that even if I request a non-existent endpoint like so:

... then Spring Security will also protect that endpoint with a 401 and the same set of headers.

I invite you to take a moment to realize the enormous leverage that Spring Security just gave our application. 

 With only the addition of the Spring Security module, it accepts a standards-based authentication scheme, it authorizes every request -- even ones you didn’t consider -- and it defends against the most common web application vulnerabilities.

There’s even more going on behind the scenes that isn’t apparent from looking at the response. Spring Security deploys a web application firewall, protects against timing attacks during the authentication process, securely encodes passwords and other secret information, and is compatible with the rest of the Spring ecosystem.

      2023-11-27T17:34:33.169-07:00 DEBUG 293546 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy    	: Securing GET /
2023-11-27T17:34:33.170-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy    	: Invoking DisableEncodeUrlFilter (1/16)
2023-11-27T17:34:33.171-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy    	: Invoking WebAsyncManagerIntegrationFilter (2/16)
2023-11-27T17:34:33.173-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy    	: Invoking SecurityContextHolderFilter (3/16)
2023-11-27T17:34:33.175-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy    	: Invoking HeaderWriterFilter (4/16)
2023-11-27T17:34:33.177-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy    	: Invoking CorsFilter (5/16)
2023-11-27T17:34:33.194-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy    	: Invoking CsrfFilter (6/16)
2023-11-27T17:34:33.196-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.security.web.csrf.CsrfFilter     	: Did not protect against CSRF since request did not match CsrfNotRequired [TRACE, HEAD, GET, OPTIONS]
2023-11-27T17:34:33.196-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy    	: Invoking LogoutFilter (7/16)
2023-11-27T17:34:33.196-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.s.w.a.logout.LogoutFilter        	: Did not match request to Ant [pattern='/logout', POST]
2023-11-27T17:34:33.197-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy    	: Invoking UsernamePasswordAuthenticationFilter (8/16)
2023-11-27T17:34:33.197-07:00 TRACE 293546 --- [nio-8080-exec-1] w.a.UsernamePasswordAuthenticationFilter : Did not match request to Ant [pattern='/login', POST]
2023-11-27T17:34:33.197-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy    	: Invoking DefaultLoginPageGeneratingFilter (9/16)
2023-11-27T17:34:33.197-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy    	: Invoking DefaultLogoutPageGeneratingFilter (10/16)
2023-11-27T17:34:33.197-07:00 TRACE 293546 --- [nio-8080-exec-1] .w.a.u.DefaultLogoutPageGeneratingFilter : Did not render default logout page since request did not match [Ant [pattern='/logout', GET]]
2023-11-27T17:34:33.198-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy    	: Invoking BasicAuthenticationFilter (11/16)
2023-11-27T17:34:33.198-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.s.w.a.www.BasicAuthenticationFilter  : Did not process authentication request since failed to find username and password in Basic Authorization header
2023-11-27T17:34:33.198-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy    	: Invoking RequestCacheAwareFilter (12/16)
2023-11-27T17:34:33.198-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.s.w.s.HttpSessionRequestCache    	: matchingRequestParameterName is required for getMatchingRequest to lookup a value, but not provided
2023-11-27T17:34:33.199-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy    	: Invoking SecurityContextHolderAwareRequestFilter (13/16)
2023-11-27T17:34:33.200-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy    	: Invoking AnonymousAuthenticationFilter (14/16)
2023-11-27T17:34:33.201-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy    	: Invoking ExceptionTranslationFilter (15/16)
2023-11-27T17:34:33.201-07:00 TRACE 293546 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy    	: Invoking AuthorizationFilter (16/16)
    

What you can see from the logs is that Spring Security is fundamentally a set of filters that intercept each request. Each filter either performs authentication, authorization, or defense or performs some infrastructural role.

For example, you can see in the list of filters an example of each type:

  • BasicAuthenticationFilter which authenticates the HTTP Basic scheme,

  • AuthorizationFilter which authorizes the request,

  • HeaderWriterFilter which writes secure headers like the cache control headers you saw earlier, and

  • ExceptionTranslationFilter which captures Spring Security exceptions and translates them into appropriate HTTP responses

Spring Security has a set of web filters that intercept each HTTP request by default. It also has other filters, like for intercepting method invocations, websocket messages, and RSocket requests that require your configuration. 

Whenever Spring Security is doing anything, it came originally from one of these Spring Security filters. The result of an authentication filter that succeeds is an instance of Authentication, which usually has the user’s identifying characteristics as well as the permissions Spring Security granted to that user.

In spite of all these helpful secure defaults, the main goal for most applications that use Spring Security is to get users logged in.

As I already mentioned, Spring Security switches on HTTP Basic authentication by default. The default user is user and there is no default password. That’s right, the password is generated on startup to ensure that the app can’t accidentally be deployed with a default password; another Spring Security secure default.

You can change the password by setting it in the application.properties file like so:

So, that shows that authentication works, but it doesn’t really show you how you’d use it in practice. Now, let’s connect it to something a little bit closer to reality.

Spring Security uses dependency injection like the rest of the Spring Framework as its primary configuration guiding principle. As such, if you publish a UserDetailsService bean, Spring Security will pick up that bean and inject it into the places it needs in order to have a different set of users from the default one.

For example, if you change your UserDetailsService out for a custom implementation like this one:

      @Component
public class MyUsers implements UserDetailsService {
    private final Map<String, User> users = new HashMap<>();

    public MyUsers() {
        this.users.put(“candice”, User.withUsername(“candice”).password(“{noop}password!”).authorities(“user”).build());
        this.users.put(“tobias”, User.withUsername(“tobias”).password(“{noop}password!”).authorities(“user”).build());
        this.users.put(“zee”, User.withUsername(“zee”).password(“{noop}password!”).authorities(“admin”, “user”).build());
   }

   @Override 
   public UserDetails loadUserByUsername(String username) {
       if (this.users.containsKey(username)) {
           return this.users.get(username);
       }
       throw new UserNotFoundException(“user not found”);
    }
}
    

... then Spring Security will use your bean instead with your user store. You can see here that this class knows about three users, each with a different username, password, and set of authorities (permissions).

If you wanted, you could instead connect this with Spring Data and have it pull from your user database.

The key is, notice that this isn’t doing any of the password checking for you. All you are doing is providing the set of users and letting Spring Security do all the password encoding, protection against timing attacks, and the rest.

In case you don’t want to use HTTP Basic, know that Spring Security also supports X.509, JWT, Form, CAS, OAuth/OIDC, SAML, and other authentication mechanisms that can be similarly configured.

Authentication proves who the user is. Authorization proves that they have permission to execute the request. And, as already mentioned earlier, Spring Security authorizes every request by default. The default authorization rule is that the user be authenticated. In other words, so long as the user is logged in, they can view any endpoint.

This is a good secure default, but probably not what you want. Certainly there are pages that anyone can see, even if they aren’t logged in, and there are pages that only certain people can see, like admins.

Let’s say, for example, that you have stylesheets and javascript in `/css` and `/js`, respectively. In all likelihood, those need to be available even when the user isn’t logged in. And let’s say that everything under the `/admin` directory is for administrators.

You can describe all of this in Spring Security by publishing authorization rules for each web request like so:

Here you can see that it all gets put together. When you define your authorization rules, you do so by publishing Spring Security’s set of HTTP filters. Several default filters are still configured for you, but you need to configure the AuthorizationFilter (authorizeHttpRequests) and any authentication filter you want to use (httpBasic). This declaration overrides any default authentication or authorization that Spring Security defined by default.

Now, if you try to hit an /admin endpoint with candice you get a 403:

Finally, web application defense. Spring Security provides an application firewall, CORS, secure response headers, session fixation, and CSRF support, each with its own security filter.

Most of these are configured automatically and require no further work on your part. You can disable them if you don’t want them but they usually don’t get in the way.

There are two exceptions since they both are specs that require some agreement between the client application and the REST API: CORS and CSRF.

CORS is how you can specify what kinds of headers, methods, and origin values are acceptable in XHR requests. This is important, for example, when your front-end is deployed to a different host than your backend. 

For example, you can say that your REST API will accept XHR requests from your Angular front-end, deployed to http://localhost:4200 like so:

      @Bean 
CorsConfigurationSource corsConfigurationSource() {
	CorsConfiguration corsConfiguration = new CorsConfiguration();
	corsConfiguration.setAllowedHeaders(List.of("*"));
	corsConfiguration.setAllowedMethods(List.of("GET”, “POST"));
	corsConfiguration.setAllowedOrigins(List.of("http://localhost:4200"));
	return (request) -> corsConfiguration;
}

@Bean 
SecurityFilterChain appSecurity(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .requestMatchers(“/css/**”, “/js/**”).permitAll()
            .requestMatchers(“/admin/**”).hasAuthority(“admin”)
            .anyRequest().authenticated()
        )
        .httpBasic(Customizer.withDefaults())
        .cors(Customizer.withDefaults());
    return http.build();
}
    

Spring Security is a powerful security framework that supports several forms of authentication, provides a flexible API for authorization, and automatically defends against numerous web exploits. You can get started by using Spring Initializr and including the Spring Security module. Instantly, you’ll get reasonable secure defaults that protect your application from being misused. Additionally, you’ll get curated and battle-tested APIs that allow you to customize any part of Spring Security to suit your needs.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK