欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

springboot+sercuity+oauth2+Jwt+手机号+微信+密码 企业级认证与授权原理以及实现(完整版)

程序员文章站 2022-03-20 23:28:21
springboot+sercuity+oauth2+Jwt企业级认证与授权1.OAUTH认证流程分析2.源码分析2.1TokenEndpoint2.2CompositeTokenGranter2.3AbstractTokenGranter2.4 getOAuth2Authentication2.5 ResourceOwnerPasswordTokenGranter2.6 AbstractAuthenticationToken2.7 OAuth2Authentication2.8Authentication...

有关什么是OAUTH2的描述

1.OAUTH认证流程分析

springboot+sercuity+oauth2+Jwt+手机号+微信+密码 企业级认证与授权原理以及实现(完整版)

2.源码分析

2.1TokenEndpoint

TokenEndpoint为令牌访问端点
类上注解FrameworkEndpoint,这个注解和Spring的Controller注解一样的,只是这个FrameworkEndpoint注解是给框架用的,它有处理/oauth/token的方法,分别对应get和post的请求
springboot+sercuity+oauth2+Jwt+手机号+微信+密码 企业级认证与授权原理以及实现(完整版)

2.2CompositeTokenGranter

ComPositeTokenGranter是多个 TokenGranter 的集合
一共有两个方法,
1.addTokenGranter添加 TokenGranter ,
2.grant * 从 TokenGranter 列表中挨个提出来获取准许证 (也就是我们要的 Token), 只要有个能获取到立即返回结果,将参数中的grant_type和集合中的进行匹配


/**
 * @author Dave Syer
 */
public class CompositeTokenGranter implements TokenGranter {
 
    private final List<TokenGranter> tokenGranters;
 
    public CompositeTokenGranter(List<TokenGranter> tokenGranters) {
        this.tokenGranters = new ArrayList<TokenGranter>(tokenGranters);
    }
    
    /**
     * 从 TokenGranter 列表中挨个提出来获取准许证 (也就是我们要的 Token), 只要有个能获取到立即返回结果。
     */
    public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
        for (TokenGranter granter : tokenGranters) {
            OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);
            if (grant!=null) {
                return grant;
            }
        }
        return null;
    }
    
    /**
     * 添加 TokenGranter
     */
    public void addTokenGranter(TokenGranter tokenGranter) {
        if (tokenGranter == null) {
            throw new IllegalArgumentException("Token granter is null");
        }
        tokenGranters.add(tokenGranter);
    }
}

2.3AbstractTokenGranter

多个TokenGranter的子类
AuthorizationCodeTokenGranter 授权码模式
ClientCredentialsTokenGranter 客户端模式
ImplicitTokenGranter implicit 模式
RefreshTokenGranter 刷新 token 模式
ResourceOwnerPasswordTokenGranter 密码模式
包括自定义模式 都继承了AbstractTokenGranter
1.判断传入的 grantType 是否符合当前的 TokenGranter
2.根据 TokenRequest 中的 clientId 加载 Client 信息
3.判断 Client 信息中的 authorizedGrantTypes (已授权的 grantType) 是否包含着传入的 grantType
4.通过 tokenService 创建 OAuth2AccessToken 并返回

/**
 * @author Dave Syer
 * 
 */
public abstract class AbstractTokenGranter implements TokenGranter {
	
	protected final Log logger = LogFactory.getLog(getClass());

	private final AuthorizationServerTokenServices tokenServices;

	private final ClientDetailsService clientDetailsService;
	
	private final OAuth2RequestFactory requestFactory;
	
	private final String grantType;

	protected AbstractTokenGranter(AuthorizationServerTokenServices tokenServices,
			ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
		this.clientDetailsService = clientDetailsService;
		this.grantType = grantType;
		this.tokenServices = tokenServices;
		this.requestFactory = requestFactory;
	}

