基于Spring Security OAuth2认证中心授权模式扩展

news2024/9/20 6:57:44

介绍

Spring Security OAuth2 默认实现的四种授权模式在实际的应用场景中往往满足不了预期。 需要扩展如下需求:

  • 手机号+短信验证码登陆
  • 微信授权登录

本次主要通过继承Spring Security OAuth2 抽象类和接口,来实现对oauth2/token接口的手机号+短信的认证授权。

代码

TechStack/springoatuh2

开发环境

  • JDK 17
  • Spring Boot 3

核心概念和流程

  • SecurityFilterChain: 表示Spring Security的过滤器链。实现安全配置和认证扩展配置
  • RegisteredClientRepository: 表示自定义的授权客户端信息,需要进行配置。这个客户端信息是oauth2/token中需要进行认证的信息。
  • AbstractAuthenticationToken: 表示用户认证信息。 需要对其进行扩展
  • AuthenticationProvider: 验证登录信息,实现token的生成。需要对其进行扩展
  • AuthenticationConverter: 实现对AbstractAuthenticationToken自定义扩展类的转换。

主要流程就是,实现上述AbstractAuthenticationToken、AuthenticationProvider、AuthenticationConverter三个抽象类和接口的扩展。并通过实现AuthenticationSuccessHandler扩展类,用来返回token给http response中。

AuthorizationServerConfig.java

Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        
        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
            .tokenEndpoint(tokenEndpoint -> tokenEndpoint
                    // 自定义授权模式转换器
                    .accessTokenRequestConverter(new MobilePhoneAuthenticationConverter())
                    .accessTokenRequestConverter(new UsernamePasswordGrantAuthenticationConverter())
                    // 自定义授权响应
                    .accessTokenResponseHandler(new CustomizerAuthenticationSuccessHandler())
                    .errorResponseHandler(new CustomizerAuthenticationFailureHandler())
            )
            .oidc(Customizer.withDefaults());

        http.exceptionHandling((exceptions) -> exceptions
            .authenticationEntryPoint((request, response, authException) -> {
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
                response.setContentType(MediaType.APPLICATION_JSON_VALUE);

                OAuth2Error error = new OAuth2Error(
                    "unauthorized",
                    authException.getMessage(),
                    "https://tools.ietf.org/html/rfc6750#section-3.1"
                );

                new ObjectMapper().writeValue(response.getOutputStream(), error);
            })
        );

         // 添加自定义的认证提供者
        http.authenticationProvider(mobilePhoneAuthenticationProvider);

        return http.build();
    }

    @Bean
    public RegisteredClientRepository registeredClientRepository(PasswordEncoder passwordEncoder) {
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
            .clientId("mobile-client")
            .clientSecret(passwordEncoder.encode("secret"))
            .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
            .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
            .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
            .authorizationGrantType(new AuthorizationGrantType("mobile_phone")) // 自定义授权类型
            .redirectUri("http://127.0.0.1:8080/login/oauth2/code/mobile-client")
            .scope("message.read")
            .scope("message.write")
                .tokenSettings(TokenSettings.builder()
                        .accessTokenFormat(OAuth2TokenFormat.REFERENCE) // 设置访问令牌格式为 REFERENCE
                        .build())
            .build();

        return new InMemoryRegisteredClientRepository(registeredClient);
    }

    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder().build();
    }

SecurityConfig.java

 @Bean
    @Order(2)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests((authorize) -> authorize
                .requestMatchers("/send-sms", "/oauth2/token").permitAll()
                .anyRequest().authenticated()
            )
            .csrf((csrf) -> csrf.ignoringRequestMatchers("/send-sms", "/oauth2/token"));

        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

MobilePhoneAuthenticationConverter.java

public class MobilePhoneAuthenticationConverter implements AuthenticationConverter {

    @Override
    public Authentication convert(HttpServletRequest request) {
        String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
        if (!"mobile_phone".equals(grantType)) {
            return null;
        }

        String phoneNumber = request.getParameter("phone_number");
        String smsCode = request.getParameter("sms_code");
        String clientId = request.getParameter(OAuth2ParameterNames.CLIENT_ID);

        if (phoneNumber == null || smsCode == null || clientId == null) {
            throw new OAuth2AuthenticationException(new OAuth2Error("invalid_request"));
        }

        return new MobilePhoneAuthenticationToken(phoneNumber, smsCode, clientId);
    }
}

