【Java】Spring Cloud OAuth2之密码模式实战

news2024/12/25 8:43:27

Spring Cloud OAuth2

代码地址:https://gitee.com/kkmy/kw-microservices.git
(又是一年1024,分享一下之前搭的OAuth2服务)

OAuth2依赖版本

 <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>

代码工程结构

在这里插入图片描述

核心代码配置

SecurityConfig

  • 密码模式配置 BCryptPasswordEncoder
  • 自定义用户信息认证myUserDetailsService
  • 暴露authenticationManagerBean
  • 安全参数配置configure()
MyUserDetailsService

这里使用了策略模式,根据传来的系统类型,调用对应系统服务的接口

package pers.kw.config.security;

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.stereotype.Component;
import pers.kw.common.spring.utils.SpringUtils;
import pers.kw.config.oauth.context.MyParamValue;
import pers.kw.config.oauth.context.MyParamValueThreadLocal;
import pers.kw.contants.AuthParamName;
import pers.kw.enums.AuthUserTypeEnum;
import pers.kw.service.UserDetailStrategy;

import java.util.ArrayList;
import java.util.List;

/**
 * 自定义UserDetailService
 */
@Component
public class MyUserDetailsService implements UserDetailsService {

    private static final Logger log = LoggerFactory.getLogger(MyUserDetailsService.class);

    private static final List<GrantedAuthority> authorities = new ArrayList<>(2);

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        log.info("自定义UserDetailsService处理start...");
        MyParamValue paramValue = MyParamValueThreadLocal.getCurrent();
        log.info("获取自定义参数信息:{}", JSON.toJSONString(paramValue));

        String userType = paramValue.getAuthParameter(AuthParamName.USER_TYPE);
        if (StringUtils.isBlank(userType)) {
            throw new OAuth2Exception(AuthParamName.USER_TYPE + "不能为空");
        }
        if (!AuthUserTypeEnum.userTypeSet.contains(userType)) {
            throw new OAuth2Exception(AuthParamName.USER_TYPE + "错误");
        }
        AuthUserTypeEnum userTypeEnum = AuthUserTypeEnum.getEnumObjByCode(userType);
        if (userTypeEnum == null) {
            log.info("oauth服务,用户认证策略配置错误,{}:{}", AuthParamName.USER_TYPE, userType);
            throw new OAuth2Exception("认证系统异常");
        }

        try {
            UserDetailStrategy userDetailStrategy = (UserDetailStrategy) SpringUtils.getBean(Class.forName(userTypeEnum.getUserStrategy()));
            return userDetailStrategy.getUserInfoByMobile(userName,authorities);
        } catch (ClassNotFoundException e) {
            log.error("oauth服务,用户认证策略配置获取异常", e);
            throw new OAuth2Exception("认证系统异常");
        }
    }
}

AuthorizationServerConfig

  • 授权服务安全认证配置configure(AuthorizationServerSecurityConfigurer security)
    • 自定义客户端异常处理过滤器(basic方式认证)
  • 客户端信息配置configure(ClientDetailsServiceConfigurer clients)
  • 授权服务端点配置configure(AuthorizationServerEndpointsConfigurer endpoints)
    • 自定义异常信息返回值(授权码模式、密码模式)
    • 设置token请求方式
    • token信息配置
通过添加自定义过滤器,实现对oauth标准接口增加自定义参数

MyOauthAuthenticationFilter

package pers.kw.config.oauth;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.GenericFilterBean;
import pers.kw.config.oauth.context.MyParamValue;
import pers.kw.config.oauth.context.MyParamValueThreadLocal;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 通过添加自定义过滤器,实现对oauth标准接口增加自定义参数
 */
@Component
public class MyOauthAuthenticationFilter extends GenericFilterBean implements ApplicationContextAware {

    private static final Logger log = LoggerFactory.getLogger(MyOauthAuthenticationFilter.class);

    private ApplicationContext applicationContext;

    private final RequestMatcher requestMatcher;

    private static final String URL = "/oauth/token";

