Spring Security in Action - Part 2. Implementation (Chapters 8-14)

· Tech· Spring
Spring

Chapter 8. Configure Authorization: Enforce Restrictions

Selecting an endpoint with a selector method Selector methods provided by Spring Security include MVC selector, Ant selector, and regular expression selector.

Too often novice developers use the dangerous approach of copy-paste programming without even knowing what selectors they are using. Don't use it until you understand how it works!

MVC selector

The book recommends choosing MVC over Ant selectors to avoid risks due to mapping.

http.authorizeRequests()
.mvcMatchers(HttpMethod.Post, "/hello").authenticated()
// 길이와 관계없이 숫자를 포함하는 문자열을 나타내는 정규식
.mvcMatchers("/reg/{code:^[0-9]*$*}").permitAll()
.anyRequest().authenticated();

Ant Selector

Unlike the MVC selector, if you set the endpoint of the /hello route in a way that does not take Spring MVC's behavior into account, the /hello/ route is not protected. In other words, a clear route setting is required.

http.authorizeRequests()
.antMatchers("/hello/#### ").hasRole("ADMIN")
.anyRequest().authenticated();

Regular expression selector

Online regular expression selector It is recommended to use in cases where MVC and Ent cannot be used to solve the problem.

http.authorizeRequests()
.regexMatchers(".*/(kr|ca)+/(en).#### ").authenticated()
.anyRequest().authenticated();

If you call an endpoint with incorrect credentials, even if you are authorized, if authentication fails at the authentication filter first, the response status does not go through to the authorization filter.

HTTP 401 Unauthorized

returns . Spring Security enforces protection against Cross-Site Request Forgery (CSRF) by default, which can be disabled for all endpoint calls.

http.csrf().disable();

Chapter 9. Filter Implementation

You can add custom filter chains beyond the BasicAuthenticationFilter filter provided by Spring Security. It becomes possible to build event or logging filters before permission filters. There is already a predetermined order among the filters provided by Spring.

CorsFilter

CsrfFilter

BasicAuthenticationFilter

This is the order.

Implementing custom filters####

public class RequestVaildationFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
var httpRequest = (HttpServletRequest) request;
var httpResponse = (HttpServletResponse) response;
String requestId = httpRequest.getHeader("Request_id");
if(requestId.isBlank){
httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return;
}
filterChain.doFilter(request, response);
}
}

Custom filter ordering

Like this

addFilterBefore

, you can use RequestVaildationFilter before BasicAuthenticationFilter. If you are creating a filter that records authentication passes, you can place it after BasicAuthenticationFilter.

addFilterAfter

You can use .

http.addFilterBefore(
new RequestVaildationFilter(),
BasicAuthenticationFilter.class)
.authorizeRequests().anyRequest().permitAll();
)

So how do I add a filter to another filter location? To place it in BasicAuthenticationFilter

addFilterAt

You can use . This should be viewed as an additional concept rather than a filter replacement. The book provides the following three examples of such scenarios.

  • Authentication based on static header values for identification
  • When authenticating by providing a simple string rather than a cryptographic signature
  • Sign authentication requests using symmetric keys
  • When the client and server share a key
  • Use OTP for authentication process
  • When using application multi-factor authentication by obtaining OTP from a third-party authentication server

In real world scenarios, you should use a secret vault to store your details. To disable the UserDetailsService configuration:

exclude

You can use the characteristics.

@SpringBootApplication(exclude={UserDetailsServiceAutoConfiguration.class})

OncePerRequestFilter

It is good to use the filter provided by Spring Security, but if there is a way to implement it as simply as possible, you should avoid simply extending it. It is recommended to implement filters using the OncePerRequestFilter class to allow for one filter call on the same request. OncePerRequestFilter casts the types to receive requests directly as HttpServletReqeust and HttpServletResponse. Filters can directly manipulate requests and responses to do what you need them to do. Using these objects directly increases independence and flexibility by eliminating the need for filters to rely on external libraries or services. By default, it does not apply to asynchronous requests or error dispatch requests. But you can make it possible by overriding the method. You can also determine whether a filter will be applied by overriding the method.

Chapter 10. CSRF protection and CORS enforcement

Apply Cross-Site Request Forgery (CSRF) protection

Change calling pages, including POST, PUT, and DELETE, must receive a CSRF token in response and use it in the next call. Here, a CSRF attack occurs when a user opens a page with a malicious script, which then takes action on the user's behalf.

CsrfFilter

This filter allows all HTTP requests using GET, HEAD, TRACE, and OPTIONS and responds with a CSRF token. In order to develop on POST, PUT, and DELETE endpoints, a token is required if CSRT protection is enabled.

_csrf