	public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
		// 判断参数的的 grant_type 是否符合当前的 TokenGranter。
		if (!this.grantType.equals(grantType)) {
			return null;
		}
		// 根据 TokenRequest 中的 clientId 加载 Client 信息
		String clientId = tokenRequest.getClientId();
		// 判断 Client 信息中的 authorizedGrantTypes (已授权的 grant_type) 是否包含着传入的 grant_type
		ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
		validateGrantType(grantType, client);
		/**
		  *	tokenService 创建 OAuth2AccessToken 并返回
          *	TokenRequest 里有调接口时传进的 parameters (包含授权根据, 例如 username, password 等), 
          *	根据 client 信息 + tokenRequest 最终可得到 OAuth2AccessToken
          */
		if (logger.isDebugEnabled()) {
			logger.debug("Getting access token for: " + clientId);
		}

		return getAccessToken(client, tokenRequest);

	}
	protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
		return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
	}
	.........	
	

2.4 getOAuth2Authentication

在AbstractTokenGranter中,的getOAuth2Authentication()方法 会找到对应的AbstractTokenGranter实现类,进行获取OAuth2Authentication,下面以用户名密码为例

2.5 ResourceOwnerPasswordTokenGranter

重写了AbstractTokenGranter 的 getOAuth2Authentication方法做了以下几步
1.不将密码放入 details 中, 防止密码泄露
2.组装用户密码模式的认证信息
3.通过 authenticationManager 认证已组装好的信息
4.过期、被锁定、用户名密码错误不能用的时候抛出异常
5.从工厂创建 OAuth2Request

/**
 * @author Dave Syer
 * 
 */
public class ResourceOwnerPasswordTokenGranter extends AbstractTokenGranter {
	private static final String GRANT_TYPE = "password";
	private final AuthenticationManager authenticationManager;
	public ResourceOwnerPasswordTokenGranter(AuthenticationManager authenticationManager,
			AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
		this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
	}
	protected ResourceOwnerPasswordTokenGranter(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 username = parameters.get("username");
		String password = parameters.get("password");
		// Protect from downstream leaks of password
		// 删除密码, 防止密码泄露
		parameters.remove("password");
 		// 组装用户密码模式的认证信息
		Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
		((AbstractAuthenticationToken) userAuth).setDetails(parameters);
		try {
		 通过 authenticationManager 认证
			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
			//:如果用户名/密码是错误的,规范说我们应该发送400/无效的授权
			throw new InvalidGrantException(e.getMessage());
		}
		if (userAuth == null || !userAuth.isAuthenticated()) {
		//无法验证用户身份
			throw new InvalidGrantException("Could not authenticate user: " + username);
		}
        // 从工厂创建 OAuth2Request
		OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);		
		return new OAuth2Authentication(storedOAuth2Request, userAuth);
	}
}

2.6 AbstractAuthenticationToken

AbstractAuthenticationToken抽象类是Authentication对象的基类。可自定义AuthenticationToken继承抽象类重写方法

2.7 OAuth2Authentication

判断用哪个provider
TokenGranter 类会 new 一个 AuthenticationToken 实现类,如 UsernamePasswordAuthenticationToken 传给 ProviderManager 类。而 ProviderManager 则通过 AuthenticationToken 来判断具体使用那个 AuthenticationProvider 实现类来处理授权。
具体的登录逻辑由 AuthenticationProvider 实现类来实现

2.8AuthenticationManager

用于抽象建模认证管理器,用于处理一个认证请求,是认证方法的入口,接收一个Authentication对象作为参数也就是Spring Security中的Authentication认证令牌。

2.9ProviderManager

在ProviderManager的authenticate方法中,轮训成员变量List providers。该providers中如果有一个
AuthenticationProvider的supports函数返回true,那么就会调用该AuthenticationProvider的authenticate函数认证,如果认证成功则整个
认证过程结束。如果不成功,则继续使用下一个合适的AuthenticationProvider进行认证,只要有一个认证成功则为认证成功。
springboot+sercuity+oauth2+Jwt+手机号+微信+密码 企业级认证与授权原理以及实现(完整版)

2.10AbstractUserDetailsAuthenticationProvider

用户密码校验
实现了AuthenticationProvider,authenticate方法调用UserDetils,查询用户信息,返回已认证的Authentication

3.类图

springboot+sercuity+oauth2+Jwt+手机号+微信+密码 企业级认证与授权原理以及实现(完整版)

4.具体实现

4.1自定义Provider implements AuthenticationProvider

手机号登录

package com.aeotrade.provider.oauth.sms;

