spring cloud oauth2(三) 自定义授权类型 手机号+短信验证码


第一部分:关于授权类型 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。

所有的授权类型都会继承 AbstractTokenGranter

自定义grant_type步骤

  • 自定义token,继承自 **AbstractAuthenticationToken* * ,用于到登录的时候传入认证信息
  • 自定义模式的提供者,继承自 *implements AuthenticationProvider, MessageSourceAware* ;作用是如果当前的token为该提供的类型,会调用authenticate方法进行登录操作
  • 自定义TokenGranter,在getOAuth2Authentication 方法中传入自定义的token进行验证

自定义token类型,直接复制 UsernamePasswordAuthenticationToken

package com.Lonni.oauth.token;

import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

import java.util.Collection;

/**
 * 继承自AbstractAuthenticationToken 用于自定义的token
 *
 */
public class MobileCodeAuthenticationToken extends AbstractAuthenticationToken {


    // ~ Instance fields
    // ================================================================================================

    /**
     * 认证之前  存的是手机号
     * 认证之后 存的是用户信息
     */

    private final Object principal;
    private Object credentials;


    public MobileCodeAuthenticationToken(Object principal, Object credentials) {
        super(null);
        this.principal = principal;
        this.credentials = credentials;
        //设置没有认证
        setAuthenticated(false);
    }


    public MobileCodeAuthenticationToken(Object principal, Object credentials,
                                               Collection<? extends GrantedAuthority> authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        // must use super, as we override
        //设置已经认证
        super.setAuthenticated(true);
    }

@Override
    public Object getCredentials() {
        return this.credentials;
    }
    @Override
    public Object getPrincipal() {
        return this.principal;
    }
    @Override
    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
        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();
        credentials = null;
    }
}

自定义模式提供者

package com.Lonni.oauth.provider;


import com.Lonni.common.constant.AuthConstant;
import com.Lonni.core.Constants.AppConstans;
import com.Lonni.oauth.service.UserDetailsServiceImpl;
import com.Lonni.oauth.token.MobileCodeAuthenticationToken;
import org.apache.commons.lang.NullArgumentException;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.StringUtils;

/**
 * 自定义手机模式的提供者
 * 判断token类型是否为MobileCodeAuthenticationToken,如果是则会使用此provider
 */

public class MobileCodeAuthenticationProvider implements AuthenticationProvider, MessageSourceAware {

    private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    @Override
    public void setMessageSource(MessageSource messageSource) {
        this.messages = new MessageSourceAccessor(messageSource);
    }

    /**
     * userDetailsService的实现类 不需要自动注入 直接传入
     */
    private UserDetailsServiceImpl userDetailsService;

    private RedisTemplate redisTemplate;

    /**
     * 业务处理的方法
     * @param authentication
     * @return
     * @throws AuthenticationException
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //未认证之前是手机号 认证之后是客户信息
        String principal =(String) authentication.getPrincipal();
        if (StringUtils.isEmpty(principal)){
            throw  new BadCredentialsException("手机号不能为空");
        }

        // 这里的Credentials是先通过AbstractTokenGranter组装  new MobileCodeAuthenticationToken()传入的
        String code = (String) authentication.getCredentials();
        if (code == null) {
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "手机验证码不能为空"));
        }
        //正式环境放开注释

//        String key=AuthConstant.AUTH_SMS_CACHE_KEY+principal+code;
//        Object cacheCode = redisTemplate.opsForValue().get(key);
//        if (cacheCode == null || !cacheCode.toString().equals(code)) {
//            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "验证码无效"));
//        }
        //清除redis中的短信验证码
        //stringRedisTemplate.delete(RedisConstant.SMS_CODE_PREFIX + mobile);
        UserDetails user;
        try {
            user = userDetailsService.loadUserByPhone(principal);
        } catch (UsernameNotFoundException var6) {
           throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "查询用户信息失败!"));
        }
        check(user);
        MobileCodeAuthenticationToken authenticationToken = new MobileCodeAuthenticationToken(user, code, user.getAuthorities());
        authenticationToken.setDetails(authenticationToken.getDetails());
        return authenticationToken;
    }

    /**
     * 账号禁用、锁定、超时校验
     *
     * @param user
     */
    private void check(UserDetails user) {
        if (user==null){
            throw  new NullArgumentException(this.messages.getMessage("未查询到用户", "未查询到用户"));
        }
        if (!user.isAccountNonLocked()) {
            throw new LockedException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
        } else if (!user.isEnabled()) {
            throw new DisabledException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
        } else if (!user.isAccountNonExpired()) {
            throw new AccountExpiredException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
        }
    }

    /**
     * 判断是否为MobileCodeAuthenticationToken类型  如果是 直接调用 切断过滤器链
     * 如果不是 继续查找
     * @param authentication
     * @return
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return MobileCodeAuthenticationToken.class.isAssignableFrom(authentication);
    }


     public void  setUserDetailsService(UserDetailsServiceImpl userDetailsService){
        this.userDetailsService=userDetailsService;
    }
    public void  setRedisTemplate( RedisTemplate redisTemplate){
        this.redisTemplate=redisTemplate;
    }
}

自定义 MobileCodeTokenGranter

package com.Lonni.oauth.granter;

import com.Lonni.oauth.token.MobileCodeAuthenticationToken;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;

import java.util.LinkedHashMap;
import java.util.Map;

public class MobileCodeTokenGranter extends AbstractTokenGranter {

    private static final String GRANT_TYPE = "mobile";

    private final AuthenticationManager authenticationManager;

    public MobileCodeTokenGranter(AuthenticationManager authenticationManager,
                                  AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
        this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
    }

    private MobileCodeTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices,
                                   ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
        super(tokenServices, clientDetailsService, requestFactory, grantType);
        this.authenticationManager = authenticationManager;
    }

    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
        String mobile = parameters.get("mobile");
        String code = parameters.get("code");
        //调用自定义的token扩展 实现用户认证 
        Authentication userAuth = new MobileCodeAuthenticationToken(mobile,code);
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
        try {
            userAuth = authenticationManager.authenticate(userAuth);
        }
        catch (AccountStatusException ase) {
            //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
            throw new InvalidGrantException(ase.getMessage());
        }
        catch (BadCredentialsException e) {
            // If the username/password are wrong the spec says we should send 400/invalid grant
            throw new InvalidGrantException(e.getMessage());
        }
        if (userAuth == null || !userAuth.isAuthenticated()) {
            throw new InvalidGrantException("Could not authenticate mobile: " + mobile);
        }

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

将自定义的TokenGanter加入到security中

复制上一节的 TokenGranterExt扩展类 加入手机验证码模式

        //添加手机验证码
        //granters.add(new SmsCodeTokenGranter(authenticationManager, userDetailsService, redisTemplate, endpointsConfigurer.getTokenServices(), endpointsConfigurer.getClientDetailsService(), endpointsConfigurer.getOAuth2RequestFactory()));

        granters.add(new MobileCodeTokenGranter(authenticationManager,  endpointsConfigurer.getTokenServices(), endpointsConfigurer.getClientDetailsService(), endpointsConfigurer.getOAuth2RequestFactory()));


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2026 CODEPRJ.COM