DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Authentication With Remote LDAP Server in Spring Web MVC
  • How to Implement Two-Factor Authentication in A Spring Boot OAuth Server? Part 2: Under the Hood
  • Spring Security Oauth2: Google Login
  • Implementing Your Own Spring Boot Oauth2 Authorization Server

Trending

  • How Can Developers Drive Innovation by Combining IoT and AI?
  • Analyzing Techniques to Provision Access via IDAM Models During Emergency and Disaster Response
  • Can You Run a MariaDB Cluster on a $150 Kubernetes Lab? I Gave It a Shot
  • The Role of AI in Identity and Access Management for Organizations
  1. DZone
  2. Coding
  3. Frameworks
  4. Authentication With Remote LDAP Server in Spring WebFlux

Authentication With Remote LDAP Server in Spring WebFlux

A guide for Java developers who want to integrate Spring Boot Webflux applications with a remote LDAP server and authenticate/authorize their users with JWT.

By 
Taner Inal user avatar
Taner Inal
·
Updated Jan. 16, 22 · Tutorial
Likes (8)
Comment
Save
Tweet
Share
11.3K Views

Join the DZone community and get the full member experience.

Join For Free

In my previous article, we covered authentication and authorization with remote LDAP servers in Spring Web MVC. Since base concepts are the same, some sections are unavoidably the same in these two articles. In order to start from scratch, please refer to the previous article before reading this one.

In this article, we will explain the concept for Spring Webflux. To keep this article as simple as possible; while walking through the sections, if necessary, you will be forwarded to the previous article's related section for detailed information.

Development Environment Information

For the sake of being on the same page, below is our development environment information. Version information for project dependencies is discussed in the next section.

  • OS: Windows 10 20H2
  • IDE: IntelliJ IDEA 2019.3.1 (Ultimate Edition)
  • Java: 1.8
  • Spring Boot: 2.5.8
  • Apache Maven: 3.5.0

Preparing the Spring Boot Project

We are initializing our Spring Boot project from Spring Initializr.

In order to integrate with LDAP server, do not forget to add the necessary dependencies explained in the "LDAP Integration" section of the previous article.

Preparing APIs

For this project; we have two roles: FINANCE and BUSINESS. We authorize these roles for paths /finance-zone and /business-zone respectively.  These APIs and how authentication is being handled via /authentication API are explained in detail in our previous article under "Preparing APIs" section. Also please refer to the "Introducing JWT Handling Mechanism" section for how JWT mechanism work for authentication purposes.

Introducing Auth/Autz Exception Handling Mechanisms

For unauthenticated requests (i.e., access attempts with expired JWT tokens) we prepare AuthenticationEntryPoint to handle this case and return the descriptive response to the client. Here is the code:

Java
 
@Configuration
@Slf4j
public class AuthenticationEntryPoint implements ServerAuthenticationEntryPoint {

    @Override
    public Mono<Void> commence(ServerWebExchange serverWebExchange, AuthenticationException e) {
        if (CollectionUtils.isNotEmpty(serverWebExchange.getRequest().getHeaders().get(Constants.HEADER_AUTHORIZATION))) {
            return Mono.empty();
        } else {
            log.info("Unauthenticated access attempt to resource {} with HttpMethod of {} Token: {}",
                    serverWebExchange.getRequest().getPath(),
                    StringUtils.defaultString(serverWebExchange.getRequest().getMethod().name(), StringUtils.EMPTY),
                    serverWebExchange.getRequest().getHeaders().get(Constants.HEADER_AUTHORIZATION));
            return Mono.error(new UnauthenticatedException("Please login to access this resource!"));
        }
    }
}

For this scenario, the client will receive a "HttpStatus.400 (Bad Request)" response since the authentication info being sent is bad.

