亲宝软件园·资讯

展开

Spring Security OAuth2 Spring Security怎样优雅的增加OAuth2协议授权模式

zlt2000 人气:0
想了解Spring Security怎样优雅的增加OAuth2协议授权模式的相关内容吗,zlt2000在本文为您仔细讲解Spring Security OAuth2的相关知识和一些Code实例,欢迎阅读和指正,我们先划重点:Spring,Security,OAuth2,下面大家一起来学习吧。

一、什么是OAuth2协议?

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

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

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

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

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

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

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

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

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

三、实现思路

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

3.1. 流程分析

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

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

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

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

 3.2. 扩展分析

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

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

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

参考代码:PwdImgCodeGranter.java

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

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

参考代码: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

加载全部内容

相关教程
猜你喜欢
用户评论