import com.aeotrade.base.constant.AeoConstant;
import com.aeotrade.provider.oauth.service.MoblieUserDetailsService;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @Author: yewei
 * @Date: 9:08 2020/11/25
 * @Description:
 * 在AuthenticationManager认证过程中,
 * 是通过AuthenticationProvider接口的扩展来实现自定义认证方式的。
 * 定义手机和验证码认证提供者PhoneAndVerificationCodeAuthenticationProvider
 */
public class MobileAuthenticationProvider implements AuthenticationProvider {


    /**
     * UserDetailsService
     */
    private MoblieUserDetailsService UserDetailsService;

    /**
     * redis服务
     */
    private StringRedisTemplate stringRedisTemplate;


    public MobileAuthenticationProvider(MoblieUserDetailsService UserDetailsService, StringRedisTemplate redisTemplate) {
        this.UserDetailsService = UserDetailsService;
        this.stringRedisTemplate = redisTemplate;
    }

    @SneakyThrows
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        MobileAuthenticationToken phoneAndVerificationCodeAuthenticationToken = (MobileAuthenticationToken) authentication;

        Object verificationCodeObj;
        String verificationCode = Objects.nonNull(verificationCodeObj = phoneAndVerificationCodeAuthenticationToken.getCredentials()) ?
                verificationCodeObj.toString() : StringUtils.EMPTY;

        Object phoneNumberObj;
        String phoneNumber = Objects.nonNull(phoneNumberObj = phoneAndVerificationCodeAuthenticationToken.getPrincipal())
                ? phoneNumberObj.toString() : StringUtils.EMPTY;
        // 验证用户
        if (StringUtils.isBlank(phoneNumber)) {
            throw new InternalAuthenticationServiceException("电话号码为空!");
        }
        // 根据电话号码获取用户
        UserDetails userDetails = UserDetailsService.loadUserByMoblie(phoneNumber);
        if (Objects.isNull(userDetails)) {
            throw new InternalAuthenticationServiceException(
                    "UserDetailsService 为空");
        }
        //校验验证码
        //验证的过期时间
        Long expire = stringRedisTemplate.opsForValue().getOperations().getExpire(AeoConstant.SMSREDIS_KEY+phoneNumber);
        System.out.println("redis过期时间返回"+expire);
        if (  expire <= 0 ) {
            throw new InternalAuthenticationServiceException(
                    "验证码已过期");
        }
        String cmsCode = this.getCmsCode(phoneNumber);
        if ( cmsCode == null && StringUtils.isEmpty(cmsCode)) {
            throw new InternalAuthenticationServiceException(
                    "验证码为空!");
        }
        //校验验证码
        if (!verificationCode.equals(cmsCode)){
            throw new InternalAuthenticationServiceException(
                    "验证码错误!");
        }
        stringRedisTemplate.delete(AeoConstant.SMSREDIS_KEY+phoneNumber);
        // 封装需要认证的PhoneAndVerificationCodeAuthenticationToken对象
        return new MobileAuthenticationToken(userDetails.getAuthorities(), userDetails, verificationCode);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return MobileAuthenticationToken.class.isAssignableFrom(authentication);
    }
    //从redis查询验证码
    public String getCmsCode(String phoneNumber) {
        //从redis中取到令牌信息
        String value = stringRedisTemplate.opsForValue().get(AeoConstant.SMSREDIS_KEY+phoneNumber);
        //转成对象
        return StringUtils.isEmpty(value)?"":value;
    }
}


微信登录

package com.aeotrade.provider.oauth.wx;

import com.aeotrade.provider.oauth.service.WxUserDetailsService;
import lombok.SneakyThrows;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;

/**
 * @Author: yewei
 * @Date: 17:08 2020/11/25
 * @Description:
 */
public class OpenIdAuthenticationProvider implements AuthenticationProvider {

    private WxUserDetailsService userDetailsService;

