乐趣区

关于java: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 受权,然而这样的实现形式有两个问题:

  1. 脱离了 OAuth2 的治理
  2. 不灵便:例如零碎应用 明码模式 受权,网页版须要减少图形验证码校验,然而手机端 APP 又不须要的状况下,应用减少 Filter 的形式去实现就比拟麻烦了。

 

所以目前在 Spring Security 中比拟优雅和灵便的扩大形式就是通过自定义 grant_type 来减少受权模式。

 

三、实现思路

在扩大之前首先须要先理解 Spring Security 的整个受权流程,我以 明码模式 为例去开展剖析,如下图所示

3.1. 流程剖析

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

第一局部 :对于受权类型 grant_type 的解析

  1. 每种 grant_type 都会有一个对应的 TokenGranter 实现类。
  2. 所有 TokenGranter 实现类都通过 CompositeTokenGranter 中的 tokenGranters 汇合存起来。
  3. 而后通过判断 grantType 参数来定位具体应用那个 TokenGranter 实现类来解决受权。

 

第二局部 :对于受权登录逻辑

  1. 每种 受权形式 都会有一个对应的 AuthenticationProvider 实现类来实现。
  2. 所有 AuthenticationProvider 实现类都通过 ProviderManager 中的 providers 汇合存起来。
  3. TokenGranter 类会 new 一个 AuthenticationToken 实现类,如 UsernamePasswordAuthenticationToken 传给 ProviderManager 类。
  4. ProviderManager 则通过 AuthenticationToken 来判断具体应用那个 AuthenticationProvider 实现类来解决受权。
  5. 具体的登录逻辑由 AuthenticationProvider 实现类来实现,如 DaoAuthenticationProvider

 

3.2. 扩大剖析

依据下面的流程,扩大分为以下两种场景

场景一 :只对原有的受权逻辑进行加强或者扩大,如:用户名明码登录前减少图形验证码校验。

该场景须要定义一个新的 grantType 类型,并新增对应的 TokenGranter 实现类 增加扩大内容 ,而后加到 CompositeTokenGranter 中的 tokenGranters 汇合里即可。

参考代码:PwdImgCodeGranter.java

 

场景二 :新加一种受权形式,如:手机号加明码登录。

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

  1. 定义一个新的 grantType 类型,并新增对应的 TokenGranter 实现类增加到 CompositeTokenGranter 中的 tokenGranters 汇合里
  2. 新增一个 AuthenticationToken 实现类,用于寄存该受权所需的信息。
  3. 新增一个 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

 
扫码关注有惊喜!

退出移动版