{"id":2290,"date":"2026-07-20T12:43:14","date_gmt":"2026-07-20T10:43:14","guid":{"rendered":"https:\/\/kindsonthegenius.com\/blog\/spring-security-tutorial-5-using-oauth-2-authentication\/"},"modified":"2026-08-26T20:46:58","modified_gmt":"2026-08-26T18:46:58","slug":"spring-security-tutorial-5-using-oauth-2-authentication","status":"publish","type":"post","link":"https:\/\/kindsonthegenius.com\/blog\/spring-security-tutorial-5-using-oauth-2-authentication\/","title":{"rendered":"Spring Security Tutorial 5 \u2013 Using OAuth 2 Authentication"},"content":{"rendered":"<p><!-- ktg-updated-banner --><\/p>\n<p><em>Updated August 2026 \u2014 OAuth 2 tutorial restored for this URL.<\/em><\/p>\n<p>In this tutorial we will see how to allow login to our application using <strong>OAuth 2.0<\/strong> \/ OpenID Connect (for example Google), on top of Spring Security.<\/p>\n<p>Previously we covered:<\/p>\n<ul>\n<li><a href=\"https:\/\/kindsonthegenius.com\/blog\/introduction-to-spring-security-a-practical-tutorial\/\"><strong>Part 1<\/strong>: Introduction to Spring Security \u2014 practical login<\/a><\/li>\n<li><a href=\"https:\/\/kindsonthegenius.com\/blog\/spring-security-tutorial-storing-user-credential-in-mysql-database\/\"><strong>Part 2<\/strong>: Storing username and password in MySQL<\/a><\/li>\n<\/ul>\n<p>Related: <a href=\"https:\/\/kindsonthegenius.com\/blog\/jpa-auditing-in-spring-boot-using-mysql-tracking-changes\/\">JPA Auditing with MySQL<\/a> (often enabled in the same secured apps).<\/p>\n<p>We will cover:<\/p>\n<ol>\n<li><a href=\"#t1\">What OAuth 2 login gives you<\/a><\/li>\n<li><a href=\"#t2\">Dependencies<\/a><\/li>\n<li><a href=\"#t3\">Register an OAuth app (Google example)<\/a><\/li>\n<li><a href=\"#t4\">application.properties \/ YAML<\/a><\/li>\n<li><a href=\"#t5\">SecurityFilterChain configuration<\/a><\/li>\n<li><a href=\"#t6\">Login page link<\/a><\/li>\n<li><a href=\"#t7\">Read the logged-in OAuth user<\/a><\/li>\n<li><a href=\"#t8\">Combine with form login (optional)<\/a><\/li>\n<\/ol>\n<p><strong id=\"t1\">1. What OAuth 2 login gives you<\/strong><\/p>\n<p>Instead of only a local username\/password form, users can sign in with an external Identity Provider (IdP). Spring Security\u2019s OAuth2 Login support handles the redirect, authorization code exchange, and builds an authenticated <code>OAuth2User<\/code>.<\/p>\n<p><strong id=\"t2\">2. Dependencies<\/strong><\/p>\n<p>In <code>pom.xml<\/code> (Spring Boot 3.x shown with Jakarta):<\/p>\n<pre><code>&lt;dependency&gt;\n  &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\n  &lt;artifactId&gt;spring-boot-starter-oauth2-client&lt;\/artifactId&gt;\n&lt;\/dependency&gt;\n&lt;dependency&gt;\n  &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\n  &lt;artifactId&gt;spring-boot-starter-security&lt;\/artifactId&gt;\n&lt;\/dependency&gt;\n&lt;dependency&gt;\n  &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\n  &lt;artifactId&gt;spring-boot-starter-thymeleaf&lt;\/artifactId&gt;\n&lt;\/dependency&gt;\n<\/code><\/pre>\n<p><strong id=\"t3\">3. Register an OAuth app (Google example)<\/strong><\/p>\n<ol>\n<li>Open Google Cloud Console \u2192 APIs &amp; Services \u2192 Credentials.<\/li>\n<li>Create an <strong>OAuth client ID<\/strong> (Web application).<\/li>\n<li>Authorized redirect URI (Spring Boot default):<\/li>\n<\/ol>\n<pre><code>http:\/\/localhost:8080\/login\/oauth2\/code\/google\n<\/code><\/pre>\n<p>Copy the <strong>Client ID<\/strong> and <strong>Client secret<\/strong>.<\/p>\n<p><strong id=\"t4\">4. application.properties<\/strong><\/p>\n<pre><code>spring.security.oauth2.client.registration.google.client-id=YOUR_CLIENT_ID\nspring.security.oauth2.client.registration.google.client-secret=YOUR_CLIENT_SECRET\nspring.security.oauth2.client.registration.google.scope=openid,profile,email\n<\/code><\/pre>\n<p>For GitHub, use <code>registration.github<\/code> and redirect <code>\/login\/oauth2\/code\/github<\/code>. Provider details are auto-configured for common IdPs.<\/p>\n<p><strong id=\"t5\">5. SecurityFilterChain configuration<\/strong><\/p>\n<pre><code>@Configuration\n@EnableWebSecurity\npublic class SecurityConfig {\n\n    @Bean\n    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {\n        http\n            .authorizeHttpRequests(auth -&gt; auth\n                .requestMatchers(\"\/\", \"\/login\", \"\/css\/**\").permitAll()\n                .anyRequest().authenticated()\n            )\n            .oauth2Login(oauth -&gt; oauth\n                .loginPage(\"\/login\")\n            )\n            .logout(logout -&gt; logout\n                .logoutSuccessUrl(\"\/\")\n            );\n        return http.build();\n    }\n}\n<\/code><\/pre>\n<p>Visiting a protected URL redirects unauthenticated users to your login page (or directly to the provider if you omit a custom login page).<\/p>\n<p><strong id=\"t6\">6. Login page link<\/strong><\/p>\n<p><code>templates\/login.html<\/code>:<\/p>\n<pre><code>&lt;!DOCTYPE html&gt;\n&lt;html xmlns:th=\"http:\/\/www.thymeleaf.org\"&gt;\n&lt;body&gt;\n  &lt;h1&gt;Login&lt;\/h1&gt;\n  &lt;p&gt;&lt;a th:href=\"@{\/oauth2\/authorization\/google}\"&gt;Continue with Google&lt;\/a&gt;&lt;\/p&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/code><\/pre>\n<p>The path <code>\/oauth2\/authorization\/{registrationId}<\/code> starts the OAuth dance (<code>google<\/code> matches the registration id in properties).<\/p>\n<p><strong id=\"t7\">7. Read the logged-in OAuth user<\/strong><\/p>\n<pre><code>@GetMapping(\"\/me\")\n@ResponseBody\npublic Map&lt;String, Object&gt; me(@AuthenticationPrincipal OAuth2User user) {\n    return Map.of(\n        \"name\", user.getAttribute(\"name\"),\n        \"email\", user.getAttribute(\"email\")\n    );\n}\n<\/code><\/pre>\n<p>Attribute names depend on the provider (Google uses <code>email<\/code>, <code>name<\/code>, <code>sub<\/code>, \u2026).<\/p>\n<p><strong id=\"t8\">8. Combine with form login (optional)<\/strong><\/p>\n<p>You can keep MySQL form login from Part 2 <em>and<\/em> OAuth:<\/p>\n<pre><code>http\n  .authorizeHttpRequests(...)\n  .formLogin(form -&gt; form.loginPage(\"\/login\").permitAll())\n  .oauth2Login(oauth -&gt; oauth.loginPage(\"\/login\"));\n<\/code><\/pre>\n<p>On the same login page, show both the username\/password form and the \u201cContinue with Google\u201d link.<\/p>\n<p>For production:<\/p>\n<ul>\n<li>Use HTTPS and real redirect URIs<\/li>\n<li>Store secrets outside source control<\/li>\n<li>Decide whether OAuth users are auto-provisioned into your MySQL <code>users<\/code> table<\/li>\n<\/ul>\n<p><strong>Troubleshooting<\/strong><\/p>\n<ul>\n<li><strong>redirect_uri_mismatch<\/strong> \u2014 the console redirect URI must match exactly, including port and <code>\/login\/oauth2\/code\/google<\/code>.<\/li>\n<li><strong>Invalid client<\/strong> \u2014 wrong client id\/secret, or secret not refreshed after reset.<\/li>\n<li><strong>Loop on \/login<\/strong> \u2014 ensure <code>\/login<\/code> and <code>\/oauth2\/**<\/code> are permitted, and you are not requiring auth for the authorization endpoint.<\/li>\n<li><strong>Missing email attribute<\/strong> \u2014 add <code>email<\/code> scope and enable the email claim in the provider console.<\/li>\n<\/ul>\n<p><strong>Next steps<\/strong><\/p>\n<ul>\n<li>Revisit <a href=\"https:\/\/kindsonthegenius.com\/blog\/introduction-to-spring-security-a-practical-tutorial\/\">Part 1<\/a> and <a href=\"https:\/\/kindsonthegenius.com\/blog\/spring-security-tutorial-storing-user-credential-in-mysql-database\/\">Part 2<\/a>.<\/li>\n<li>Add roles\/authorities after OAuth login for API authorization.<\/li>\n<li>Enable <a href=\"https:\/\/kindsonthegenius.com\/blog\/jpa-auditing-in-spring-boot-using-mysql-tracking-changes\/\">JPA Auditing<\/a> so <code>createdBy<\/code> uses the OAuth email\/username.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Updated August 2026 \u2014 OAuth 2 tutorial restored for this URL. In this tutorial we will see how to allow login to our application using &hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"pagelayer_contact_templates":[],"_pagelayer_content":"","footnotes":""},"categories":[85],"tags":[],"class_list":["post-2290","post","type-post","status-publish","format-standard","hentry","category-java"],"acf":[],"_links":{"self":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts\/2290","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/comments?post=2290"}],"version-history":[{"count":2,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts\/2290\/revisions"}],"predecessor-version":[{"id":2438,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/posts\/2290\/revisions\/2438"}],"wp:attachment":[{"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/media?parent=2290"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/categories?post=2290"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/kindsonthegenius.com\/blog\/wp-json\/wp\/v2\/tags?post=2290"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}