Spring Security如何优雅的增加OAuth2协议授权模式

一、什么是OAuth2协议?

OAuth 2.0 是一个关于授权的开放的网络协议,是目前最流行的授权机制。

数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。

由于授权的场景众多,OAuth 2.0 协议定义了获取令牌的四种授权方式,分别是:

  • 授权码模式:授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。
  • 简化模式:简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。
  • 密码模式:密码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向"服务商提供商"索要授权。
  • 客户端模式:客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。

四种授权模式分别使用不同的 grant_type 来区分

二、为什么要自定义授权类型?

虽然 OAuth2 协议定义了4种标准的授权模式,但是在实际开发过程中还是远远满足不了各种变态的业务场景,需要我们去扩展。

例如增加图形验证码、手机验证码、手机号密码登录等等的场景

而常见的做法都是通过增加 过滤器Filter 的方式来扩展 Spring Security 授权,但是这样的实现方式有两个问题:

  • 脱离了 OAuth2 的管理
  • 不灵活:例如系统使用 密码模式 授权,网页版需要增加图形验证码校验,但是手机端APP又不需要的情况下,使用增加 Filter 的方式去实现就比较麻烦了。

所以目前在 Spring Security 中比较优雅和灵活的扩展方式就是通过自定义 grant_type 来增加授权模式。

三、实现思路

在扩展之前首先需要先了解 Spring Security 的整个授权流程,我以 密码模式 为例去展开分析,如下图所示

3.1. 流程分析

整个授权流程关键点分为以下两个部分:

第一部分:关于授权类型 grant_type 的解析

  • 每种 grant_type 都会有一个对应的 TokenGranter 实现类。
  • 所有 TokenGranter 实现类都通过 CompositeTokenGranter 中的 tokenGranters 集合存起来。
  • 然后通过判断 grantType 参数来定位具体使用那个 TokenGranter 实现类来处理授权。

第二部分:关于授权登录逻辑

  • 每种 授权方式 都会有一个对应的 AuthenticationProvider 实现类来实现。
  • 所有 AuthenticationProvider 实现类都通过 ProviderManager 中的 providers 集合存起来。
  • TokenGranter 类会 new 一个 AuthenticationToken 实现类,如 UsernamePasswordAuthenticationToken 传给 ProviderManager 类。
  • ProviderManager 则通过 AuthenticationToken 来判断具体使用那个 AuthenticationProvider 实现类来处理授权。

具体的登录逻辑由 AuthenticationProvider 实现类来实现,如 DaoAuthenticationProvider

 3.2. 扩展分析

根据上面的流程,扩展分为以下两种场景

场景一:只对原有的授权逻辑进行增强或者扩展,如:用户名密码登录前增加图形验证码校验。

该场景需要定义一个新的 grantType 类型,并新增对应的 TokenGranter 实现类 添加扩展内容,然后加到 CompositeTokenGranter 中的 tokenGranters 集合里即可。

参考代码:PwdImgCodeGranter.java

场景二:新加一种授权方式,如:手机号加密码登录。

该场景需要实现以下内容:

  • 定义一个新的 grantType 类型,并新增对应的 TokenGranter 实现类添加到 CompositeTokenGranter 中的 tokenGranters 集合里
  • 新增一个 AuthenticationToken 实现类,用于存放该授权所需的信息。
  • 新增一个 AuthenticationProvider 实现类 实现授权的逻辑,并重写 supports 方法绑定步骤二的 AuthenticationToken 实现类

参考代码:MobilePwdGranter.java

 四、代码实现

下面以 场景二 新增手机号加密码授权方式为例,展示核心的代码实现

4.1. 创建 AuthenticationToken 实现类

创建 MobileAuthenticationToken 类,用于存储手机号和密码信息

public class MobileAuthenticationToken extends AbstractAuthenticationToken {
	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

	private final Object principal;
	private Object credentials;

	public MobileAuthenticationToken(String mobile, String password) {
		super(null);
		this.principal = mobile;
		this.credentials = password;
		setAuthenticated(false);
	}

	public MobileAuthenticationToken(Object principal, Object credentials,
									 Collection<? extends GrantedAuthority> authorities) {
		super(authorities);
		this.principal = principal;
		this.credentials = credentials;
		super.setAuthenticated(true);
	}

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

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

	@Override
	public void setAuthenticated(boolean isAuthenticated) {
		if (isAuthenticated) {
			throw new IllegalArgumentException(
					"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
		}
		super.setAuthenticated(false);
	}

	@Override
	public void eraseCredentials() {
		super.eraseCredentials();
	}
}

4.2. 创建 AuthenticationProvider 实现类

创建 MobileAuthenticationProvider 类,实现登录逻辑,并绑定 MobileAuthenticationToken

@Setter
public class MobileAuthenticationProvider implements AuthenticationProvider {
  private ZltUserDetailsService userDetailsService;
  private PasswordEncoder passwordEncoder;

