Spring Security 6 Custom Login, OAuth2 Login with Google and Basic Auth
Introduction:
Consuming Authentication objects:
- Authentication (authn): represent the users.
- Principal: (name, email etc)
- GrantedAuthorities: roles.
- Authorization (authz): are the users allowed to perform.
Demos:
Technology
- Spring Boot 3.0.6
- Java 17
- OAuth2
- Maven
- IntelliJ IDEA
Security Configuration:
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/**»);
}
}
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);
}
}
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:
- Create new project or choose existing project.
- Go to APIs & Services or Type oauth in search bar
- Choose Credentials
- Click on CREATE CREDENTIALS -> OAuth client ID
- Application Type -> Web application
- Name -> your app name
- Authorized redirect URIs: http://localhost:8080/login/oauth2/code/google
- CREATE
View
Create a login page our own.
<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»;
}
}
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
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
<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
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.
- Without Security go to: http://localhost:8080/ result this:
- With Security go to: http://localhost:8080/private result this: