Knowledge Transfer

Ethickfox kb page with all notes


Project maintained by ethickfox Hosted on GitHub Pages — Theme by mattgraham

Spring Security

It provides several essential security features like l, authorization, rolebasedaccesscontrolusingsprings,  remembers the password, URL protection, concurrent active sessions management, etc. To enable Spring security in Java Web application, you need to configure three things -  declare a delegating proxy filter in web.xml, add the ContextLoaderListener in web.xml and provide actual security constraints on applicationContext-Security.xml file. Since Spring security uses a chain of filters to implement various security constraints, also known as Spring's "security chain filter", it relies on web container for the initialization of delegating filter proxy.

Salting

Salt is random data that is combined with a password before password hashing. This makes a dictionary attack more difficult. This process is known as salting. The hashed version of the password is then stored in a database along with salt.

Btw, some hashing algorithms are not suitable for password hashing, if salt is too small or predictable it's possible to recover passwords by matching random words with salt then comparing the hashed version of output with the data stored in the database.

howtoenablesp includes password hashing out of the box. Since version 3.1, Spring Security automatically takes care of salting too. You can use PasswordEncoder implementation to implement password hashing in Spring security. The two important implementations of the new PasswordEncoder interface are BCryptPasswordEncoder and the confusingly named StandardPasswordEncoder based on SHA-256. The BCrypt implementation is the recommended one.

Delegating filter proxy

The delegating filter proxy is a generic bean that provides a link between web.xml and application-Context.xml. Spring security uses filters to implement several security related cross-cutting concerns like authentication and authorization.

Since filters need to be declared in the web.xml so that the Servlet container can invoke them before passing the request to the actual Servlet class.

The Spring Security framework uses a chain of filters to apply various security concerns like intercepting the request, detecting (absence of) authentication, redirecting to the authentication entry point, or pass the request to authorization service, and eventually let the request either hit the servlet or throw a security exception (unauthenticated or unauthorized).

Untitled 60.png

Untitled 1 18.png

The DelegatingFitlerProxy glues these filters together and forms the security filter chain. That's why you see the name "springSecurityFilterChain" when we declare DelegatingFilterProxy as a filter in web.xml.

Here are some of the important filters from Spring's security filter chain, in the order they occur in:

You can add or replace individual filters with your own logic in Spring's security filter chain.

SecurityContext

The SecurityContext is used to store the details of the currently authenticated user, also known as a principle. So, if you have to get the username or any other user details, you need to get this SecurityContext first.

SecurityContextHolder

The SecurityContextHolder is a helper class, which provides access to the security context. By default, it uses a ThreadLocal object to store security context, which means that the security context is always available to methods in the same thread of execution, even if you don't pass the SecurityContext object around. Don't worry about the ThreadLocal memory leak in web application though, Spring Security takes care of cleaning ThreadLocal.

Object principal = SecurityContextHolder.getContext()
                                        .getAuthentication()
                                        .getPrincipal();

if (principal instanceof UserDetails) {
  String username = ((UserDetails)principal).getUsername();
} else {
  String username = principal.toString();
}

Untitled 2 13.png

If you ever need to know current logged-in user details like, in Spring MVC controller, I suggest you declare a dependency and let Spring provide you the Principal object, rather you querying for them and create a tightly coupled system.

@Controller
public class MVCController {

  @RequestMapping(value = "/username", method = RequestMethod.GET)
  @ResponseBody
  public String currentUserName(Principal principal) {
     return principal.getName();
  }

}

// OR

@Controller
public class SpringMVCController {

  @RequestMapping(value = "/username", method = RequestMethod.GET)
  @ResponseBody
  public String currentUserName(Authentication authentication) {
     return authentication.getName();
  }
}

Limiting number of concurrent sessions

You will need to include the following xml snippet in your Spring Security Configuration file mostly named as applicaContext-security.xml. You can name the file whatever you want but just make sure you use the same name in all relevant places

<session-management invalid-session-url="/logout.html">
    <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>

Authentication