    public OpenIdAuthenticationProvider( WxUserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @SneakyThrows
    @Override
    public Authentication authenticate(Authentication authentication) {
        OpenIdAuthenticationToken authenticationToken = (OpenIdAuthenticationToken) authentication;
        String openId = (String) authenticationToken.getPrincipal();
        UserDetails user = userDetailsService.loadUserByOpenId(openId);
        if (user == null) {
            throw new InternalAuthenticationServiceException("openId错误");
        }
        OpenIdAuthenticationToken authenticationResult = new OpenIdAuthenticationToken(user, user.getAuthorities());
        authenticationResult.setDetails(authenticationToken.getDetails());
        return authenticationResult;
    }

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

    public WxUserDetailsService getUserDetailsService() {
        return userDetailsService;
    }

    public void setUserDetailsService(WxUserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
}

4.2自定义AuthenticationToken extends AbstractAuthenticationToken

手机号登录

package com.aeotrade.provider.oauth.sms;

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

import java.util.Collection;

/**
 * @Author: yewei
 * @Date: 9:06 2020/11/25
 * @Description:
 * 在OAuth2认证开始认证时,
 * 会提前Authentication认证信息,
 * 然后交由AuthenticationManager认证。
 * 定义电话号码+验证码的Authentication认证信息
 */
public class MobileAuthenticationToken extends AbstractAuthenticationToken {

    /**
     * 手机号
     */
    private final Object mobile;

    /**
     * 验证码
     */
    private final Object code;



    public MobileAuthenticationToken(Object mobile, Object code) {
        super(null);
        this.mobile = mobile;
        this.code = code;
    }

    public MobileAuthenticationToken(Collection<? extends GrantedAuthority> authorities, Object mobile, Object code) {
        super(authorities);
        this.mobile = mobile;
        this.code = code;
        // 认证已经通过
        setAuthenticated(true);
    }

    /**
     * 用户身份凭证(一般是密码或者验证码)
     */
    @Override
    public Object getCredentials() {
        return code;
    }

    /**
     * 身份标识(一般是姓名,手机号)
     */
    @Override
    public Object getPrincipal() {
        return mobile;
    }
}

微信

package com.aeotrade.provider.oauth.wx;

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

import java.util.Collection;

/**
 * @Author: yewei
 * @Date: 17:08 2020/11/25
 * @Description:
 */
public class OpenIdAuthenticationToken extends AbstractAuthenticationToken {

	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

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

	private final Object principal;

	// ~ Constructors
	// ===================================================================================================

	/**
	 * This constructor can be safely used by any code that wishes to create a
	 * <code>UsernamePasswordAuthenticationToken</code>, as the {@link #isAuthenticated()}
	 * will return <code>false</code>.
	 *
	 */
	public OpenIdAuthenticationToken(String openId) {
		super(null);
		this.principal = openId;
		setAuthenticated(false);
	}

	/**
	 * This constructor should only be used by <code>AuthenticationManager</code> or
	 * <code>AuthenticationProvider</code> implementations that are satisfied with
	 * producing a trusted (i.e. {@link #isAuthenticated()} = <code>true</code>)
	 * authentication token.
	 *
	 * @param principal
	 * @param authorities
	 */
	public OpenIdAuthenticationToken(Object principal,
                                     Collection<? extends GrantedAuthority> authorities) {
		super(authorities);
		this.principal = principal;
		super.setAuthenticated(true);
	}

	// ~ Methods
	// ========================================================================================================

	@Override
	public Object getCredentials() {
		return null;
	}

	@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.3自定义TokenGranter

手机号

package com.aeotrade.provider.oauth.sms;

import org.springframework.beans.factory.annotation.Autowired;
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;

/**
 * @Author: yewei
 * @Date: 9:10 2020/11/25
 * @Description:
 * 新增电话验证码类型,PhoneAndVerificationCodeTokenGranter,
 * 参考密码类型ResourceOwnerPasswordTokenGranter的认证流程,
 * 首先进行电话号码与验证码的认证,然后生成访问授权码
 */
public class MobileTokenGranter extends AbstractTokenGranter {

    private static final String GRANT_TYPE = "cms_code";

    private final AuthenticationManager authenticationManager;

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

    @Autowired
    protected MobileTokenGranter(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<>(tokenRequest.getRequestParameters());
        // 电话号码与验证码
        String phoneNumber = parameters.get("mobile");
        String verificationCode = parameters.get("cms_code");

        Authentication userAuth = new MobileAuthenticationToken(phoneNumber, verificationCode);
        ((AbstractAuthenticationToken) userAuth).setDetails(parameters);
        try {
            // authenticationManager进行验证
            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 phone number: " + phoneNumber);
        }
        OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
        return new OAuth2Authentication(storedOAuth2Request, userAuth);
    }
}

微信

package com.aeotrade.provider.oauth.wx;

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

import java.util.Collection;

/**
 * @Author: yewei
 * @Date: 17:08 2020/11/25
 * @Description:
 */
public class OpenIdAuthenticationToken extends AbstractAuthenticationToken {

	private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

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

	private final Object principal;

	// ~ Constructors
	// ===================================================================================================

	/**
	 * This constructor can be safely used by any code that wishes to create a
	 * <code>UsernamePasswordAuthenticationToken</code>, as the {@link #isAuthenticated()}
	 * will return <code>false</code>.
	 *
	 */
	public OpenIdAuthenticationToken(String openId) {
		super(null);
		this.principal = openId;
		setAuthenticated(false);
	}

	/**
	 * This constructor should only be used by <code>AuthenticationManager</code> or
	 * <code>AuthenticationProvider</code> implementations that are satisfied with
	 * producing a trusted (i.e. {@link #isAuthenticated()} = <code>true</code>)
	 * authentication token.
	 *
	 * @param principal
	 * @param authorities
	 */
	public OpenIdAuthenticationToken(Object principal,
                                     Collection<? extends GrantedAuthority> authorities) {
		super(authorities);
		this.principal = principal;
		super.setAuthenticated(true);
	}

	// ~ Methods
	// ========================================================================================================

	@Override
	public Object getCredentials() {
		return null;
	}

	@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.4 OauthConfig

OauthConfig的具体配置

package com.aeotrade.provider.oauth.config;

import com.aeotrade.provider.oauth.service.AeoWebResponseExceptionTranslator;
import com.aeotrade.provider.oauth.service.AeotradeJdbcClientDetailsService;
import com.aeotrade.provider.oauth.service.RedisClientDetailsService;
import com.aeotrade.provider.oauth.service.TokenJwtEnhancer;
import com.aeotrade.provider.oauth.sms.AeotradeCompositeTokenGranter;
import com.aeotrade.provider.oauth.sms.AeotradeTokenGranter;
import com.aeotrade.provider.oauth.sms.MobileTokenGranter;
import com.aeotrade.provider.oauth.wx.OpenIdGranter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.OAuth2RequestFactory;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.client.ClientCredentialsTokenGranter;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeTokenGranter;
import org.springframework.security.oauth2.provider.implicit.ImplicitTokenGranter;
import org.springframework.security.oauth2.provider.password.ResourceOwnerPasswordTokenGranter;
import org.springframework.security.oauth2.provider.refresh.RefreshTokenGranter;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.*;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.util.FileCopyUtils;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;

@Configuration
@EnableAuthorizationServer
public class OauthConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private AeoWebResponseExceptionTranslator exceptionTranslator;

    @Autowired
    private RedisClientDetailsService clientDetailsService;
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security.allowFormAuthenticationForClients();
        security.checkTokenAccess("permitAll()");
        security.tokenKeyAccess("permitAll()");
        security.passwordEncoder(passwordEncoder);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        /**
         * 从数据库读取客户端配置
        clients.withClientDetails(clientDetailsService()); */
        /**从redis读取*/
        clients.withClientDetails(clientDetailsService);
        clientDetailsService.loadAllClientToCache();
    }

 /* 数据库读取客户端的配置
    private ClientDetailsService clientDetailsService() {
        AeotradeJdbcClientDetailsService aeotradeJdbcClientDetailsService = new AeotradeJdbcClientDetailsService(dataSource);
        aeotradeJdbcClientDetailsService.setPasswordEncoder(passwordEncoder);
        return aeotradeJdbcClientDetailsService;
    }*/

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {

        // 初始化所有的TokenGranter,并且类型为CompositeTokenGranter
        List<TokenGranter> defaultTokenGranters = this.getDefaultTokenGranters(endpoints);
        //添加手机号模式
        defaultTokenGranters.add(new MobileTokenGranter(authenticationManager, endpoints.getTokenServices(),
                endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
        //添加微信模式
        defaultTokenGranters.add(new OpenIdGranter(authenticationManager,endpoints.getTokenServices(),
                endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));

        endpoints.tokenGranter(new CompositeTokenGranter(defaultTokenGranters));
        endpoints
                .tokenStore(tokenStore())    /**采用JWT非对称加密*/
                .authenticationManager(authenticationManager)
                .allowedTokenEndpointRequestMethods(HttpMethod.POST, HttpMethod.GET);
                //.userDetailsService(userDetailsService).exceptionTranslator(exceptionTranslator);
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> enhancerList = new ArrayList<>();
        enhancerList.add(tokenEnhancer());
        enhancerList.add(jwtAccessTokenConverter());
        enhancerChain.setTokenEnhancers(enhancerList);
        endpoints
                .tokenEnhancer(enhancerChain)
                .accessTokenConverter(jwtAccessTokenConverter());
        new AeotradeCompositeTokenGranter(defaultTokenGranters);
    }


    private TokenEnhancer tokenEnhancer() {
        return new TokenJwtEnhancer();
    }

    @Bean
    @Primary
    public DefaultTokenServices defaultTokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setReuseRefreshToken(false);
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(clientDetailsService);
        tokenServices.setTokenEnhancer(jwtAccessTokenConverter());
        return tokenServices;
    }
    /**采用JWT非对称加密*/
    private TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("aeotrade.jks"),
                "hmtx20191001".toCharArray());
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("aeotrade"));

        Resource resouce=new ClassPathResource("aeotrade.crt");
        String publicKey=null;
        try {
            publicKey=new String(FileCopyUtils.copyToByteArray(resouce.getInputStream()),"utf-8");
        }catch (Exception e){
            throw new RuntimeException(e);
        }

        //converter.setVerifierKey(publicKey);
        converter.setVerifier(new RsaVerifier(publicKey));

        return converter;
    }