  @Override
  public Authentication authenticate(Authentication authentication) {
    MobileAuthenticationToken authenticationToken = (MobileAuthenticationToken) authentication;
    String mobile = (String) authenticationToken.getPrincipal();
    String password = (String) authenticationToken.getCredentials();
    UserDetails user = userDetailsService.loadUserByMobile(mobile);
    if (user == null) {
      throw new InternalAuthenticationServiceException("手机号或密码错误");
    }
    if (!passwordEncoder.matches(password, user.getPassword())) {
      throw new BadCredentialsException("手机号或密码错误");
    }
    MobileAuthenticationToken authenticationResult = new MobileAuthenticationToken(user, password, user.getAuthorities());
    authenticationResult.setDetails(authenticationToken.getDetails());
    return authenticationResult;
  }

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

4.3. 创建 TokenGranter 实现类

创建 MobilePwdGranter 类并定义 grant_type 的值为 mobile_password

public class MobilePwdGranter extends AbstractTokenGranter {
  private static final String GRANT_TYPE = "mobile_password";

  private final AuthenticationManager authenticationManager;

  public MobilePwdGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices
      , ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
    super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
    this.authenticationManager = authenticationManager;
  }

  @Override
  protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
    Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());
    String mobile = parameters.get("mobile");
    String password = parameters.get("password");
    parameters.remove("password");

    Authentication userAuth = new MobileAuthenticationToken(mobile, password);
    ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
    userAuth = authenticationManager.authenticate(userAuth);
    if (userAuth == null || !userAuth.isAuthenticated()) {
      throw new InvalidGrantException("Could not authenticate mobile: " + mobile);
    }

    OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
    return new OAuth2Authentication(storedOAuth2Request, userAuth);
  }
}

4.4. 加到 CompositeTokenGranter 中的集合里

// 添加手机号加密码授权模式
tokenGranters.add(new MobilePwdGranter(authenticationManager, tokenServices, clientDetailsService, requestFactory));

4.5. 测试

使用以下地址,指定 grant_typemobile_password 进行授权获取 access_token

/oauth/token?grant_type=mobile_password&mobile={mobile}&password={password}

五、参考样例

详细的代码实现可以参考

https://gitee.com/zlt2000/microservices-platform/tree/master/zlt-uaa

