Monday, April 24, 2023

Spring Security 6 Custom Login, OAuth2 Login with Google and Basic Auth

 

Introduction:


Spring Security is a framework that provides authentication, authorization, and protection against common attacks. With first class support for securing both imperative and reactive applications, it is the de-facto standard for securing Spring-based applications.

Consuming Authentication objects:

Spring security provides Authentication
  • Authentication (authn): represent the users.
    • Principal: (name, email etc)
    • GrantedAuthorities: roles.
  • Authorization (authz): are the users  allowed to perform.


Demos: 

In this post I integrate Custom Login Form, OAuth2 Login with Google and Basic Auth.


Technology


  • Spring Boot 3.0.6
  • Java 17
  • OAuth2
  • Maven 
  • IntelliJ IDEA

Security Configuration:


SecurityConfig.java: In this class, I configure Custom Login, OAuth2 Login with Google and Basic Auth. sometimes we need apply some filter before, or some case we need test with user admin in this case I create the class AdminAuthenticateProvider without password.

package com.henry.springsecurity.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig {


@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

return http
.authorizeRequests( authorizeConfig -> {
authorizeConfig.requestMatchers("/").permitAll();
authorizeConfig.requestMatchers("/login/**").permitAll();
authorizeConfig.requestMatchers("/error").permitAll();
authorizeConfig.requestMatchers("/favicon.ico").permitAll();
authorizeConfig.anyRequest().authenticated();
})
.formLogin( login -> {
login.loginPage("/login").permitAll();
login.defaultSuccessUrl("/private");
login.failureUrl("/login?error=true").permitAll();
}) //Normal Login
.logout(logout -> {
logout.logoutSuccessUrl("/login?logout=true").permitAll();
logout.invalidateHttpSession(true).permitAll();
logout.deleteCookies("JSESSIONID").permitAll();
})
.httpBasic(Customizer.withDefaults()) // support basic auth
.oauth2Login(oauth -> {
oauth.loginPage("/login").permitAll();
oauth.defaultSuccessUrl("/private");
oauth.failureUrl("/login?error=true").permitAll();
}) // OpenID Connect with google
.addFilterBefore(new CustFilter(), UsernamePasswordAuthenticationFilter.class)
.authenticationProvider(new AdminAuthenticateProvider())
.csrf()
.disable()
.build();
}

@Bean
public UserDetailsService userDetailsService(){
return new InMemoryUserDetailsManager(
User.builder()
.username("henry")
.password("{noop}password")
.authorities("ROLE_USER")
.build()
);
}

@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring() .requestMatchers("/resources/**", "/static/**", "/css/**");
}
}


AdminAuthenticateProvider.java

package com.henry.springsecurity.config;

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;

public class AdminAuthenticateProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
var username = authentication.getName();
if("admin".equalsIgnoreCase(username)){
return UsernamePasswordAuthenticationToken.authenticated(
"admin",
null,
AuthorityUtils.createAuthorityList("ROLE_ADMIN")
);
}
return null;
}

@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}

CustFilter.java

package com.henry.springsecurity.config;

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

public class CustFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
System.out.println("Hello filter");
filterChain.doFilter(request, response);
}
}




OAuth2 Login with Google Configuration:


  1. Create new project or choose existing project.
  2. Go to APIs & Services or Type oauth in search bar
  3. Choose  Credentials 
  4. Click on CREATE CREDENTIALS ->  OAuth client ID 
  5. Application Type -> Web application
  6. Name -> your app name
  7. Authorized redirect URIs: http://localhost:8080/login/oauth2/code/google
  8. CREATE





View 


Create a login page our own.


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head>
<title>Please Log In</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" >
<link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" >
<link rel="stylesheet" href="/css/login.css" type="text/css"/>
</head>
<body>
<section class="vh-100">
<div class="container-fluid h-custom">
<div class="row d-flex justify-content-center align-items-center h-100">
<div class="col-md-8 col-lg-6 col-xl-4 offset-xl-1">
<div
class="d-flex flex-row align-items-center justify-content-center justify-content-lg-start">
<p class="lead fw-normal mb-0 me-3">Login with OAuth 2.0</p>
</div>
<div class="d-flex flex-row align-items-center justify-content-center justify-content-lg-start">
<table class="table table-striped">
<tbody>
<tr>
<td><a href="/oauth2/authorization/google">Google</a></td>
</tr>
</tbody>
</table>
</div>
<form th:action="@{/login}" method="post">
<div class="divider d-flex align-items-center my-4">
<p class="text-center fw-bold mx-3 mb-0">Or</p>
</div>
<!-- Username input -->
<div class="form-outline mb-4">
<input type="text" name="username" class="form-control form-control-lg"
placeholder="Username" />
</div>
<!-- Password input -->
<div class="form-outline mb-3">
<input type="password" name="password" class="form-control form-control-lg"
placeholder="Password" />
</div>
<div class="text-center text-lg-start mt-4 pt-2">
<button type="submit" class="btn btn-primary btn-lg"
style="padding-left: 2.5rem; padding-right: 2.5rem;">Login</button>
</div>
</form>
</div>
</div>
</div>
</section>
</body>
</html>



Controller:


LoginController.java

package com.henry.springsecurity.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class LoginController {

@GetMapping("/login")
public String login() {
return "login";
}
}


WebController.java


package com.henry.springsecurity.controller;

import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Optional;

@RestController
public class WebController {


@GetMapping("/")
public String publicPage(){
return "Hello Henry";
}

@GetMapping("/private")
public String privatePage(Authentication authentication){
return "Welcome " + getName(authentication);
}

private String getName(Authentication authentication) {
return Optional.of(authentication.getPrincipal())
.filter(OidcUser.class::isInstance)
.map(OidcUser.class::cast)
.map(OidcUser::getEmail)
.orElseGet(authentication::getName);

}
}



Project Dependencies:


pom.xml


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.henry</groupId>
<artifactId>spring-security</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-security</name>
<description>Demo project for Spring Boot Security</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>


application.yml


spring:
security:
oauth2:
client:
registration:
google:
client-id: YOUR_CLIENT_ID
client-secret: YOUR_CLIENT_SECRET


Run & Test


Run Spring Boot application with command: mvn spring-boot:run. by console, IntelliJ etc.

  • Without Security go to: http://localhost:8080/ result this:







  • With Security go to: http://localhost:8080/private result this:












Type:

User: henry 
Password: password 


Note: In the case, we will be test with admin user,  you can typing anything password, because I created the  AdminAuthenticateProvider class and configure in Spring Security config authenticationProvider. for more details check in the end the references.


  • Test 1 - With user and password result this





  • Test 2 - With  OAuth2 Login with Google result this





  • Test 3 - With Basic Auth

  • Test 4 - With admin user typing anything password.






















Source Code


Here on GitHub.




References.

https://docs.spring.io/spring-security/reference/index.html
https://docs.spring.io/spring-security/reference/features/authentication/index.html



No comments:

Post a Comment

Deploying a Spring Boot Application with Cloud SQL and Cloud Run on GCP

In this post, we'll explore how to provision Cloud SQL instances with different connectivity options using Terraform and then access the...