Because it is added to the characteristics, it is output in the response or using thymeleaf. CSRF protection is used for web apps that run in the browser, unless there are no changing actions performed within the app, such as logging in. And although it works well in an architecture where the same server handles the front and back, when the solutions are independent, the corresponding security approach (Chapters 11 to 15) must be considered. Spring security basically supports CSRF protection, but it is used only when pages that use resources created on the server are created on the same server. So what if you want to set up protection only on some paths rather than all paths? CSRF protection like the code below:

disable

Rather than processing it, you can specify a path to exclude protection using the provided handlers and matchers.

http.httpBasic(HttpBasicConfigurer::disable)
.csrf(CsrfConfigurer::disable)

Storing csrf tokens in server-side sessions is not suitable for horizontal scaling. To switch to managing tokens in a database,

CsrfTokenRepository

You can implement a new one and manage it using session ID.

CsrtTokenRepository

If you create an implementation, it will be overridden

generateToken()

and

saveToken()

,

loadToken()

You implement the method and use it. Another alternative is to set and manage the token expiration time.

Using CORS (Cross-Origin Resource Sharing)

CORS allows requests between different origins under some conditions. For example

domain.com

in

domain2.com

in

domain.com

When calling a REST endpoint, the call is rejected. CORS can be used to solve this problem. +

Access-Control-Allow-Origin

Specify accessible external domains +

Access-Control-Allow-Methods

Allow access to other domains but only specific endpoints +

Access-Control-Allow-Headers

Add restrictions to the headers available for specific requests The concept should not be confused with CSRF. CORS is a concept that mitigates cross-domain calls and is about the browser and not a way to protect endpoints. CSRF Boch is a concept that prevents attacks. There are cases where a pre-test request is made using HTTP OPTIONS. If this request fails, the browser will not accept the original request. CSRF's policy is

@CrossOrigin

It can be applied. When applied to a method as follows, the method will allow cross-origin requests to the localhost origin: At this time *

If you allow a wide range of origins, you may be exposed to cross-site scripting (XXS) requests, making you vulnerable to DDoS attacks.

@CrossOrigin("http://localhost:8080")

However, if individual management is done this way, it is difficult to manage, so

CorsConfigurationSource

By defining the sources and methods to be allowed,

cors()

Can be applied to .

Chapter 11. In Practice: Separation of Responsibilities

Authentication logic

The order of how to handle OTP token authentication is listed in the order presented in the book:

  1. The client sends credentials along with the endpoint.
  2. Then the business logic server authenticates the user on the authentication server and sends the OTP.
  3. Here, the authentication server authenticates the user in the database.
  4. And send the OTP to the client. It can be said that it is wrong for the business logical server and the authentication server to exchange OTP with each other, but the book says that it is set up this way to explain with an easy implementation example.

Token

To access the endpoint, authentication is performed by inserting a string called a token (UUID or JWT, etc.) in the header. And you can access application resources.

  • No need to share credentials with every request.
  • You can manage it by specifying its lifespan.
  • Details can be stored in the token. JWT(JSON Web Token) Refers to a token containing the JSON data format. It consists of three Base64 encodings: header, body, and signature, separated by periods. Java supports the JJWT library to easily create JWT tokens. Instructions for use in JJWT github overview,

builder()

processing,

claims

,

parser()

, you can learn about key processing methods.

Chapter 12. How OAuth 2 works

OAuth 2 is also known as an authorization framework and a delegation protocol. In HTTP Basic, a problem arises in the authentication method, where credentials must be sent for every request and must be managed by a separate system. This means that credentials are frequently shared across the network and become less secure. OAuth 2 consists of resource server, user, client, and authorization server. The resource owner sends a request to the application and receives proof from the authorization server that it has received permission to operate on the data. Then, this proof is passed to the resource server so that the client can use the data. When the authorization server provides the client with an access token, it calls the client with the redirect URI.

사용자 - 클라이언트 - 권한 부여 서버 - 리소스 서버

OAuth 2 grant types In OAuth 2, the grant type is the most used type and the flow is as follows:

Approval Code Grant Type

  • Perform authorization request with authorization code grant type
  • User can create an endpoint

````response_type```

`,

client_id

,

redirect_uri

,

scope

,

state

Call it with a query containing .

  • When authentication is successful, the client is called with a redirect URI and a code and status value are provided.
  • Get access token with authorization code grant type
  • Receive torque from the authentication server with the authorization code.
  • At this time, the request includes

code

,

client_id

,

client_secret

,

redict_uri

,

grant_type

It contains details such as:

  • Calling protected resources with authorization code grant type

Password Grant Type

Unlike the authorization code grant type, this is used when the client and authorization server are built by the same organization. It's almost similar, but without the process of receiving an authorization code, the client can

grant_type

,

client_id

, client_secret

,

scope

,

username

,

password

Send your details and receive an access token. And then call the resource with this token. That is, in an environment where the resource owner trusts the client.

Client Credential Grant Type

This refers to a way to implement authentication between two applications without user involvement.

grant_type

,

client_id

,

client_secret

,

scope

This method sends a request to the authorization server, receives an access token, and calls the resource server.