    @Bean
    public ResourceOwnerPasswordTokenGranter resourceOwnerPasswordTokenGranter(@Autowired AuthenticationManager authenticationManager,@Autowired OAuth2RequestFactory oAuth2RequestFactory) {
        DefaultTokenServices defaultTokenServices = defaultTokenServices();
            defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
        return new ResourceOwnerPasswordTokenGranter(authenticationManager, defaultTokenServices, clientDetailsService, oAuth2RequestFactory);
    }

    @Bean
    public MobileTokenGranter mobileTokenGranter(@Autowired AuthenticationManager authenticationManager,@Autowired OAuth2RequestFactory oAuth2RequestFactory){
        DefaultTokenServices defaultTokenServices = defaultTokenServices();
        defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
        return new MobileTokenGranter(authenticationManager,defaultTokenServices, clientDetailsService, oAuth2RequestFactory);
    }
    @Bean
    public OpenIdGranter openIdGranter(@Autowired AuthenticationManager authenticationManager,@Autowired OAuth2RequestFactory oAuth2RequestFactory){
        DefaultTokenServices defaultTokenServices = defaultTokenServices();
        defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
        return new OpenIdGranter(authenticationManager,defaultTokenServices, clientDetailsService, oAuth2RequestFactory);
    }

    @Bean
    public DefaultOAuth2RequestFactory oAuth2RequestFactory() {
        return new DefaultOAuth2RequestFactory(clientDetailsService);
    }
    /**
     * 初始化所有的TokenGranter
     */
    private List<TokenGranter> getDefaultTokenGranters(AuthorizationServerEndpointsConfigurer endpoints) {

        ClientDetailsService clientDetails = endpoints.getClientDetailsService();
        AuthorizationServerTokenServices tokenServices = endpoints.getTokenServices();
        AuthorizationCodeServices authorizationCodeServices = endpoints.getAuthorizationCodeServices();
        OAuth2RequestFactory requestFactory = endpoints.getOAuth2RequestFactory();

        List<TokenGranter> tokenGranters = new ArrayList<>();
        // 添加授权码模式
        tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,requestFactory));
        // 添加刷新令牌的模式
        tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
        ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
        // 添加影式授权模式
        tokenGranters.add(implicit);
        // 添加客户端模式
        tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
        if (authenticationManager != null) {
            // 添加密码模式
            tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices,
                    clientDetails, requestFactory));
        }
        return tokenGranters;
    }

}

本文地址:https://blog.csdn.net/ywdevil1314/article/details/110802590