MobilePhoneAuthenticationProvider.java

@Component
public class MobilePhoneAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private OAuth2AuthorizationService authorizationService;

    @Autowired
    private OAuth2TokenGenerator<OAuth2Token> tokenGenerator;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        MobilePhoneAuthenticationToken mobilePhoneAuthentication = (MobilePhoneAuthenticationToken) authentication;

        // 验证手机号和验证码的逻辑...
        String phoneNumber = (String) mobilePhoneAuthentication.getPrincipal();
        String smsCode = (String) mobilePhoneAuthentication.getCredentials();

        // 这里应该添加实际的验证逻辑
        if (!"123456".equals(smsCode)) {  // 示例验证,实际应该查询数据库或缓存
            throw new BadCredentialsException("Invalid SMS code");
        }

        OAuth2ClientAuthenticationToken clientPrincipal =
                getAuthenticatedClientElseThrowInvalidClient();
        RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();

        OAuth2TokenContext tokenContext = DefaultOAuth2TokenContext.builder()
                .registeredClient(registeredClient)
                .principal(mobilePhoneAuthentication)
                .authorizationServerContext(AuthorizationServerContextHolder.getContext())
                .authorizedScopes(registeredClient.getScopes())
                .tokenType(OAuth2TokenType.ACCESS_TOKEN)
                .authorizationGrantType(new AuthorizationGrantType("mobile_phone"))
                .build();


        OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
        if (!(generatedAccessToken instanceof OAuth2AccessToken)) {
            throw new OAuth2AuthenticationException(new OAuth2Error("server_error", "The token generator failed to generate the access token.", null));
        }

        OAuth2AccessToken accessToken = (OAuth2AccessToken) generatedAccessToken;

        OAuth2RefreshToken refreshToken = null;
        if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN)) {
            tokenContext = DefaultOAuth2TokenContext.builder()
                    .registeredClient(registeredClient)
                    .principal(mobilePhoneAuthentication)
                    .authorizationServerContext(AuthorizationServerContextHolder.getContext())
                    .authorizedScopes(registeredClient.getScopes())
                    .tokenType(OAuth2TokenType.REFRESH_TOKEN)
                    .authorizationGrantType(new AuthorizationGrantType("mobile_phone"))
                    .build();

            OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
            if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
                throw new OAuth2AuthenticationException(new OAuth2Error("server_error", "The token generator failed to generate the refresh token.", null));
            }
            refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
        }

        OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient)
                .principalName(phoneNumber)
                .authorizationGrantType(new AuthorizationGrantType("mobile_phone"))
                .token(accessToken)
                .refreshToken(refreshToken)
                .build();

        this.authorizationService.save(authorization);

        return new OAuth2AccessTokenAuthenticationToken(
                registeredClient, clientPrincipal, accessToken, refreshToken,  Collections.emptyMap());
    }

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

    private OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient() {
        // 这里需要实现获取当前认证的客户端逻辑
        // 例如,从 SecurityContextHolder 中获取
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication instanceof OAuth2ClientAuthenticationToken) {
            return (OAuth2ClientAuthenticationToken) authentication;
        }
        throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
    }

MobilePhoneAuthenticationToken.java

public class MobilePhoneAuthenticationToken extends AbstractAuthenticationToken {
    private final String phoneNumber;
    private final String smsCode;
    private final String clientId;

    public MobilePhoneAuthenticationToken(String phoneNumber, String smsCode, String clientId) {
        super(null);
        this.phoneNumber = phoneNumber;
        this.smsCode = smsCode;
        this.clientId = clientId;
        setAuthenticated(false);
    }

    public MobilePhoneAuthenticationToken(String phoneNumber, String smsCode, String clientId, Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.phoneNumber = phoneNumber;
        this.smsCode = smsCode;
        this.clientId = clientId;
        super.setAuthenticated(true);
    }

    @Override
    public Object getCredentials() {
        return this.smsCode;
    }

    @Override
    public Object getPrincipal() {
        return this.phoneNumber;
    }

    public String getClientId() {
        return this.clientId;
    }
}

测试验证

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

GD32F4开发 -- FATFS移植