Проверка подлинности путем проверки введенных имени и пароля с сохраненными в базе

Authorization

Проверка разрешений на доступ к тому или иному ресурсу

Untitled 3 13.png

Untitled 4 10.png

Method Security

Simply put, Spring Security supports authorization semantics at the method level.

Typically, we could secure our service layer by, for example, restricting which roles are able to execute a particular method — and test it using dedicated method-level security test support.

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
</dependency>
@Configuration
@EnableGlobalMethodSecurity(
  prePostEnabled = true, 
  securedEnabled = true, 
  jsr250Enabled = true)
public class MethodSecurityConfig 
  extends GlobalMethodSecurityConfiguration {
}

@Secured

Annotation is used to specify a list of roles on a method. So, a user only can access that method if she has at least one of the specified roles.

@Secured({ "ROLE_VIEWER", "ROLE_EDITOR" })
public String getUsername() {
    SecurityContext securityContext = SecurityContextHolder.getContext();
    return securityContext.getAuthentication().getName();
}

The @Secured annotation doesn't support Spring Expression Language (SpEL).

@RolesAllowed

The @RolesAllowed annotation is the JSR-250’s equivalent annotation of the @Secured annotation.

@RolesAllowed("ROLE_VIEWER")
public String getUsername2() {
    //...
}
    
@RolesAllowed({ "ROLE_VIEWER", "ROLE_EDITOR" })
public boolean isValidUsername2(String username) {
    //...
}

@PreAuthorize/@PostAuthorize

Both @PreAuthorize and @PostAuthorize annotations provide expression-based access control.

The @PreAuthorize annotation checks the given expression before entering the method, whereas the @PostAuthorize annotation verifies it after the execution of the method and could alter the result.

@PreAuthorize("hasRole('ROLE_VIEWER')")
public String getUsernameInUpperCase() {
    return getUsername().toUpperCase();
}
@Service
@PreAuthorize("hasRole('ROLE_ADMIN')")
public class SystemService {

    public String getSystemYear(){
        //...
    }
 
    public String getSystemDate(){
        //...
    }
}

The @PreAuthorize(“hasRole(‘ROLE_VIEWER')”) has the same meaning as @Secured(“ROLE_VIEWER”), which we used in the previous section.

Moreover, we can actually use the method argument as part of the expression:

@PreAuthorize("\#username == authentication.principal.username")
public String getMyRoles(String username) {
    //...
}

@PostAuthorize annotation provides the ability to access the method result:

@PostAuthorize("returnObject.username == authentication.principal.nickName")
public CustomUser loadUserDetail(String username) {
    return userRoleRepository.loadUserByUserName(username);
}

Method Security Meta-Annotation

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('VIEWER')")
public @interface IsViewer {
}

//usage
@IsViewer
public String getUsername4() {
    //...
}

Security expressions:

@PreFilter/@PostFilter

Basic authentication

RESTful web services can be authenticated in many ways, but the most basic one is basic authentication. For basic authentication, we send a username and password using the HTTP [Authorization] header to enable us to access the resource. Usernames and passwords are encoded using base64 encoding (not encryption) in Basic Authentication. The encoding is not secure since it can be easily decoded.

		@Bean
    public UserDetailsService userDetails(){
        UserDetails userDetails = User.withDefaultPasswordEncoder()
                .username("user")
                .password("qwe")
                .roles(Role.USER.name())
                .build();
        UserDetails adminDetails = User.withDefaultPasswordEncoder()
                .username("admin")
                .password("qwe")
                .roles(Role.ADMIN.name())
                .build();
        return new InMemoryUserDetailsManager(userDetails, adminDetails);
    }

@Bean
    public UserDetailsService userDetails(DataSource dataSource) {
        return new JdbcUserDetailsManager(dataSource);
    }

Configuration

@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests((auth) -> auth
                        .requestMatchers("/","/home").permitAll()
                        .requestMatchers("/flats").hasRole("USER")
                        .requestMatchers("/users").hasRole("ADMIN")
                        .anyRequest().authenticated()
                )
                .httpBasic(withDefaults());
        return http.build();
    }

password encoder, default - bcrypt

@Bean
  public PasswordEncoder passwordEncoder(){
      return NoOpPasswordEncoder.getInstance();
  }

Untitled 5 10.png

Form based auth

Untitled 6 10.png

Untitled 7 8.png

Digest authentication

Session management

As far as security is concerned, session management relates to securing and managing multiple users' sessions against their request. It facilitates secure interactions between a user and a service/application and pertains to a sequence of requests and responses associated with a particular user. Session Management is one of the most critical aspects of Spring security as if sessions are not managed properly, the security of data will suffer. To control HTTP sessions, Spring security uses the following options:

  1. Explain SecurityContext and SecurityContext Holder in Spring sec

OAth2

OAuth is an open standard that describes a process of authorization. It can be used to authorize user access to an API. For example, a REST API can restrict access to only registered users with a proper role.

An OAuth authorization server is responsible for authenticating the users and issuing access tokens containing the user data and proper access policies.

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-authorization-server</artifactId>
    <version>0.2.0</version>
</dependency>

Untitled 8 8.png

Untitled 9 8.png

Untitled 10 8.png

72.png

Security Server

@Configuration
@Import(OAuth2AuthorizationServerConfiguration.class)
public class AuthorizationServerConfig {
    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
          .clientId("articles-client")
          .clientSecret("{noop}secret")
          .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
          .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
          .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
          .redirectUri("http://127.0.0.1:8080/login/oauth2/code/articles-client-oidc")
          .redirectUri("http://127.0.0.1:8080/authorized")
          .scope(OidcScopes.OPENID)
          .scope("articles.read")
          .build();
        return new InMemoryRegisteredClientRepository(registeredClient);
    }
}

