10 posts
Spring Security in Action - Part 2. Implementation (Chapters 8-14)
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:
- The client sends credentials along with the endpoint.
- Then the business logic server authenticates the user on the authentication server and sends the OTP.
- Here, the authentication server authenticates the user in the database.
- 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:
- 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!