到此这篇关于Spring Security如何优雅的增加OAuth2协议授权模式的文章就介绍到这了,更多相关Spring Security OAuth2内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Spring Security OAuth2实现使用JWT的示例代码

    1.概括 在博客中,我们将讨论如何让Spring Security OAuth2实现使用JSON Web Tokens. 2.Maven 配置 首先,我们需要在我们的pom.xml中添加spring-security-jwt依赖项. <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-jwt</artifactId> &l

  • 基于Spring Security的Oauth2授权实现方法

    前言 经过一段时间的学习Oauth2,在网上也借鉴学习了一些大牛的经验,推荐在学习的过程中多看几遍阮一峰的<理解OAuth 2.0>,经过对Oauth2的多种方式的实现,个人推荐Spring Security和Oauth2的实现是相对优雅的,理由如下: 1.相对于直接实现Oauth2,减少了很多代码量,也就减少的查找问题的成本. 2.通过调整配置文件,灵活配置Oauth相关配置. 3.通过结合路由组件(如zuul),更好的实现微服务权限控制扩展. Oauth2概述 oauth2根据使用场景不同

  • 使用Spring Security OAuth2实现单点登录

    1.概述 在本教程中,我们将讨论如何使用Spring Security OAuth和Spring Boot实现SSO - 单点登录. 我们将使用三个单独的应用程序: •授权服务器 - 这是中央身份验证机制 •两个客户端应用程序:使用SSO的应用程序 非常简单地说,当用户试图访问客户端应用程序中的安全页面时,他们将被重定向到首先通过身份验证服务器进行身份验证. 我们将使用OAuth2中的授权代码授权类型来驱动身份验证委派. 2.客户端应用程序 让我们从客户端应用程序开始;当然,我们将使用Sprin

  • Spring Security整合Oauth2实现流程详解

    一.创建项目并导入依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <art

  • Spring Security OAuth2认证授权示例详解

    本文介绍了如何使用Spring Security OAuth2构建一个授权服务器来验证用户身份以提供access_token,并使用这个access_token来从资源服务器请求数据. 1.概述 OAuth2是一种授权方法,用于通过HTTP协议提供对受保护资源的访问.首先,OAuth2使第三方应用程序能够获得对HTTP服务的有限访问权限,然后通过资源所有者和HTTP服务之间的批准交互来让第三方应用程序代表资源所有者获取访问权限. 1.1 角色 OAuth定义了四个角色 资源所有者 - 应用程序的

  • Spring Security OAuth2 token权限隔离实例解析

    这篇文章主要介绍了Spring Security OAuth2 token权限隔离实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 由于项目OAuth2采用了多种模式,授权码模式为第三方系统接入,密码模式用于用户登录,Client模式用于服务间调用, 所有不同的模式下的token需要用 @PreAuthorize("hasAuthority('client')") 进行隔离,遇到问题一直验证不通过. 通过调试发现资源服务从授权服

  • Spring Security OAuth2 授权码模式的实现

    写在前边 在文章OAuth 2.0 概念及授权流程梳理 中我们谈到OAuth 2.0的概念与流程,这里我准备分别记一记这几种授权模式的demo,一方面为自己的最近的学习做个总结,另一方面做下知识输出,如果文中有错误的地方,请评论指正,在此不胜感激 受众前提 阅读本文,默认读者已经过Spring Security有一定的了解,对OAuth2流程有一定了解 本文目标 带领读者对Spring Security OAuth2框架的授权码模式有一个比较直观的概念,能使用框架搭建授权码模式授权服务器与资源服

  • Spring Security如何优雅的增加OAuth2协议授权模式

    一.什么是OAuth2协议? OAuth 2.0 是一个关于授权的开放的网络协议,是目前最流行的授权机制. 数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据.系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用. 由于授权的场景众多,OAuth 2.0 协议定义了获取令牌的四种授权方式,分别是: 授权码模式:授权码模式(authorization code)是功能最完整.流程最严密的授权模式.它的特点就是通过客户端的后台服务器,与"服务提供商"的认

  • Spring Security实现微信公众号网页授权功能

    微信公众号提供了微信支付.微信优惠券.微信H5红包.微信红包封面等等促销工具来帮助我们的应用拉新保活.但是这些福利要想正确地发放到用户的手里就必须拿到用户特定的(微信应用)微信标识openid甚至是用户的微信用户信息.如果用户在微信客户端中访问我们第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑.今天就结合Spring Security来实现一下微信公众号网页授权. 环境准备 在开始之前我们需要准备好微信网页开发的环境. 微信公众号服务号 请注意,一定是微信公众号

  • SpringBoot集成Spring Security的方法

    至今Java能够如此的火爆Spring做出了很大的贡献,它的出现让Java程序的编写更为简单灵活,而Spring如今也形成了自己的生态圈,今天咱们探讨的是Spring旗下的一个款认证工具:SpringSecurity,如今认证框架主流"shiro"和"SpringSecurity",由于和Spring的无缝衔接,使用SpringSecurity的企业也越来越多. 1.Spring Security介绍 Spring security,是一个强大的和高度可定制的身份验

  • Spring Security 安全框架应用原理解析

    Spring Security 简介 背景分析 企业中数据是最重要的资源,对于这些数据而言,有些可以直接匿名访问,有些只能登录以后才能访问,还有一些你登录成功以后,权限不够也不能访问.总之这些规则都是保护系统资源不被破坏的一种手段.几乎每个系统中都需要这样的措施对数据(资源)进行保护.我们通常会通过软件技术对这样业务进行具体的设计和实现.早期没有统一的标准,每个系统都有自己独立的设计实现,但是对于这个业务又是一个共性,后续市场上就基于共享做了具体的落地实现,例如Spring Security,A

  • Spring security自定义用户认证流程详解

    1.自定义登录页面 (1)首先在static目录下面创建login.html 注意:springboot项目默认可以访问resources/resources,resources/staic,resources/public目录下面的静态文件 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登录页面</titl

  • Spring Security如何在Servlet中执行

    Spring Security 是一个强大的认证和授权框架,它的使用方式也非常简单,但是要想真正理解它就需要花一时间来学习了,最近在学习 Spring Security 时有一些新的理解,特意记录下来防止知识忘记的太快,毕竟好记性不如烂笔关,也给即将准备学习 Spring Security 的同志做一个参考. 由于我在学习和使用是基于 Servlet Applications 的,所以文中的大部分都与 Servlet 相关,当然 Spring Security 还支持 Reactive Appl

  • Spring Security和自定义filter的冲突导致多执行的解决方案

    问题描述: 使用Spring Security时,在WebSecurityConfig中需要通过@bean注解注入Security的filter对象,但是不知是不是因为spring boot框架的原因还是什么未知原因,导致在这里注入,就会多注入一次这个对象,导致filter链走完之后,又会回到这个filter中再执行一次. @Bean public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Except

  • Spring Security OAuth2集成短信验证码登录以及第三方登录

    前言 基于SpringCloud做微服务架构分布式系统时,OAuth2.0作为认证的业内标准,Spring Security OAuth2也提供了全套的解决方案来支持在Spring Cloud/Spring Boot环境下使用OAuth2.0,提供了开箱即用的组件.但是在开发过程中我们会发现由于Spring Security OAuth2的组件特别全面,这样就导致了扩展很不方便或者说是不太容易直指定扩展的方案,例如: 图片验证码登录 短信验证码登录 微信小程序登录 第三方系统登录 CAS单点登录

随机推荐