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

news/2025/2/19 7:56:51/

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);@Overridepublic 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"));}@Overridepublic 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);}}@Overridepublic 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();@Overridepublic ResponseEntity<ExceptionResponse> translate(Exception e) throws Exception {log.error("OAuth2异常处理:", e);// Try to extract a SpringSecurityException from the stacktraceThrowable[] 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);}@Overridepublic String getOAuth2ErrorCode() {return "access_denied";}@Overridepublic int getHttpErrorCode() {return 403;}}private static class ServerErrorException extends OAuth2Exception {public ServerErrorException(String msg, Throwable t) {super(msg, t);}@Overridepublic String getOAuth2ErrorCode() {return "server_error";}@Overridepublic int getHttpErrorCode() {return 500;}}private static class UnauthorizedException extends OAuth2Exception {public UnauthorizedException(String msg, Throwable t) {super(msg, t);}@Overridepublic String getOAuth2ErrorCode() {return "unauthorized";}@Overridepublic int getHttpErrorCode() {return 401;}}private static class MethodNotAllowed extends OAuth2Exception {public MethodNotAllowed(String msg, Throwable t) {super(msg, t);}@Overridepublic String getOAuth2ErrorCode() {return "method_not_allowed";}@Overridepublic int getHttpErrorCode() {return 405;}}
}

http://www.ppmy.cn/news/1173129.html

相关文章

假脸检测:Exploring Decision-based Black-box Attacks on Face Forgery Detection

论文作者&#xff1a;Zhaoyu Chen,Bo Li,Kaixun Jiang,Shuang Wu,Shouhong Ding,Wenqiang Zhang 作者单位&#xff1a;Fudan University;Yiwu Research Institute of Fudan University 论文链接&#xff1a;http://arxiv.org/abs/2310.12017v1 内容简介&#xff1a; 1&…

docker和k8s之间的关系

一句话总结&#xff1a;Docker只是容器的一种&#xff0c;它面向的是单体&#xff0c;K8S可以管理多种容器&#xff0c;它面向的是集群&#xff0c;Docker可以作为一种容器方案被K8S管理。 https://baijiahao.baidu.com/s?id1763716289717819767&wfrspider&forpc 背…

kepler笔记:Trip

Trip图层可以显示动画路径 1 数据格式 目前Trip图层支持一种特殊的GeoJSON格式&#xff0c;其中坐标线串有一个表示时间戳的第4个元素 为了使路径动画化&#xff0c;GeoJSON数据需要在其特征的几何形状中包含LineString&#xff0c;并且LineString中的坐标需要有4个元素&…

JSX 动态类名控制

学习目标&#xff1a; 根据需求判断是否显示某个类名的样式 实现&#xff1a; 使用三元表达式或逻辑&&运算 import ./app.css; function App() {const color1 trueconst color2 truereturn (<div className"App">1. 三元&#xff1a;<div classN…

RK3568驱动指南|第七期-设备树-第59章 实例分析:CPU

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

5分钟搞懂分布式可观测性

可观测性是大规模分布式(微服务)系统的必要组件&#xff0c;没有可观测系统的支持&#xff0c;监控和调试分布式系统将是一场灾难。本文讨论了可观测系统的主要功能&#xff0c;并基于流行的开源工具搭建了一套可观测系统架构。原文: A Primer on Distributed Systems Observab…

最详细STM32,cubeMX 超声波测距

这篇文章将详细介绍 STM32使用 cubeMX驱动超声波测距 。 文章目录 前言一、超声波模块测距原理 &#xff1a; 二、cubeMX 配置三、实验程序总结 前言 实验材料&#xff1a;STM32F103C8T6开发板&#xff0c; HC-SR04 超声波模块。所需软件&#xff1a;keil5 &#xff0c; cubeM…

第十三章:L2JMobius学习 – 玩家攻击怪物

本章节&#xff0c;我们学习一下玩家周边怪物的刷新。在上一章节中&#xff0c;我们提过这个事情。当玩家移动完毕之后&#xff0c;会显示周围的游戏对象&#xff0c;其中就包括NPC怪物。当然&#xff0c;玩家“孵化”自己&#xff08;调用spawnMe方法&#xff09;的时候&#…