    public MyOauthAuthenticationFilter() {
        this.requestMatcher = new OrRequestMatcher(
                new AntPathRequestMatcher(URL, "GET"),
                new AntPathRequestMatcher(URL, "POST")
        );
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        if (requestMatcher.matches(request)) {
            //将自定义参数,保存到当前本地线程中
            MyParamValue paramValue = new MyParamValue();
            paramValue.setAuthParameters(request.getParameterMap());
            MyParamValueThreadLocal.set(paramValue);
            filterChain.doFilter(request, response);
            //执行完成,清除线程本地变量
            MyParamValueThreadLocal.remove();
        } else {
            filterChain.doFilter(request, response);
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

自定义异常信息返回值MyWebResponseExceptionTranslator

这里的响应码一定要设置为200,若取oauth2返回的非200响应码,在微服务调用过程中,返回值无法被正常序列化

return new ResponseEntity<>(
                ExceptionResponse.fail(status,
                        e.getMessage())
                , headers,
                HttpStatus.OK);

crm调用auth服务的feign接口
在这里插入图片描述

package pers.kw.config.oauth;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.DefaultThrowableAnalyzer;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InsufficientScopeException;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.web.util.ThrowableAnalyzer;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import pers.kw.protocol.ExceptionResponse;

import java.io.IOException;

public class MyWebResponseExceptionTranslator implements WebResponseExceptionTranslator {

    private static final Logger log = LoggerFactory.getLogger(MyWebResponseExceptionTranslator.class);

    private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();


    @Override
    public ResponseEntity<ExceptionResponse> translate(Exception e) throws Exception {
        log.error("OAuth2异常处理:", e);
        // Try to extract a SpringSecurityException from the stacktrace
        Throwable[] causeChain = throwableAnalyzer.determineCauseChain(e);
        Exception ase = (OAuth2Exception) throwableAnalyzer.getFirstThrowableOfType(OAuth2Exception.class, causeChain);

        if (ase != null) {
            if (ase instanceof InvalidGrantException) {
                log.info("ase:{}", ase.getMessage());
                return handleOAuth2Exception((OAuth2Exception) ase, "密码错误");
            }
            return handleOAuth2Exception((OAuth2Exception) ase);
        }

        ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class,
                causeChain);
        if (ase != null) {
            return handleOAuth2Exception(new UnauthorizedException(e.getMessage(), e));
        }

        ase = (AccessDeniedException) throwableAnalyzer
                .getFirstThrowableOfType(AccessDeniedException.class, causeChain);
        if (ase != null) {
            return handleOAuth2Exception(new ForbiddenException(ase.getMessage(), ase));
        }

        ase = (HttpRequestMethodNotSupportedException) throwableAnalyzer.getFirstThrowableOfType(
                HttpRequestMethodNotSupportedException.class, causeChain);
        if (ase != null) {
            return handleOAuth2Exception(new MethodNotAllowed(ase.getMessage(), ase));
        }

        return handleOAuth2Exception(new ServerErrorException(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), e));
    }

    private ResponseEntity<ExceptionResponse> handleOAuth2Exception(OAuth2Exception e, String msg) throws IOException {

        int status = e.getHttpErrorCode();

        HttpHeaders headers = new HttpHeaders();
        headers.set("Cache-Control", "no-store");
        headers.set("Pragma", "no-cache");
        if (status == HttpStatus.UNAUTHORIZED.value() || (e instanceof InsufficientScopeException)) {
            headers.set("WWW-Authenticate", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, e.getSummary()));
        }
        //HttpStatus.valueOf(status)
        return new ResponseEntity<>(
                ExceptionResponse.fail(status,
                        msg)
                , headers, HttpStatus.OK
        );

    }

    private ResponseEntity<ExceptionResponse> handleOAuth2Exception(OAuth2Exception e) throws IOException {

        int status = e.getHttpErrorCode();
        HttpHeaders headers = new HttpHeaders();
        headers.set("Cache-Control", "no-store");
        headers.set("Pragma", "no-cache");
        if (status == HttpStatus.UNAUTHORIZED.value() || (e instanceof InsufficientScopeException)) {
            headers.set("WWW-Authenticate", String.format("%s %s", OAuth2AccessToken.BEARER_TYPE, e.getSummary()));
        }

        return new ResponseEntity<>(
                ExceptionResponse.fail(status,
                        e.getMessage())
                , headers,
                HttpStatus.OK);

    }

