SpringBoot OAuth2自定义登陆/授权页

      背景

        5 月份的时候,我实践并整理了一篇博客:SpringBoot搭建OAuth2,该博客完成之后,很长一段时间里我都有种意犹未尽的感觉。诚然,我把OAuth2搭起来了,各种场景的用例也跑通了,甚至源码也看了,但我还是觉得自己的了解不够透彻。

        我们公司也使用OAuth认证服务,企业微服务中是怎么应用OAuth2的?我搭建的认证服务离公司的成熟框架还有多远?这是我在学习OAuth时一直想弄明白的问题。幸运的是,在经历重重困难后,我终于把它们都梳理清楚了。我的学习成果会在本次及后面的几篇博客中体现。今天,咱们先迈出第一步:OAuth自定义登陆和授权页面

        在企业内使用OAuth2时,研发人员最先做的就是重新设计登陆和授权页面了。诚然,OAuth里有默认的登陆和授权页面,但那是最原生的页面,既不美观,也和其他服务的页面风格不搭,所以基本没有人会用原生页面。在这里,我就先实践下怎么自定义登陆和授权页面吧。

      代码实践

        纵观网上的各种资料,我发现OAuth2自定义登陆页面有两种方式,一种是利用thymeleaf的方式,通过Controller跳转到html,另一种是直接跳转到html的方式。这两种方式我都会演示下。为了方便测试,下文中的实践均采用InMemory的配置方式。另外建议下,在实践时不要在服务上加上下文根(如http://127.0.0.1:8080/leixi/oauth/…里的/leixi),非常影响测试。

      一、通过thymeleaf跳转自定义页面

        1、首先,引入Jar包

        <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><!--  OAuth2.0依赖  --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId><version>2.2.5.RELEASE</version></dependency><!--页面要用到的框架--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>

        2、配置@Config

package com.leixi.auth2.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;/**** @author leixiyueqi* @since 2024/9/4 22:00*/
// security 安全相关的配置类
@Configuration
@Order(1)
public class SecurityMemoryConfiguration extends WebSecurityConfigurerAdapter {private static final String loginUrl = "/login";private static final String loginPage = "/authcation/login";@Overrideprotected void configure(HttpSecurity http) throws Exception {http// http security 要拦截的url,这里这拦截,oauth2相关和登录登录相关的url,其他的交给资源服务处理.requestMatchers().antMatchers( "/oauth/**",loginUrl,loginPage).and().authorizeRequests()// 自定义页面或处理url是,如果不配置全局允许,浏览器会提示服务器将页面转发多次.antMatchers(loginUrl,loginPage).permitAll().anyRequest().authenticated();// 表单登录http.formLogin()// 登录页面.loginPage(loginPage)// 登录处理url.loginProcessingUrl(loginUrl);http.httpBasic().disable();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();auth.inMemoryAuthentication()   //直接创建一个静态用户.passwordEncoder(encoder).withUser("leixi").password(encoder.encode("123456")).roles("USER");}@Bean   //这里需要将AuthenticationManager注册为Bean,在OAuth配置中使用@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Bean@Overridepublic UserDetailsService userDetailsServiceBean() throws Exception {return super.userDetailsServiceBean();}@Beanpublic TokenStore tokenStore() {return new InMemoryTokenStore();}
}package com.leixi.auth2.config;import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;import javax.annotation.Resource;/*** 基于内存的设置方式,所有的客户端,用户信息都在内存里** @author leixiyueqi* @since 2024/9/4 22:00*/
@EnableAuthorizationServer   //开启验证服务器
@Configuration
public class OAuth2MemoryConfiguration extends AuthorizationServerConfigurerAdapter {@Resourceprivate AuthenticationManager manager;private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();@ResourceUserDetailsService service;/*** 这个方法是对客户端进行配置,* 之后这些指定的客户端就可以按照下面指定的方式进行验证* @param clients 客户端配置工具*/@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {clients.inMemory()   //这里我们直接硬编码创建,当然也可以像Security那样自定义或是使用JDBC从数据库读取.withClient("client")   //客户端名称,随便起就行.secret(encoder.encode("654321"))      //只与客户端分享的secret,随便写,但是注意要加密.autoApprove(false)    //自动审批,这里关闭,要的就是一会体验那种感觉.scopes("book", "user", "borrow")     //授权范围,这里我们使用全部all.autoApprove(false).redirectUris("http://127.0.0.1:19210/leixi/demo").authorizedGrantTypes("client_credentials", "password", "implicit", "authorization_code", "refresh_token");//授权模式,一共支持5种,除了之前我们介绍的四种之外,还有一个刷新Token的模式}@Overridepublic void configure(AuthorizationServerSecurityConfigurer security) {security.passwordEncoder(encoder)    //编码器设定为BCryptPasswordEncoder.allowFormAuthenticationForClients()  //允许客户端使用表单验证,一会我们POST请求中会携带表单信息.checkTokenAccess("permitAll()");   //允许所有的Token查询请求,没有这一行,check_token就会报401}@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) {endpoints.userDetailsService(service).authenticationManager(manager);//这个是用于在登陆成功后,将授权请求Action替换自定义的Action,以便进入自定义授权页面endpoints.pathMapping("/oauth/confirm_access","/custom/confirm_access");}
}

        3、编写跳转的Controller

package com.leixi.auth2.controller;import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
/**** @author leixiyueqi* @since 2024/9/4 22:00*/
@Controller
@SessionAttributes("authorizationRequest")
public class BootGrantController {@RequestMapping("/authcation/login")public String loginPage(Model model, HttpServletRequest request) {//跳转到登陆页return "login-page";}@RequestMapping("/custom/confirm_access")public String getAccessConfirmation(Map<String, Object> param, HttpServletRequest request, Model model) throws Exception {AuthorizationRequest authorizationRequest = (AuthorizationRequest) param.get("authorizationRequest");if (authorizationRequest==null){return "redirect:"+"login-page";}String clientId = authorizationRequest.getClientId();model.addAttribute("scopes",authorizationRequest.getScope());Map<String, Object> client = new HashMap<>();client.put("clientId",clientId);client.put("name","leixi");  // 这里应该是用户名model.addAttribute("client",client);return "oauth-check";}
}

        4、在resources/static文件夹下编写登陆页login-page.html,授权页oauth-check.html。注意一定要在resources/static下,且文件取名要和Controller里配置的一样。

<!--这是登陆页login-page.html -->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"/><title>登录</title>
</head>
<body>
<h2>自定义登录页面</h2>
<!--spring security 默认处理用户名密码就是/login,可以自定义,需要loginProcessingUrl()-->
<p style="color: red" th:if="${param.error}">用户名或密码错误</p>
<form th:action="'/login'" method="post"><table><tr><td>用户名:</td><td><label><input type="text" name="username"/></label></td></tr><tr><td>密码:</td><td><label><input type="password" name="password"/></label></td></tr><tr><td colspan="2"><button type="submit">登录</button></td></tr></table>
</form>
</body>
</html><!--这是授权页oauth-check.html -->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"/><title>确认授权页面</title><meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no"/><link rel="stylesheet" href="//i.gtimg.cn/vipstyle/frozenui/2.0.0/css/frozen.css"/><style>.block{position: relative;}.ui-notice{position: relative;padding:20px 15px;box-sizing: border-box;}.ui-notice p{color:#333;font-weight: 600;}.ui-btn-primary{background-color: #02cd93;border-color:#02cd93;;}.ui-notice-btn{padding:50px 0px 15px;}</style>
</head>
<body>
<div class="block"><section class="ui-notice"><i class="icon icon-notice"></i><p>是否授权:<span th:text="${session.authorizationRequest.clientId}">clientId</span></p><div class="ui-notice-btn"><form id='confirmationForm' name='confirmationForm' th:action="'/oauth/authorize'" method='post'><input name='user_oauth_approval' value='true' type='hidden'/><!--写好授权访问领域--><div th:each="item:${scopes}"><input type="radio" th:name="'scope.'+${item}" value="true" hidden="hidden" checked="checked"/></div><input class="ui-btn-primary ui-btn-lg ui-btn-primary" name='authorize' value='授权' type='submit'/></form></div></section>
</div>
</body>
</html>

        5、yml中添加配置

spring:# 模板引擎配置thymeleaf:prefix: classpath:/static/suffix: .htmlcache: falsemvc:throw-exception-if-no-handler-found: true

        6、启动项目,输入地址 

                http://127.0.0.1:19200/oauth/authorize?client_id=client&response_type=code

        进行测试,效果如下:

        自定义登陆页面:

        

        自定义授权页面:

        

        授权成功,可以得到code

        

        这么一看,怎么页面还没有原生的漂亮?

        嘞个……我只是为了演示怎么设置自定义页面,用的是最精减的代码,没有做相关样式的设计,所以丑点是正常的。

      二、直接跳转登陆页

        第二种实现方法是参考公司OAuth Server中的实现,直接在Config里配置登陆页,而不再通过Controller和thymeleaf实现页面的跳转,相比于第一种方式,它的实现更加简单,缺点是我目前还没有找到怎么跳转到自定义授权页面的方法,但是一般在企业应用中,都会直接配置自动授权,很少有需要进入授权页面的,这个缺陷并不重要。下面是相比于5月份的那个版本的代码变动。

        1、修改SecurityMemoryConfiguration中的configure(HttpSecurity http),如下

    private static final String loginUrl = "/loginpage.html";@Overrideprotected void configure(HttpSecurity http) throws Exception {http// http security 要拦截的url,这里这拦截,oauth2相关和登录登录相关的url,其他的交给资源服务处理.authorizeRequests().antMatchers( "/oauth/**","/**/*.css", "/**/*.ico", "/**/*.png", "/**/*.jpg", "/**/*.svg", "/login","/**/*.js", "/**/*.map",loginUrl, "/base-grant.html").permitAll().anyRequest().authenticated();// post请求要设置允许跨域,然后会报401http.csrf().ignoringAntMatchers("/login", "/logout", "/unlock/apply");// 表单登录http.formLogin()// 登录页面.loginPage(loginUrl)// 登录处理url.loginProcessingUrl("/login");http.httpBasic();}

        2、在/resources/static下添加loginpage.html,如下:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"/><title>登录</title>
</head>
<body>
<h2>自定义登录页面</h2>
<!--spring security 默认处理用户名密码就是/login,可以自定义,需要loginProcessingUrl()-->
<p style="color: red" th:if="${param.error}">用户名或密码错误</p>
<form th:action="'/login'" method="post"><table><tr><td>用户名:</td><td><label><input type="text" name="username"/></label></td></tr><tr><td>密码:</td><td><label><input type="password" name="password"/></label></td></tr><tr><td colspan="2"><button type="submit">登录</button></td></tr></table>
</form>
</body>
</html>

        3、如果不需要手动授权,可以修改OAuth2MemoryConfiguration中的client配置,这样就会自动略过授权页面了。

        4、功能测试,依然是那个链接,那个密码,结果如下:

        

        

      后记与致谢

        之前参考的资料里,大多都只写了thymeleaf的实现方法,这里之所以把两种方法都写出来,主要原因是第二种实现方法很简洁,被惊艳到了,另一个原因是我在实践的过程中,因为这两种方法吃了太多的亏。我总是以一种质疑的方式去模仿,为什么网上的方法和公司的实现不一样?非要按网上的来吗?公司好像没引入依赖包啊,这部分可以看网上,那部分公司写的很精练……结果抄来抄去,把两种实现方案抄混了,都没能达到效果。这是一种很低效的学习方法,无论学习什么技术,在有参考的情况下,至少先沿着前人的路走通一条,再去想着优化,革新,这是我得到的最宝贵的教训。

        在实践这篇博客的过程中,雷袭也参考学习了很多大佬的博客,以下这篇:Spring boot+Security OAuth2 自定义登录和授权页面是对我启发最大,最有帮助的,博主还在文中贴心的放上了源码链接,非常值得学习和尊重,拜谢大佬!

        最后再提一嘴,在网上搜索相关资料时,发现很多资料都很老,有的都是17,18年的老博客了。这也让我在研究这OAuth时有着浓浓的挫败感,毕竟技术是日新月异,不断迭代的,最新的博客很少,说明世面上肯定有很多新的技术取代旧技术了。侧面也说明了,我现在研究的东西,在七八年之前,已经有人成体系的研究出方案了,想想都感觉好落伍啊!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/419920.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

99.WEB渗透测试-信息收集-网络空间搜索引擎shodan(1)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;98.WEB渗透测试-信息收集-Google语法&#xff08;12&#xff09; 信息收集方向-网络空间…

【IDEA配置一个maven项目(详细操作流程)】

目录 一、安装Maven 1、官网下载maven链接地址&#xff1a;Maven – Download Apache Maven 2、下载完成后&#xff0c;解压到某一路径下。E:\JavaTools\apache-maven-3.9.8为例&#xff0c;实际配置环境变量时以自己安装的路径为准。 二、配置环境变量 1、右键此电脑–&g…

springboot、flowable 生成图片发布到Docker乱码问题

flowable自带的方法生成图片时&#xff0c;如设置字体为宋体&#xff0c;则本地测试没有问题&#xff0c;因为windows自带宋体字体库&#xff0c;但是如果发布到Docker&#xff0c;则会出现乱码问题&#xff0c;因为大部分Docker并不包含宋体字体库&#xff1b; 通过Java代码&a…

基于springboot+vue实现的在线商城系统

系统主要功能&#xff1a; &#xff08;1&#xff09;商品管理模块&#xff1a;实现了商品的基本信息录入、图片上传、状态管理等相关功能。 &#xff08;2&#xff09;商品分类模块&#xff1a;实现了分类的增删改查、分类层级管理、商品分类的关联等功能。 &#xff08;3&…

一个穷稳且病多的中年案例

调整 理性消费&#xff0c;量入为出 重视健康&#xff0c;提前规划 多元收入&#xff0c;提升自我 心态平和&#xff0c;知足常乐 提示&#xff1a;最后悔买“方”。 “方”和“車”对现金流的影响非常大。 全都是大额消耗性支出。 保持健康也需要物质基础。 为何收入或…

深度学习应用 - 自然语言处理(NLP)篇

序言 在信息技术的浩瀚星空中&#xff0c;深度学习犹如一颗璀璨的新星&#xff0c;正引领着人工智能领域的深刻变革。作为这一领域的核心分支&#xff0c;自然语言处理&#xff08; NLP \text{NLP} NLP&#xff09;更是借助深度学习的力量&#xff0c;实现了前所未有的飞跃。自…

BookStack在线文档管理系统本地Docker部署与远程访问详细教程

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

电池的电-热-寿命模型是什么?

一、背景 电池的电-热-寿命模型在工程领域具有重要意义&#xff0c;它是一种描述电池性能、温度与使用寿命之间相互关系的复杂模型。具体工程意义体现在以下几个方面&#xff1a; 性能预测&#xff1a; 通过电-热-寿命模型&#xff0c;工程师可以预测在不同负载条件下电池的…

基于YOLOv8的PCB缺陷检测算法,加入一种基于内容引导注意力(CGA)的混合融合方案(一)

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文内容&#xff1a;针对基于YOLOv8的PCB缺陷检测算法进行性能提升&#xff0c;加入各个创新点做验证性试验。 1&#xff09;提出了一种基于内容引导注意力(CGA)的混合融合方案&#xff0c;mAP0.5由原始的0.966提升至0.975 1.PCB缺陷…

【数据结构】排序算法篇二

【数据结构】排序算法篇二 1. 快速排序&#xff08;hoare版本&#xff09;&#xff08;1&#xff09;基本思想&#xff1a;&#xff08;2&#xff09;动态图解&#xff1a;&#xff08;3&#xff09;代码实现&#xff1a;&#xff08;4&#xff09;特性总结&#xff1a; 2. 快速…

Spring Boot属性注入的多种方式!

Spring Boot的一个问题&#xff0c;证明你是不是真正的 "会用" Spring boot ?Spring Boot的一个问题&#xff0c;直接暴露你是不是真正使用Spring Boothttps://mp.weixin.qq.com/s?__bizMzkzMTY0Mjc0Ng&mid2247484040&idx1&sn64ad15d95e44c874cc890973…

uboot源码分析uboot启动流程,uboot-CMD命令调用关系

uboot的最终目的是引导启动内核加载系统&#xff0c;根据这个线索我们可以首先找到uboot引导内核的main函数&#xff0c;查看系统引导的执行跳转的函数 main_loop。 下面对uboot函数的调用关系和主要调用函数进行分析。 一、uboot函数调用关系梳理 函数调用如下&#xff1a; …

Oracle Linux 8.10安装Oracle19c(19.3.0)完整教程

安装前请仔细将文档通读一遍&#xff0c;安装过程中根据安装命令仔细核对&#xff0c;特别留意一些字体加粗或标红的字样&#xff0c;遇到问题请及时咨询公司 1、基础环境 1.1、操作系统 cat /etc/redhat-release 1.2、主机名 医院默认分配的主机名可能跟其他主机会有重复&a…

Idea配置 阿里云 Spring Initializr URL

Idea默认Strart services url Idea中默认使用为https://start.spring.io/&#xff0c;国内网络如果不稳定创建工程会很慢修改为阿里云地址 https://start.aliyun.com/

局域网文件分发如何实现?掌握这4个秘籍,文件一键分发破次元!

局域网文件分发是许多企业和组织在日常工作中常见的需求&#xff0c; 有效的文件分发可以显著提高工作效率。 以下是四种实现局域网文件一键分发的秘籍&#xff1a; 1.使用终端监控软件的文件分发功能 软件示例&#xff1a;安企神等。 步骤简述&#xff1a; 安装软件&…

IP学习——oneday

1.什么是网络&#xff1f;为什么需要网络&#xff1f; 空间&#xff0c;时间&#xff1b;传统的邮件传输要考虑到距离&#xff0c;网络解决了空间距离&#xff08;太远&#xff09;、解决了时间问题&#xff08;旧音乐等&#xff09; 云:面向客户的虚拟化服务 运营商公司主营…

麒麟信安重庆渠道伙伴行业研讨会,共探国产化发展机遇

9月5日下午&#xff0c;麒麟信安举办重庆渠道伙伴行业研讨会。研讨会旨在探讨国产化浪潮下操作系统相关产业的发展机遇与挑战&#xff0c;以及如何在各关键领域实现市场拓展与应用&#xff0c;共商合作、共创未来。 会议伊始&#xff0c;麒麟信安详细阐述了公司以国产自主操作系…

攻防世界 unseping

unseping 攻防世界web新手练习 -unseping_攻防世界web新手题unseping-CSDN博客 这道题对我来说还是有点难&#xff0c;什么oct绕过命令执行第一次遇到捏&#xff0c;所以基本是跟着别人的wp写的&#xff0c;一点点记录吧 先对源码进行分析 <?php highlight_file(__FILE…

10款国民级企业文件加密系统介绍,究竟哪一个是你的菜?

A: “你知道为什么文件加密系统对企业至关重要吗&#xff1f;” B: “当然&#xff0c;随着数据泄露风险增加&#xff0c;文件加密成了保护敏感信息的必要手段。” A: “没错&#xff0c;它能确保即使文件被窃取&#xff0c;未授权者也无法轻易访问内容。” B: “而且&#…

解决SRS流媒体服务服务器无法接收客户端ipv6 RTMP推流的思路

这篇短文我不介绍SRS是什么&#xff0c;主要介绍一个场景问题&#xff0c;场景是你使用服务器并且部署了SRS服务配置成一个媒体流转发服务&#xff0c;也就是客户端往SRS流媒体服务器推流&#xff0c;然后SRS把流转推出去&#xff0c;但是会涉及到一个问题是&#xff1a;用户客…