Using update token

Password grant types require the user to re-authenticate or store their credentials. On the other hand, if there is a refresh token, the security risk is reduced and it is returned along with the access token by the authorization server. This is unnecessary for client credential grant types because user credentials are not required. OAuth 2 is a framework, so you need to know its features and implement them properly to mitigate application vulnerabilities.

Implementing a simple single sign-on (SSO) application

You can implement the authorization code grant type using GitHub as an authorization server. First, you need to register a GitHub OAuth application. Add dependency

  • spring-boot-starter-oauth2-client
  • spring-boot-starter-security
  • spring-boot-starter-web Security Config settings include:

http.oauth2Login()

It can be implemented using .

formLogin()

Add a new authentication filter to the filter chain, like OAuth2LoginAuthenticationFilter. ClientRegistration instance

ClientRegistration cr =
ClientRegistration.withRegistrationId("github")
.clientId("ae4a6f5a46fe6af5vs")
.clientSecret("1f6sadf86a5wef364f51dcs8ae4615")
.scope(new String[]("read:user"))
.authorizationUri("https://github.com/login/oauth/authorize")
.tokenUri("https://github.com/login/oauth/access_token")
.userInfoUri("https://api.github.com/user")
.userNameAttributeName("id")
.clientName("Github")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}")
.build();

Already in Spring Security

withRegistrationId()

Authentication for general providers is supported using ClientOAuth2Provider as follows so that you can set it up without having to use .

ClientRegistration cr =
ClientOAuth2Provider.GITHUB
.getBuilder("github")
.clientId("a4f6a5e3ws48d615a")
.clientSecret("a1465eafw184651waf1aw4615waw")
.build();

At the time of writing, I don't know why spring security doc still describes spring boot 2.x...?! After reading this book, I felt the need to update the oauth 2 configuration sample code and link based on Spring Boot 3.x, so I will explain it based on the book first;; Implementing ClientRegistrationRepositroy Similar to UserDetailsService, spring security provides InMemoryClientRegistrationRepository, which is an implementation of ClientRegistrationRepositroy. Register as empty or

oauth2login()

You can register using a Customizer object as a method parameter.

The steps for implementing a full SSO application are as follows:

  1. Register the OAuth application,

client_id

,

client_secret

Receive 2. Add dependencies 3. Apply filter for OAuth 2 with Security Configuration 4. Implement a ClientRegistration instance 5. Implement ClientRegistrationRepositroy 6. Get authenticated user details from OAuth2AuthenticationToken

Chapter 13. OAuth 2: Implementing an authorization server

Implement an authorization server based on the OAuth 2 grant type learned earlier. In general, this does not apply to web applications that use authorization servers such as GitHub or Google, but if you implement your own authorization server, you can implement a customized authorization server.

  • Approval Code Grant Type
  • Password grant type
  • Client Credential Grant Type Currently, Spring security provides functions related to login, client, and resource server in relation to OAuth 2. Related Link The book covers how to implement a custom authorization server from where spring security oauth 2's dependencies were deprecated. (You can also choose tools like Keycloak or Okta as alternatives.)

Implementing a custom authorization server

Use @EnableAuthorizationServer to prepare the configuration for configuring an authorization server. The authorization server also needs its own credentials, so it stores credentials for users and clients and grants authorization only if the client credentials are registered. You can configure ClientDetails in memory. This provides a similar function to using UserDetailsService, but in reality, it is preferable to implement it using a database rather than memory.

clients.inMemory()
.withClient("client")
.secret("secret")
.authorizedGrantTypes("password")
.scope("read");

To enable the Authorization Code grant type, you must allow the client to obtain an access token using an authorization code provided to it. That is, the authorization server must be able to return a redirection URI and authorization code to the client. It is recommended that the implementation prevent clients from all obtaining access tokens from the authorization server with the same credentials. The reason is that if you obtain one token, you run the risk of hacking all other clients as well. When to use the refresh token type:

authorizedGrantTypes()

to

refresh_token

You must enter the type.

Chapter 14. OAuth 2: Resource Server Implementation

When a user attempts to access a resource server, the resource server verifies that the access token passed by the user is correct. There are various token verification methods used at this time, including having them reviewed by an authorization server, referencing a resource server's database, and using token signatures.

Since this part also differs from the current spring security (@EnableResourceServer support has been discontinued, etc.), we decided to focus on only the major trends.

Verify remotely

In some cases, the resource server implements token validation by calling the authorization server directly. The resource server passes the token received from the client to the authorization server and obtains the user details. The downside is that you always have to call the authorization server whenever you have a new token.

Database reference method

As with the above method, there is no need to access the authorization server every time, but the disadvantage is that a shared database must be added and a bottleneck may occur. The authentication filter intercepts the HTTP request, verifies the token in the TokenStore, and retrieves the user details. We store these details in the security context.

Comments

No comments yet. Be the first!

    164 posts in 테크

    15 / 164