    public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) {
        this.throwableAnalyzer = throwableAnalyzer;
    }

    private static class ForbiddenException extends OAuth2Exception {

        public ForbiddenException(String msg, Throwable t) {
            super(msg, t);
        }

        @Override
        public String getOAuth2ErrorCode() {
            return "access_denied";
        }

        @Override
        public int getHttpErrorCode() {
            return 403;
        }

    }

    private static class ServerErrorException extends OAuth2Exception {

        public ServerErrorException(String msg, Throwable t) {
            super(msg, t);
        }

        @Override
        public String getOAuth2ErrorCode() {
            return "server_error";
        }

        @Override
        public int getHttpErrorCode() {
            return 500;
        }

    }

    private static class UnauthorizedException extends OAuth2Exception {

        public UnauthorizedException(String msg, Throwable t) {
            super(msg, t);
        }

        @Override
        public String getOAuth2ErrorCode() {
            return "unauthorized";
        }

        @Override
        public int getHttpErrorCode() {
            return 401;
        }

    }

    private static class MethodNotAllowed extends OAuth2Exception {

        public MethodNotAllowed(String msg, Throwable t) {
            super(msg, t);
        }

        @Override
        public String getOAuth2ErrorCode() {
            return "method_not_allowed";
        }

        @Override
        public int getHttpErrorCode() {
            return 405;
        }

    }
}

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

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

相关文章

Kubernetes 部署 kubeflow1.6.1

前言 安装前请注意捋清楚版本关系&#xff0c;如kubeflow版本对应的K8S版本及其相关工具版本等等 我们此处使用的是是kubeflow-1.6.1和K8s-v1.22.8 单机部署 部署K8S 初始化Linux 1.关闭selinux setenforce 0 && sed -i "s/SELINUXenforcing/SELINUXdisable…

flutter开发实战-hero动画简单实现

flutter开发实战-hero动画简单实现 使用Flutter的Hero widget创建hero动画。 将hero从一个路由飞到另一个路由。 将hero 的形状从圆形转换为矩形,同时将其从一个路由飞到另一个路由的过程中进行动画处理。 Flutter Hero动画 Hero 指的是可以在路由(页面)之间“飞行”的 widge…

应用在PC机中的低功耗触摸感应芯片

PC机一般指个人计算机。个人计算机是指一种大小、价格和性能适用于个人使用的多用途计算机。台式机、笔记本电脑、小型笔记本电脑、平板电脑以及超级本等都属于个人计算机。计算机的发展主要按照构成计算机的电子元器件来划分&#xff0c;共分为四个阶段&#xff0c;即电子管阶…

作为语雀的深度用户,谈谈语雀崩溃

文章目录 语雀简介语雀不可用接近8小时&#xff0c;我的感受谈谈云端存储的主要缺点其他软件如何解决云端存储的缺点总结 语雀简介 在数字化时代&#xff0c;云端服务扮演着关键的角色&#xff0c;为个人和企业提供了各种在线服务。其中&#xff0c;协作与知识管理工具变得越来…

自考02378《信息资源管理》第一章信息资源管理基础——思维导图

备战2024年04月自考科目02378《信息资源管理》第一章信息资源管理基础 思维导图如下&#xff1a; 以上便是本文的全部内容了&#xff0c;不知道对你有没有帮助呢。 我会认真写好每一篇文章&#xff0c;一直努力下去&#xff01;

从京东API接口,三个数字,带你认识真正的京东工业

京东工业赴港上市&#xff0c;带着非常优秀的成绩。 招股书显示&#xff0c;2022年实现交易额223亿元&#xff0c;营收141亿元&#xff0c;调整后净利润7亿元。短短六年时间&#xff0c;已成为中国工业供应链技术与服务市场领导者。 京东工业与传统工业品贸易商有何不同&#x…

【2021集创赛】Digilent杯二等奖:基于FPGA的动态视觉感知融合的运动目标检测系统

杯赛题目&#xff1a;Diligent杯&#xff1a;基于FPGA开源软核的硬件加速智能平台 参赛组别&#xff1a;A组 设计任务&#xff1a; 利用业界主流软核处理器(仅限于Cortex-M系列及 RISC-V系列)在限定的DIGILENT官方FPGA平台上构建SoC片上系统&#xff0c;在 SoC中添加面向智能应…

猿辅导发布博物馆新知计划,上线文物科普记录片《文物也有AB面》

博物馆里有什么&#xff1f;文物&#xff0c;可能是大多数人脱口而出的答案。博物馆拥有包罗万象的文物&#xff0c;不仅能够传递知识&#xff0c;提供艺术养分&#xff0c;更有助于青少年增强文化自信和文化传承的使命感。一座博物馆就像一所大学校&#xff0c;一个能够普及知…

lwip多网卡自适应选择

当系统中有多个网卡时&#xff0c;lwip会选择第一个网卡作为默认网卡&#xff0c;ping、tftp、iperf都会选择第一个网卡来进行&#xff0c;没有办法使用第二个网卡&#xff08;一些命令可以通过-i选项选择网卡&#xff0c;有些命令则没有提供&#xff09;&#xff0c;此时需要修…

