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()));
原文地址:https://www.cnblogs.com/HiLzd/p/14367865.html