The properties we're configuring are:

Configure a bean to apply the default OAuth security and generate a default form login page:

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
    return http.formLogin(Customizer.withDefaults()).build();
}

@EnableWebSecurity
public class DefaultSecurityConfig {

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests(authorizeRequests ->
          authorizeRequests.anyRequest().authenticated()
        )
          .formLogin(withDefaults());
        return http.build();
    }

    // ...
}

Each authorization server needs its signing key for tokens to keep a proper boundary between security domains.

@Bean
public JWKSource<SecurityContext> jwkSource() {
    RSAKey rsaKey = generateRsa();
    JWKSet jwkSet = new JWKSet(rsaKey);
    return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}

private static RSAKey generateRsa() {
    KeyPair keyPair = generateRsaKey();
    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
    return new RSAKey.Builder(publicKey)
      .privateKey(privateKey)
      .keyID(UUID.randomUUID().toString())
      .build();
}

private static KeyPair generateRsaKey() {
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
    keyPairGenerator.initialize(2048);
    return keyPairGenerator.generateKeyPair();
}

@Bean
public ProviderSettings providerSettings() {
    return ProviderSettings.builder()
      .issuer("http://auth-server:9000")
      .build();
}

Untitled 12 8.png

Untitled 13 8.png

@EnableWebSecurity

конфигурация(содержит @Configuration) для Spring Security

Secured object

Объект, который имеет какие-то ограничения, по правам доступа, которые могут к нему обратиться

Authentication manager

основной интерфейс стратегии для аутентификации. Он отвечает за проверку аутентификации.

SecurityContext

содержит объект Authentication и в случае необходимости информацию системы безопасности, связанную с запросом от пользователя.

WebSecurityConfigureradapter

класс для переопределения настроек Security. Переопрепределяется configure

UserBuilder

класс для создания и настройки пользователей

HttpSecurity

настройки доступа для http запросов

Authentication

Authentication is a process to verify that the user is the one who he claims to be. It is generally implemented using a username and password. If a user enters the correct username and password then authentication is successful, otherwise, authentication failed.

Untitled 14 8.png

Authorisation

Authorisation provides access control. For example, only the admin can see some pages in a web application. To implement that, the admin must have some admin-related permissions or roles.

Untitled 15 7.png

Untitled 16 7.png