NSS [SWPUCTF 2021 新生赛]PseudoProtocols

NSS [SWPUCTF 2021 新生赛]PseudoProtocols 先看题目&#xff0c;题目要求我们先找到hint.php。 看这个get请求头&#xff0c;我们先用php://filter协议读一波 得到提示&#xff0c;让我们前往/test2222222222222.php 源码如下 <?php ini_set("max_execution_time&qu…

Excel怎么合并单元格?这4个方法很简单!

“有没有朋友知道Excel合并单元格应该怎么操作呀&#xff1f;在制作工作报表中&#xff0c;需要对Excel单元格进行合并操作&#xff0c;但是我不太熟悉详细的操作&#xff0c;希望大家帮帮我&#xff01;” 在Excel中&#xff0c;合并单元格是一项常用的操作&#xff0c;用于改…

【Docker】Dockerfile使用技巧

开启Buildkit BuildKit是Docker官方社区推出的下一代镜像构建神器&#xff0c;可以更加快速&#xff0c;有效&#xff0c;安全地构建docker镜像。 尽管目前BuildKit不是Docker的默认构建工具&#xff0c;但是完全可以考虑将其作为Docker&#xff08;v18.09&#xff09;的首选…

基本的爬虫工作原理

爬虫是一种自动化程序&#xff0c;能够模拟人类的浏览行为&#xff0c;从网络上获取数据。爬虫的工作原理主要包括网页请求、数据解析和数据存储等几个步骤。本文将详细介绍爬虫的基本工作原理&#xff0c;帮助读者更好地理解和应用爬虫技术。 首先&#xff0c;爬虫的第一步是…

CTFHub-SSRF-读取伪协议

WEB攻防-SSRF服务端请求&Gopher伪协议&无回显利用&黑白盒挖掘&业务功能点-CSDN博客 伪协议有&#xff1a; file:/// — 访问本地文件系统 http:/// — 访问 HTTP(s) 网址 ftp:/// — 访问 FTP(s) URLs php:/// — 访问各个输入/输出流(I/O streams) dic…

NSS [NCTF 2018]滴!晨跑打卡

NSS [NCTF 2018]滴!晨跑打卡 很明显是sql注入 输入一个1&#xff0c;语句直接显示了&#xff0c;非常的真诚和坦率 简单尝试了一下&#xff0c;发现有waf&#xff0c;过滤了空格 拿burp跑一下fuzz&#xff0c;看看有多少过滤 过滤了# * - 空格那我们无法通过#或者–来注释掉…

如何在 Azure 容器应用程序上部署具有 Elastic Observability 的 Hello World Web 应用程序

作者&#xff1a;Jonathan Simon Elastic Observability 是提供对正在运行的 Web 应用程序的可见性的最佳工具。 Microsoft Azure 容器应用程序是一个完全托管的环境&#xff0c;使你能够在无服务器平台上运行容器化应用程序&#xff0c;以便你的应用程序可以扩展和缩减。 这使…

华为云 CodeArts Snap 智能编程助手 PyCharm 插件安装与使用指南

1 插件安装下载 1.1 搜索插件 打开 PyCharm&#xff0c;选择 File&#xff0c;点击 Settings。 选择 Plugins&#xff0c;点击 Marketplace&#xff0c;并在搜索框中输入 Huawei Cloud CodeArts Snap。 1.2 安装插件 如上图所示&#xff0c;点击 Install 按钮安装 Huawei Cl…

C#调用C/C++从零深入讲解

C#调用非托管DLL从零深入讲解 一、结构对齐 结构对齐是C#调用非托管DLL的必备知识。 在没有#pragma pack声明下结构体内存对齐的规则为: 第一个成员的偏移量为0,每个成员的首地址为自身大小的整数倍子结构体的第一个成员偏移量应当是子结构体最大成员的整数倍结构体总大小…

内衣洗衣机和手洗哪个干净?内衣洗衣机热销第一名

这两年内衣洗衣机可以称得上较火的小电器&#xff0c;小小的身躯却有大大的能力&#xff0c;一键可以同时启动洗、漂、脱三种全自动为一体化功能&#xff0c;在多功能和性能的提升上&#xff0c;还可以解放我们双手的同时将衣物给清洗干净&#xff0c;让越来越多小伙伴选择一款…

【深度学习 | 核心概念】那些深度学习路上必经的 常见问题解决方案及最佳实践,确定不来看看? (一)

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…