之前已经讲了 GD32F4开发 – FATFS文件系统 现在将其一直到我的工程。 一、移植 在工程里创建FATFS文件夹。 移植正点原子 实验39 FATFS实验里的代码。 移植完后如下图&#xff1a; 注意&#xff1a;ffconf.h文件&#xff0c;找到对应宏并按照需求修改。 二、创建 FATFS 分…

最新中科院预警名单发布,多本高分区期刊被标记“On hold”(附20-24年所有名单)

2024年2月&#xff0c;期刊分区表团队发布2024年度《国际期刊预警名单 》。 最新版的《国际期刊预警名单》共有24本期刊&#xff0c;较23年版本的28本减少了4本&#xff0c;全部预警期刊当中&#xff0c;医学类数量最多&#xff0c;达11本。期刊JOURNAL OF BIOMATERIALS AND T…

高效率免费创作文章,4款ai写作生成器来帮忙

高效率免费创作文章&#xff0c;这对于每个创作者来说是非常不错的方法&#xff0c;即能提高创作效率&#xff0c;而且还能节省文章创作成本&#xff0c;但是想要高效率免费创作我们就需要找到相应的ai写作生成器来帮忙。因为如果是人工创作文章就需要耗费时间成本与人力成本的…

在pycharm终端中运行pip命令安装模块时,出现了“你要如何打开这个文件”弹出窗口,是什么状况?

这种情况发生在Windows系统上&#xff0c;当在PyCharm终端中运行pip命令安装模块时&#xff0c;如果系统无法确定要使用哪个程序打开该文件&#xff0c;就会出现“你要如何打开这个文件”弹出窗口。 解决方法是&#xff1a; 选择“查找一个应用于此文件”的选项。在弹出的窗口…

C++与C语言的区别

前言 本文主要用C语言和C做对比来学习C&#xff0c;便于个人理解。C包含C语言&#xff0c;是对C语言的扩展&#xff0c;在C中&#xff0c;支持C语言的语法使用&#xff0c;C是C语言的超集 一、C与C语言的区别 C语言简单高效&#xff0c;适合低级系统编程和硬件相关的开发。…

揭秘Web3新纪元:算力共享平台如何重塑数字世界的力量源泉

目录 一、Web3:算力共享的新舞台 二、技术革新:解锁算力的无限潜能 三、应用场景:算力如何改变世界 四、未来展望:算力共享的无尽可能 在区块链技术的浪潮中,Web3.0的曙光正引领我们迈向一个前所未有的数字时代。而在这场变革的洪流中,基于Web3的算力共享平台犹如一股…

Redis集群_主从复制

Redis集群基本概念 在实际项目中&#xff0c;一般不会只在一台机器上部署redis服务器&#xff0c;因为单台redis服务器不能满足高并发的压力&#xff0c;另外如果该服务器或者redis失效&#xff0c;整个系统就可能崩溃项目里一般会用主从复制的模式来提升性能&#xff0c;用集…

“精装朋友圈”的年轻人,开始在40度高温买羽绒服

文 | 螳螂观察 作者 | 如意 人生一世&#xff0c;苦了自己也不能苦朋友圈。 这届的年轻人&#xff0c;无论人生有多“毛坯”&#xff0c;都有一个一生要强的朋友圈&#xff0c;而且“装修”朋友圈还有一套哲学&#xff0c;信奉图片精修&#xff0c;排版讲究&#xff0c;文案…

OpenAI o1 Review 大模型PHD水平数理推理能力 OpenAI o1 vs GPT4o vs Gemini vs Claude

1. 介绍 OpenAI昨天发布了o1推理优化的大模型&#xff0c;利用了CoT (Chain of Thought) 思维链推理机制&#xff0c;提升了针对数学/物理/编程/逻辑等复杂问题的推理能力。OpenAI官方网站评测 OpenAI o1大模型对比GPT4o的数学、编程能力有显著提升。我们利用DeepNLP的AI Stor…

2024.9.13 Python与图像处理新国大EE5731课程大作业,SIFT 特征和描述符,单应性矩阵透视变换

1.SIFT特征点和描述符 import cv2 import numpy as np import matplotlib.pyplot as plt # read image img cv2.imread(im01.jpg,cv2.IMREAD_COLOR) gray cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) plt.imshow(gray,plt.cm.gray)提取图片&#xff0c;以灰度图像输出 #SIFT sift…