For unauthorized requests (i.e., access attempts to an API that is not allowed by that user's role), we prepare AccessDeniedHandler to handle this case and return the descriptive response to the client. Here is the code:

Java
 
@Configuration
@Slf4j
public class AccessDeniedHandler implements ServerAccessDeniedHandler {

    @Override
    public Mono<Void> handle(ServerWebExchange serverWebExchange, AccessDeniedException e) {
        log.info("Access Denied. Unauthorized access attempt to resource {} from {} with HttpMethod of {} Authorization header: {}",
                serverWebExchange.getRequest().getPath(),
                Optional.ofNullable(serverWebExchange.getRequest().getHeaders().get(Constants.HEADER_X_FORWARDED_FOR))
                        .orElse(Collections.singletonList(serverWebExchange.getRequest().getRemoteAddress().getHostName())),
                StringUtils.defaultString(serverWebExchange.getRequest().getMethod().name(), StringUtils.EMPTY),
                serverWebExchange.getRequest().getHeaders().get(Constants.HEADER_AUTHORIZATION));

        return Mono.error(new UnauthorizedException("You are trying to access to a resource that you're not allowed to."));
    }
}

We'll use both mechanisms while configuring security in later topics.

Configuring Spring Security

This is the point where we should describe the core of this whole mechanism: WebSecurityConfiguration. In this class, we configure the security of our web application under the configure method:

Java
 
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
@RequiredArgsConstructor
public class WebSecurityConfiguration {
    private final AuthenticationSuccessHandler authenticationSuccessHandler;
    private final AuthenticationEntryPoint authenticationEntryPoint;
    private final AccessDeniedHandler accessDeniedHandler;
    private final PortalUserService portalUserService;

    @Value("${autz.permitted.paths.all}")
    private String[] permittedPaths;
    @Value("${autz.permitted.paths.finance}")
    private String[] financeRolePermittedPaths;
    @Value("${autz.permitted.paths.business}")
    private String[] businessRolePermittedPaths;

    @Value("${jwt.secret}")
    private String jwtSecret;

    @Bean
    SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        AuthenticationWebFilter authenticationWebFilter = new AuthenticationWebFilter(new UserDetailsRepositoryReactiveAuthenticationManager(portalUserService));
        authenticationWebFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);

        http
                .cors()
                .and()
                .csrf()
                    .disable()
                .addFilterAt(authenticationWebFilter, SecurityWebFiltersOrder.FIRST)
                .authorizeExchange()
                    .pathMatchers(permittedPaths).permitAll()
                    .pathMatchers(financeRolePermittedPaths).hasAuthority(Constants.LDAP_ROLE_FINANCE)
                    .pathMatchers(businessRolePermittedPaths).hasAuthority(Constants.LDAP_ROLE_BUSINESS)
                    .anyExchange().denyAll()
                .and()
                    .exceptionHandling()
                    .authenticationEntryPoint(this.authenticationEntryPoint)
                    .accessDeniedHandler(this.accessDeniedHandler)
                .and()
                    .securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
                    .logout()
                        .disable()
                    .formLogin()
                        .disable();

        http.addFilterAt(new JwtRequestFilter(jwtSecret, portalUserService, authenticationSuccessHandler), SecurityWebFiltersOrder.FIRST);

        return http.build();
    }
}

The following list explains the above code:

  1. Cors and csrf strategies are declared. We disabled csrf since we use JWT for authorization in each and every request.
  2. Under authorizeExchange(), we declared "who has access to where." Authorization checks are done according to these rules.
  3. Since we authorize each request with JWT, we don't need a session. We declared the session creation policy as stateless by setting securityContextRepository to NoOpServerSecurityContextRepository. (This also works well if you don't share sessions between application servers.)
  4. What will happen when an authentication or authorization error occurs? This question is being answered under exceptionHandling(). We covered the contents of these classes in previous sections. 
  5. At last, we disabled the built-in login mechanism of Spring Security, since we provided the  /authentication API. We also disabled the logout mechanism, because it is beyond the scope of this article.

Testing

We test our application with Postman. You can find the exported Postman collection in my GitHub repository.

First things first; we authenticate our Finance user and retrieve the token:
Screenshot showing user authentication.

Here is the response we received:Screenshot of user authentication response.

Now its time to call the /finance API with the above token:Screenshot of the Finance API.

Here is the response we received;
Response returned for Finance API.

So what if we call the /business API with our Finance user? Here is the request:
Screenshot showing /business API request.

Here is the response:

Conclusion

In this article, we developed a Spring Boot project and integrated it into a remote LDAP through Spring Security. In addition, we performed authentication and authorization operations over JWT for the APIs we opened. 

You can get the full implementation of this article from my GitHub repository.

Spring Framework authentication workplace Spring Security intellij Spring Boot

Opinions expressed by DZone contributors are their own.

Related

  • Authentication With Remote LDAP Server in Spring Web MVC
  • How to Implement Two-Factor Authentication in A Spring Boot OAuth Server? Part 2: Under the Hood
  • Spring Security Oauth2: Google Login
  • Implementing Your Own Spring Boot Oauth2 Authorization Server

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!