【免费分享】OpenHarmony鸿蒙物联网开发板资料包一网打尽,附教程/视频/项目/源码...

想要深入学习鸿蒙设备开发及鸿蒙物联网开发吗&#xff1f;现在机会来了&#xff01;我们为初学者们准备了一份全面的资料包&#xff0c;包括原理图、教程、视频、项目、源码等&#xff0c;所有资料全部免费领取&#xff0c;课程视频可试看&#xff08;购买后看完整版&#xff0…

带你深入了解C语言指针(二)

目录 前言一、数组名的理解二、使用指针访问数组三、⼀维数组传参的本质四、冒泡排序五、二级指针六、指针数组七、 指针数组模拟⼆维数组总结 前言 前面我们基本了解了C语言指针的概念&#xff0c;也初步开启了指针的用处&#xff0c;这期我们主要围绕数 组展开&#xff0c;也…

学生请假管理系统

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 学生请假管理系统拥有两种角色 管理员&#xff1a;班级管理、课程管理、学生管理、审核请假信息、导出请假单 学生&#xff1a;填写请假单、查看请假审核情况 1.1 背景描述 学生请假管…

GIS应届生不考研,不考公,不考编,未来要怎么安排?

01 考公考研or就业 一直以来&#xff0c;大学生毕业去向的话题就居高不下。 近日&#xff0c;便有“不考研不考公&#xff0c;未来要怎么样&#xff1f;”的话题&#xff0c;出现在某社交媒体热榜&#xff0c;迅速引起54.5万人围观。 国内本科毕业后大学生的出路无外乎&…

什么空气净化器可以除猫毛?范罗士、希喂、小米、IAM、安德迈横测谁是毛克星

秋季掉毛季来咯&#xff0c;每入掉毛季&#xff0c;我们医院鱼油销量都暴涨。都是被家里猫猫、狗狗掉毛折腾得没办法了&#xff0c;想喂点鱼油&#xff0c;减少点掉毛。鱼油含有丰富的Ω-3&#xff0c;能够调节皮脂分泌&#xff0c;减轻炎症反应。平时喂点对宠物身体是有好处的…

矩阵引流助手有试用吗

矩阵引流助手有试用吗 还在为内容和流量曝光发愁吗&#xff1f;来了解一下矩阵工具让你事半功倍 #矩阵工具 #矩阵管理 #矩阵引流 推荐阅读&#xff1a; 短视频代运营代发 短视频代运营代发帖https://www.bsw80.com/post/111.html 抖音通过矩阵获客&#xff0c;我告诉大家新上…

Qt/C++ 了解NTFS文件系统,解析盘符引导扇区数据获取MFT(Master File Table)主文件表偏移地址

系列文章目录 一、Qt/C 了解NTFS文件系统&#xff0c;了解MFT(Master File Table)主文件表&#xff08;一&#xff09; 二、Qt/C 了解NTFS文件系统&#xff0c;解析盘符引导扇区数据获取MFT(Master File Table)主文件表偏移地址 目录导读 系列文章目录前言BOOTICE工具介绍读取…

防止文件外发泄密有什么方法?这7防外发方式可以看下!

防止文件外发泄密有什么方法&#xff1f; 一、使用防泄密软件外发&#xff1a;可对发送的文件进行权限设定。接收&#xff1a;可查看次数、可查看时间复制、修改、打印、外发受到限制。文件外发控制&#xff1a;以对外发的文件进行权限设定&#xff0c;如可打开的次数、可打开时…

基于JavaWeb开发的java ssm springboot+VUE疫情防疫系统系统前后端分离设计和实现

基于JavaWeb开发的java ssm springbootVUE疫情防疫系统系统前后端分离设计和实现 &#x1f345; 作者主页 网顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; &#x1f345; 查看下方微信号获取…

【深度学习】【OnnxRuntime】【Python】模型转化、环境搭建以及模型部署的详细教程

【深度学习】【OnnxRuntime】【Python】模型转化、环境搭建以及模型部署的详细教程 提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论 文章目录 【深度学习】【OnnxRuntime】【Python】模型转化、环境搭建以及模型部署的详细教程前言模型转换--pytorch转on…