微信小程序登录的问题——code换openid换token——结合spring-security-oauth2-autoconfigure - Go语言中文社区

微信小程序登录的问题——code换openid换token——结合spring-security-oauth2-autoconfigure


微信小程序登录的问题——code换openid换token——结合spring-security-oauth2-autoconfigure

问题描述:在微信小程序登录时我们得到的是code,需要自己的服务器换取openid,再得到本系统对应的用户。现在我想使用spring security oauth2在查到对应的用户后可以生成token,整体思路如下图

因为整个架构涉及很多自定义的类,自己做了封装,在这说说大概思路或说明一些类的用法,如未了解spring-security-oauth2-autoconfigure的可以先了解

首先是OAuth2ClientAuthenticationProcessingFilter这个类,如果我们要重写授权模式的处理逻辑,我们需要指定其中的RestTemplate(与授权服务器获取token请求有关)和TokenServices(与获取用户信息有关)。我做的封装如下

package cn.cjx913.spring_custom.oauth2.client;

import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;

public class CustomOAuth2ClientAuthenticationProcessingFilter extends OAuth2ClientAuthenticationProcessingFilter {

    public CustomOAuth2ClientAuthenticationProcessingFilter(
            String defaultFilterProcessesUrl,//请求的地址
            OAuth2ProtectedResourceDetails client,//请求客户端的信息,建议了解这个类封装了那些变量
            ResourceServerProperties resource,//请求客户端的资源,建议了解这个类封装了那些变量
            OAuth2ClientContext oAuth2ClientContext//oauth2请求上下文,可以通过自动注入的方式
    ) {
        super(defaultFilterProcessesUrl);
        OAuth2RestTemplate oAuth2RestTemplate = getOAuth2RestTemplate(client, oAuth2ClientContext);
        this.setRestTemplate(oAuth2RestTemplate);
        UserInfoTokenServices userInfoTokenServices = getUserInfoTokenServices(resource.getUserInfoUri(),
                client.getClientId());
        userInfoTokenServices.setRestTemplate(oAuth2RestTemplate);
        this.setTokenServices(userInfoTokenServices);
    }

    public OAuth2RestTemplate getOAuth2RestTemplate(OAuth2ProtectedResourceDetails client, OAuth2ClientContext oAuth2ClientContext) {
        OAuth2RestTemplate oAuth2RestTemplate = new OAuth2RestTemplate(client, oAuth2ClientContext);
        return oAuth2RestTemplate;
    }

    public UserInfoTokenServices getUserInfoTokenServices(String userInfoEndpointUrl, String clientId) {
        UserInfoTokenServices tokenServices = new UserInfoTokenServices(userInfoEndpointUrl, clientId);
        return tokenServices;
    }
}

对应微信小程序的有WxOAuth2ClientAuthenticationProcessingFilter,如下

package cn.cjx913.spring_custom.oauth2.client.wx.access_token;

import cn.cjx913.spring_custom.oauth2.client.CustomOAuth2ClientAuthenticationProcessingFilter;
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;

public class WxOAuth2ClientAuthenticationProcessingFilter extends CustomOAuth2ClientAuthenticationProcessingFilter {



    public WxOAuth2ClientAuthenticationProcessingFilter(String defaultFilterProcessesUrl, OAuth2ProtectedResourceDetails client, ResourceServerProperties resource, OAuth2ClientContext oAuth2ClientContext) {
        super(defaultFilterProcessesUrl, client, resource, oAuth2ClientContext);
    }

    @Override
    public OAuth2RestTemplate getOAuth2RestTemplate(OAuth2ProtectedResourceDetails client, OAuth2ClientContext oAuth2ClientContext) {
        WxOAuth2RestTemplate wxOAuth2RestTemplate = new WxOAuth2RestTemplate(client, oAuth2ClientContext);
        return wxOAuth2RestTemplate;
    }

    @Override
    public UserInfoTokenServices getUserInfoTokenServices(String userInfoEndpointUrl, String clientId) {
        return new WxUserInfoTokenServices(userInfoEndpointUrl, clientId);
    }




}

我们现在提供WxOAuth2RestTemplate和WxUserInfoTokenServices

WxOAuth2RestTemplate和WxUserInfoTokenServices不用特别修改,注意在这里我定义的授权类型为wx

package cn.cjx913.spring_custom.oauth2.client.wx.access_token;

import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;

public class WxUserInfoTokenServices extends UserInfoTokenServices {
    public WxUserInfoTokenServices(String userInfoEndpointUrl, String clientId) {
        super(userInfoEndpointUrl, clientId);
    }
}
package cn.cjx913.spring_custom.oauth2.client.wx.access_token;

import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.token.AccessTokenProvider;
import org.springframework.security.oauth2.client.token.AccessTokenProviderChain;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.implicit.ImplicitAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider;

import java.util.Arrays;

public class WxOAuth2RestTemplate extends OAuth2RestTemplate {

    {
        AccessTokenProvider accessTokenProvider = new AccessTokenProviderChain(Arrays. <AccessTokenProvider>asList(
                new AuthorizationCodeAccessTokenProvider(), new ImplicitAccessTokenProvider(),
                new ResourceOwnerPasswordAccessTokenProvider(), new ClientCredentialsAccessTokenProvider(),
                new WxAccessTokenProvider()));
        setAccessTokenProvider(accessTokenProvider);
    }

    public WxOAuth2RestTemplate(OAuth2ProtectedResourceDetails resource) {
        super(resource);
    }

    public WxOAuth2RestTemplate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context) {
        super(resource, context);
    }

//    public OAuth2AccessToken getAccessToken() throws UserRedirectRequiredException {
//        OAuth2ClientContext context = getOAuth2ClientContext();
//        OAuth2AccessToken accessToken = context.getAccessToken();
//
//        if (accessToken == null || accessToken.isExpired()) {
//            try {
//                accessToken = acquireAccessToken(context);
//            } catch (UserRedirectRequiredException e) {
//                context.setAccessToken(null); // No point hanging onto it now
//                accessToken = null;
//                String stateKey = e.getStateKey();
//                if (stateKey != null) {
//                    Object stateToPreserve = e.getStateToPreserve();
//                    if (stateToPreserve == null) {
//                        stateToPreserve = "NONE";
//                    }
//                    context.setPreservedState(stateKey, stateToPreserve);
//                }
//                throw e;
//            }
//        }
//        return accessToken;
//    }

//    @Override
//    protected OAuth2AccessToken acquireAccessToken(OAuth2ClientContext oauth2Context) throws UserRedirectRequiredException {
//
//        AccessTokenRequest accessTokenRequest = oauth2Context.getAccessTokenRequest();
//        if (accessTokenRequest == null) {
//            throw new AccessTokenRequiredException(
//                    "No OAuth 2 security context has been established. Unable to access resource '"
//                            + getResource().getId() + "'.", getResource());
//        }
//
//        // Transfer the preserved state from the (longer lived) context to the current request.
//        String stateKey = accessTokenRequest.getStateKey();
//        if (stateKey != null) {
//            accessTokenRequest.setPreservedState(oauth2Context.removePreservedState(stateKey));
//        }
//
//        OAuth2AccessToken existingToken = oauth2Context.getAccessToken();
//        if (existingToken != null) {
//            accessTokenRequest.setExistingToken(existingToken);
//        }
//
//        OAuth2AccessToken accessToken = null;
//        accessToken = accessTokenProvider.obtainAccessToken(getResource(), accessTokenRequest);
//        if (accessToken == null || accessToken.getValue() == null) {
//            throw new IllegalStateException(
//                    "Access token provider returned a null access token, which is illegal according to the contract.");
//        }
//        oauth2Context.setAccessToken(accessToken);
//        return accessToken;
//    }
}

关于AccessTokenProvider,除了基本的集中类型外,我添加了WxAccessTokenProvider,AccessTokenProvider处理参数的

package cn.cjx913.spring_custom.oauth2.client.wx.access_token;

import org.springframework.http.HttpHeaders;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.oauth2.client.filter.state.DefaultStateKeyGenerator;
import org.springframework.security.oauth2.client.filter.state.StateKeyGenerator;
import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException;
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
import org.springframework.security.oauth2.client.token.AccessTokenProvider;
import org.springframework.security.oauth2.client.token.AccessTokenRequest;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.util.Iterator;
import java.util.List;

/**
 * Provider for obtaining an oauth2 access token by using resource owner password.
 */
public class WxAccessTokenProvider extends WxOAuth2AccessTokenSupport implements AccessTokenProvider {
    private StateKeyGenerator stateKeyGenerator = new DefaultStateKeyGenerator();

    public boolean supportsResource(OAuth2ProtectedResourceDetails resource) {
        return resource instanceof WxResourceDetails && "wx".equals(resource.getGrantType());
    }

    public boolean supportsRefresh(OAuth2ProtectedResourceDetails resource) {
        return supportsResource(resource);
    }

    public OAuth2AccessToken refreshAccessToken(OAuth2ProtectedResourceDetails resource,
                                                OAuth2RefreshToken refreshToken, AccessTokenRequest request) throws UserRedirectRequiredException,
            OAuth2AccessDeniedException {
        MultiValueMap <String, String> form = new LinkedMultiValueMap <String, String>();
        form.add("grant_type", "refresh_token");
        form.add("refresh_token", refreshToken.getValue());
        return retrieveToken(request, resource, form, new HttpHeaders());
    }

    public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest request)
            throws UserRedirectRequiredException, AccessDeniedException, OAuth2AccessDeniedException {
        WxResourceDetails resource = (WxResourceDetails) details;
        MultiValueMap <String, String> parametersForTokenRequest = getParametersForTokenRequest(resource, request);
        HttpHeaders httpHeaders = new HttpHeaders();
        return retrieveToken(request, resource, parametersForTokenRequest, httpHeaders);

    }

    protected MultiValueMap <String, String> getParametersForTokenRequest(WxResourceDetails details, AccessTokenRequest request) {
        request.setStateKey(stateKeyGenerator.generateKey(details));

        MultiValueMap <String, String> form = new LinkedMultiValueMap <String, String>();
        form.set("grant_type", details.getGrantType());

        form.putAll(request);

        if (details.isScoped()) {

            StringBuilder builder = new StringBuilder();
            List <String> scope = details.getScope();

            if (scope != null) {
                Iterator <String> scopeIt = scope.iterator();
                while (scopeIt.hasNext()) {
                    builder.append(scopeIt.next());
                    if (scopeIt.hasNext()) {
                        builder.append(' ');
                    }
                }
            }

            form.set("scope", builder.toString());
        }

        return form;

    }

}

在WxAccessTokenProvider有WxResourceDetails和WxOAuth2AccessTokenSupport,都是一些封装

package cn.cjx913.spring_custom.oauth2.client.wx.access_token;

import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails;

public class WxResourceDetails extends BaseOAuth2ProtectedResourceDetails {

    private String wxCode;

    public WxResourceDetails() {
        setGrantType("wx");

    }

    public String getWxCode() {
        return wxCode;
    }

    public void setWxCode(String wxCode) {
        this.wxCode = wxCode;
    }

}

WxOAuth2AccessTokenSupport可以使用其父类OAuth2AccessTokenSupport(并没有特别的修改,父类是适用的),里有一个重要的方法,更具方法名是取回token,就是请求授权服务器获得token

protected OAuth2AccessToken retrieveToken(AccessTokenRequest request, OAuth2ProtectedResourceDetails resource,MultiValueMap<String, String> form, HttpHeaders headers) throws OAuth2AccessDeniedException {......}

方法有两句可以去看一下

// Prepare headers and form before going into rest template call in case the URI is affected by the result
authenticationHandler.authenticateTokenRequest(resource, form, headers);
// Opportunity to customize form and headers
tokenRequestEnhancer.enhance(request, resource, form, headers);

关于发送请求到授权服务器的部分     

补充:如何配置客户端

在WebSecurityConfigurerAdapter的protected void configure(HttpSecurity http) throws Exception {}中添加http.addFilterBefore(customSsoFilter(), BasicAuthenticationFilter.class);

customSsoFilter()如下

@Bean
public CustomSsoFilter customSsoFilter() {
    List <CustomOAuth2ClientAuthenticationProcessingFilter> filters = new ArrayList <>();
    filters.add(wx());
    CustomSsoFilter customSsoFilter = new CustomSsoFilter(filters);
    return customSsoFilter;
}
CustomSsoFilter只是
package cn.cjx913.spring_custom.oauth2.client;

import org.springframework.web.filter.CompositeFilter;

import java.util.List;

public class CustomSsoFilter extends CompositeFilter {

    public CustomSsoFilter(List <CustomOAuth2ClientAuthenticationProcessingFilter> customOAuth2ClientAuthenticationProcessingFilters) {
        this.setFilters(customOAuth2ClientAuthenticationProcessingFilters);
    }
}
@Bean
    public WxOAuth2ClientAuthenticationProcessingFilter wx() {
        WxResourceDetails resourceDetails = new WxResourceDetails();
        resourceDetails.setClientId("××××××");
        resourceDetails.setClientSecret("××××××××××");
        ArrayList <String> scopes = new ArrayList <>();
        scopes.add("××××");
        resourceDetails.setScope(scopes);
        resourceDetails.setGrantType("wx");
        resourceDetails.setAccessTokenUri("××××××××××");
        ResourceServerProperties resourceServerProperties = new ResourceServerProperties();
        resourceServerProperties.setUserInfoUri("××××××××××");
        return new WxOAuth2ClientAuthenticationProcessingFilter("/login/wx", resourceDetails,           resourceServerProperties, oauth2ClientContext);
    }

注意:添加下列代码

@Autowired
private OAuth2ClientContext oauth2ClientContext;
@Bean
public FilterRegistrationBean <OAuth2ClientContextFilter> oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
    FilterRegistrationBean <OAuth2ClientContextFilter> registration = new FilterRegistrationBean <OAuth2ClientContextFilter>();
    registration.setFilter(filter);
    registration.setOrder(-100);
    return registration;
}

 

授权服务器AuthorizationServerConfigurerAdapter,因为有一些其他的自定义配置,可以只看需要的

package cn.cjx913.spring_custom.oauth2;

import cn.cjx913.spring_custom.common.user.CustomUserDetailsService;
import cn.cjx913.spring_custom.oauth2.client.wx.authenticate.WxTokenGranter;
import cn.cjx913.spring_custom.oauth2.properties.OAuth2Client;
import cn.cjx913.spring_custom.oauth2.properties.OAuth2Properties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Import;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.builders.ClientDetailsServiceBuilder;
import org.springframework.security.oauth2.config.annotation.builders.InMemoryClientDetailsServiceBuilder;
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.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

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

//@Configuration
//@EnableAuthorizationServer
@EnableConfigurationProperties(OAuth2Properties.class)
@Import({
        OAuth2CustomBeanConfig.class,
        AuthServerWebMvcConfiguration.class
})
public class AuthServerCustomConfiguration extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private OAuth2Properties oAuth2Properties;
    @Autowired(required = false)
    private DataSource dataSource;

    @Autowired(required = false)
    private PasswordEncoder passwordEncoder;
    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManagerBean;

    @Autowired
    private TokenStore tokenStore;

    @Autowired(required = false)
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    @Autowired(required = false)
    private AbstractJwtTokenEnhancer jwtTokenEnhancer;

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess(oAuth2Properties.getTokenKeyAccess())
                .checkTokenAccess(oAuth2Properties.getCheckTokenAccess())
        ;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        if (OAuth2Properties.ClientType.IN_MEMORY.equals(oAuth2Properties.getClientType())) {
            List <OAuth2Client> oAuth2Clients = oAuth2Properties.getClients();
            if (!CollectionUtils.isEmpty(oAuth2Clients)) {
                InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
                for (OAuth2Client oAuth2Client : oAuth2Clients) {
                    ClientDetailsServiceBuilder <InMemoryClientDetailsServiceBuilder>.ClientBuilder clientBuilder =
                            builder
                                    .withClient(oAuth2Client.getClientId())
                                    .secret(passwordEncoder.encode(oAuth2Client.getSecret()))
                                    .accessTokenValiditySeconds(oAuth2Client.getAccessTokenValiditySeconds())
                                    .refreshTokenValiditySeconds(oAuth2Client.getRefreshTokenValiditySeconds());
                    String[] scopes = oAuth2Client.getScopes();
                    if (!ObjectUtils.isEmpty(scopes)) {
                        clientBuilder.scopes(scopes);
                    }
                    String[] authorizedGrantTypes = oAuth2Client.getAuthorizedGrantTypes();
                    if (!ObjectUtils.isEmpty(authorizedGrantTypes)) {
                        clientBuilder.authorizedGrantTypes(authorizedGrantTypes);
                    }
                    String[] redirectUris = oAuth2Client.getRedirectUris();
                    if (!ObjectUtils.isEmpty(redirectUris)) {
                        clientBuilder.redirectUris(redirectUris);
                    }
                    String[] resourceIds = oAuth2Client.getResourceIds();
                    if (!ObjectUtils.isEmpty(resourceIds)) {
                        clientBuilder.resourceIds(resourceIds);
                    }
                    Map <String, String> additionalInformation = new HashMap <>();
                    additionalInformation.put(OAuth2Constant.SECRET, oAuth2Client.getSecret());

                    String registrationId = oAuth2Client.getRegistrationId();
                    if (StringUtils.hasText(registrationId)) {
                        additionalInformation.put(OAuth2Constant.REGISTRATION_ID, registrationId);
                    }
                    if (!CollectionUtils.isEmpty(additionalInformation)) {
                        clientBuilder.additionalInformation(additionalInformation);
                    }
                }
                builder.build();
            }
        } else {
            Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
            Assert.notNull(dataSource, "dataSource can not be null");
            clients
                    .jdbc(dataSource)
                    .passwordEncoder(passwordEncoder)
                    .build()
            ;
        }
    }

    public TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
        List <TokenGranter> tokenGranters = new ArrayList <>();
        //4种默认的授权模式
        TokenGranter tokenGranter = endpoints.getTokenGranter();
        tokenGranters.add(tokenGranter);
        //自动义的授权模式
        WxTokenGranter wxTokenGranter = new WxTokenGranter(authenticationManagerBean, endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory());
        wxTokenGranter.setPasswordEncoder(passwordEncoder);
        tokenGranters.add(wxTokenGranter);
        CompositeTokenGranter delegate = new CompositeTokenGranter(tokenGranters);
        return delegate;
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .tokenStore(tokenStore)
                .authenticationManager(authenticationManagerBean)
                .userDetailsService(customUserDetailsService)
                .tokenGranter(tokenGranter(endpoints))
        ;

        if (tokenStore instanceof JwtTokenStore && jwtAccessTokenConverter != null) {
            if (jwtTokenEnhancer != null) {
                TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
                List <TokenEnhancer> enhancers = new ArrayList <TokenEnhancer>();
                enhancers.add(jwtTokenEnhancer);
                enhancers.add(jwtAccessTokenConverter);
                enhancerChain.setTokenEnhancers(enhancers);
                endpoints.tokenEnhancer(enhancerChain).accessTokenConverter(jwtAccessTokenConverter);
            } else {
                endpoints.accessTokenConverter(jwtAccessTokenConverter);
            }
        }
    }

    public DataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public PasswordEncoder getPasswordEncoder() {
        return passwordEncoder;
    }

    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    public CustomUserDetailsService getCustomUserDetailsService() {
        return customUserDetailsService;
    }

    public void setCustomUserDetailsService(CustomUserDetailsService customUserDetailsService) {
        this.customUserDetailsService = customUserDetailsService;
    }

    public AuthenticationManager getAuthenticationManagerBean() {
        return authenticationManagerBean;
    }

    public void setAuthenticationManagerBean(AuthenticationManager authenticationManagerBean) {
        this.authenticationManagerBean = authenticationManagerBean;
    }

    public TokenStore getTokenStore() {
        return tokenStore;
    }

    public void setTokenStore(TokenStore tokenStore) {
        this.tokenStore = tokenStore;
    }

    public JwtAccessTokenConverter getJwtAccessTokenConverter() {
        return jwtAccessTokenConverter;
    }

    public void setJwtAccessTokenConverter(JwtAccessTokenConverter jwtAccessTokenConverter) {
        this.jwtAccessTokenConverter = jwtAccessTokenConverter;
    }

    public AbstractJwtTokenEnhancer getJwtTokenEnhancer() {
        return jwtTokenEnhancer;
    }

    public void setJwtTokenEnhancer(AbstractJwtTokenEnhancer jwtTokenEnhancer) {
        this.jwtTokenEnhancer = jwtTokenEnhancer;
    }


}

注意PasswordEncoder,如果系统中有,在授权验证时会对客户端的client_id,client_secret匹配

在上述代码中我们需要添加的是WxTokenGranter

package cn.cjx913.spring_custom.oauth2.client.wx.authenticate;

import cn.cjx913.spring_custom.oauth2.OAuth2Constant;
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.crypto.password.PasswordEncoder;
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 org.springframework.util.Assert;

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

public class WxTokenGranter extends AbstractTokenGranter {
    private static final String GRANT_TYPE = "wx";

    private PasswordEncoder passwordEncoder;

    private final AuthenticationManager authenticationManager;

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

    protected WxTokenGranter(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) {
        Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
        Map <String, String> parameters = new LinkedHashMap <String, String>(tokenRequest.getRequestParameters());
        String wxCode = parameters.get("wxCode");
        String clientId = client.getClientId();

        Map <String, Object> additionalInformation = client.getAdditionalInformation();
        String clientSecret = String.valueOf(additionalInformation.get(OAuth2Constant.SECRET));

        String registrationId = String.valueOf(additionalInformation.get(OAuth2Constant.REGISTRATION_ID));

        Authentication userAuth = new WxAuthenticationToken(clientId, clientSecret, wxCode, registrationId);

        ((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 user: " + wxCode);
        }

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

    public AuthenticationManager getAuthenticationManager() {
        return authenticationManager;
    }

    public PasswordEncoder getPasswordEncoder() {
        return passwordEncoder;
    }

    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }
}

为什么要通过additionalInformation.get(OAuth2Constant.SECRET)获取client_secret?因为通过client.getClientSecret()是加密后的字符串

userAuth = authenticationManager.authenticate(userAuth);将进行用户验证

WxAuthenticationToken的包装
package cn.cjx913.spring_custom.oauth2.client.wx.authenticate;

import cn.cjx913.spring_custom.common.user.CustomUser;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.util.Base64Utils;

import javax.validation.constraints.NotNull;
import java.nio.charset.Charset;

public class WxAuthenticationToken extends AbstractAuthenticationToken {
    private String appId;
    private String appSecret;
    private String wxCode;
    private String registrationId;
    private CustomUser customUser;

    public WxAuthenticationToken(String appId, String appSecret, String wxCode, String registrationId) {
        super(null);
        this.appId = appId;
        this.appSecret = appSecret;
        this.wxCode = wxCode;
        this.registrationId = registrationId;
        setAuthenticated(false);
    }

    public WxAuthenticationToken(@NotNull CustomUser customUser) {
        super(customUser.getAuthorities());
        this.customUser = customUser;
        super.setAuthenticated(true);
    }

    @Override
    public Object getPrincipal() {
        return customUser;
    }

    @Override
    public Object getCredentials() {
        if (customUser == null) {
            return null;
        }
        return Base64Utils.encodeToString((customUser.getUserId() + "_" + customUser.getUsername()).getBytes(Charset.forName("UTF-8")));
    }


    @Override
    public void setAuthenticated(boolean authenticated) {
        if (authenticated) {
            throw new IllegalArgumentException(
                    "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
        }
        super.setAuthenticated(false);
    }

    public String getAppId() {
        return appId;
    }

    public String getAppSecret() {
        return appSecret;
    }

    public String getWxCode() {
        return wxCode;
    }

    public String getRegistrationId() {
        return registrationId;
    }

    public CustomUser getCustomUser() {
        return customUser;
    }

}
用户验证需要WxAuthenticationProvider,在WebSecurityConfigurerAdapter的中有
@Bean
public WxAuthenticationProvider wxAuthenticationProvider(CustomUserDetailsService customUserDetailsService) {
    return new WxAuthenticationProvider(customUserDetailsService);
}

@Autowired
private WxAuthenticationProvider wxAuthenticationProvider;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(wxAuthenticationProvider);

    auth
            .userDetailsService(customUserDetailsService)
            .passwordEncoder(passwordEncoder)
    ;
}

下面看WxAuthenticationProvider

package cn.cjx913.spring_custom.oauth2.client.wx.authenticate;

import cn.cjx913.spring_custom.common.user.CustomUser;
import cn.cjx913.spring_custom.common.user.CustomUserDetailsService;
import cn.cjx913.spring_custom.oauth2.OAuth2Constant;
import cn.cjx913.spring_custom.oauth2.client.wx.exception.WxAuthenticationException;
import cn.cjx913.spring_custom.oauth2.client.wx.exception.WxUnregisterAuthenticationException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.util.Map;


public class WxAuthenticationProvider implements AuthenticationProvider {
    private CustomUserDetailsService customUserDetailsService;
    private RestTemplate restTemplate;
    private ObjectMapper objectMapper;

    private final static String URL = OAuth2Constant.JSCODE_2_SESSION_URL;

    {
        if (restTemplate == null) {
            restTemplate = new RestTemplate();
        }
        if (objectMapper == null) {
            objectMapper = new ObjectMapper();
        }
    }

    public WxAuthenticationProvider() {
    }

    public WxAuthenticationProvider(CustomUserDetailsService customUserDetailsService) {
        Assert.notNull(customUserDetailsService, WxAuthenticationProvider.class.getName() + ".customUserDetailsService cannot be null");
        this.customUserDetailsService = customUserDetailsService;
    }

    public WxAuthenticationProvider(CustomUserDetailsService customUserDetailsService, RestTemplate restTemplate, ObjectMapper objectMapper) {
        Assert.notNull(customUserDetailsService, WxAuthenticationProvider.class.getName() + ".customUserDetailsService cannot be null");
        Assert.notNull(restTemplate, WxAuthenticationProvider.class.getName() + ".restTemplate cannot be null");
        Assert.notNull(objectMapper, WxAuthenticationProvider.class.getName() + ".objectMapper cannot be null");
        this.customUserDetailsService = customUserDetailsService;
        this.restTemplate = restTemplate;
        this.objectMapper = objectMapper;
    }

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

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.notNull(customUserDetailsService, WxAuthenticationProvider.class.getName() + ".customUserDetailsService cannot be null");
        Assert.notNull(restTemplate, WxAuthenticationProvider.class.getName() + ".restTemplate cannot be null");
        Assert.notNull(objectMapper, WxAuthenticationProvider.class.getName() + ".objectMapper cannot be null");

        WxAuthenticationToken wxAuthenticationToken = (WxAuthenticationToken) authentication;
        Map <String, String> parameters = (Map <String, String>) authentication.getDetails();

        String appId = wxAuthenticationToken.getAppId();
        String appSecret = wxAuthenticationToken.getAppSecret();
        String wxCode = wxAuthenticationToken.getWxCode();
        String url = String.format(URL, appId, appSecret, wxCode);

        String result = restTemplate.getForObject(url, String.class);
        WxLoginResult wxLoginResult = null;
        try {
            wxLoginResult = objectMapper.readValue(result, WxLoginResult.class);
        } catch (IOException e) {
            throw new WxAuthenticationException("auth.code2Session请求错误!");
        }

        int errCode = wxLoginResult.getErrCode();
        if (errCode != 0) {
            throw new WxAuthenticationException(wxLoginResult);
        }
        String registrationId = wxAuthenticationToken.getRegistrationId();
        OAuth2User oAuth2User = new DefaultOAuth2User(AuthorityUtils.createAuthorityList("ROLE_USER"), wxLoginResult.toMap(), "openId");
        CustomUser customUser = customUserDetailsService.loadCustomUser(registrationId, oAuth2User);
        if (customUser == null) {
            throw new WxUnregisterAuthenticationException("用户未注册");
        }
        WxAuthenticationToken authenticationResult = new WxAuthenticationToken(customUser);
        return authenticationResult;
    }



    public void setCustomUserDetailsService(CustomUserDetailsService customUserDetailsService) {
        Assert.notNull(customUserDetailsService, WxAuthenticationProvider.class.getName() + ".customUserDetailsService cannot be null");
        this.customUserDetailsService = customUserDetailsService;
    }

    public void setRestTemplate(RestTemplate restTemplate) {
        Assert.notNull(restTemplate, WxAuthenticationProvider.class.getName() + ".restTemplate cannot be null");
        this.restTemplate = restTemplate;
    }

    public void setObjectMapper(ObjectMapper objectMapper) {
        Assert.notNull(objectMapper, WxAuthenticationProvider.class.getName() + ".objectMapper cannot be null");
        this.objectMapper = objectMapper;
    }
}
WxLoginResult
package cn.cjx913.spring_custom.oauth2.client.wx.authenticate;

import com.fasterxml.jackson.annotation.JsonAlias;
import lombok.Getter;
import lombok.Setter;

import java.util.HashMap;
import java.util.Map;

@Setter
@Getter
public class WxLoginResult {
    /**
     * 用户唯一标识
     */
    @JsonAlias("openid")
    private String openId;
    /**
     * 会话密钥
     */
    @JsonAlias("session_key")
    private String sessionKey;
    /**
     * 用户在开放平台的唯一标识符,
     * 在满足 UnionID
     * 下发条件的情况下会返回,
     * 详见 UnionID
     * 机制说明。
     */
    @JsonAlias("union_id")
    private String unionId;

    /**
     * 错误码
     * -1	系统繁忙,此时请开发者稍候再试
     * 0	请求成功
     * 40029	code 无效
     * 45011	频率限制,每个用户每分钟100次
     */
    @JsonAlias("errcode")
    private int errCode;
    /**
     * 错误信息
     */
    @JsonAlias("errmsg")
    private String errMsg;

    public Map <String, Object> toMap() {
        HashMap <String, Object> map = new HashMap <>();
        map.put("openId", openId);
        map.put("unionId", unionId);
        map.put("sessionKey", sessionKey);
        map.put("errCode", errCode);
        map.put("errMsg", errMsg);
        return map;
    }

}
CustomUserDetailsService,这里主要是自定义的获取本地用户的接口
package cn.cjx913.spring_custom.common.user;

import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.oauth2.core.user.OAuth2User;

public interface CustomUserDetailsService extends UserDetailsService {
    /**
     * 第三方登录获取与系统对应的用户
     *
     * @param registrationId 第三方系统唯一标识
     * @param oAuth2User     第三方系统中用户
     * @return
     */
    CustomUser loadCustomUser(String registrationId, OAuth2User oAuth2User);

    /**
     * 系统注册第三方登录用户
     *
     * @param oAuth2User
     * @return
     */
    CustomUser registerCustomUser(String registrationId, OAuth2User oAuth2User);

    @Override
    CustomUser loadUserByUsername(String username) throws UsernameNotFoundException;
}

大概是这么多,可能有些地方不好说清楚,写的不好勿喷,源码暂不公开(因为没有充分的测试,而且涉及很多自定义封装的),可以自己研究研究或者留言,需要了解spring-security-oauth2-autoconfigure的具体执行过程,关键是OAuth2ClientAuthenticationProcessingFilter和AbstractTokenGranter(TokenGranter)。了解这些可以修改一些请求参数或者自定义授权模式,微信登录就可以很好解决,例如

AuthorizationCodeAccessTokenProvider(授权码模式)可以做如下修改
 protected MultiValueMap <String, String> getParametersForAuthorizeRequest(AuthorizationCodeResourceDetails resource,
                                                                            AccessTokenRequest request) {
        MultiValueMap <String, String> form = new LinkedMultiValueMap <String, String>();
        form.set("response_type", "code");
        form.set("appid", resource.getClientId());

        if (request.get("scope") != null) {
            form.set("scope", request.getFirst("scope"));
        } else {
            form.set("scope", OAuth2Utils.formatParameterList(resource.getScope()));
        }

        // Extracting the redirect URI from a saved request should ignore the current URI, so it's not simply a call to
        // resource.getRedirectUri()
        String redirectUri = resource.getPreEstablishedRedirectUri();

        Object preservedState = request.getPreservedState();
        if (redirectUri == null && preservedState != null) {
            // no pre-established redirect uri: use the preserved state
            // TODO: treat redirect URI as a special kind of state (this is a historical mini hack)
            redirectUri = String.valueOf(preservedState);
        } else {
            redirectUri = request.getCurrentUri();
        }

        String stateKey = request.getStateKey();
        if (stateKey != null) {
            form.set("state", stateKey);
            if (preservedState == null) {
                throw new InvalidRequestException(
                        "Possible CSRF detected - state parameter was present but no state could be found");
            }
        }

        if (redirectUri != null) {
            form.set("redirect_uri", redirectUri);
        }

        return form;
    }

    protected MultiValueMap <String, String> getParametersForTokenRequest(WechatResourceDetails resource,
                                                                        AccessTokenRequest request) {

        MultiValueMap <String, String> form = new LinkedMultiValueMap <String, String>();
        form.set("grant_type", "authorization_code");
        form.set("code", request.getAuthorizationCode());
        form.set("appid", resource.getClientId());
        form.set("secret", resource.getClientSecret());
        Object preservedState = request.getPreservedState();
        if (request.getStateKey() != null || stateMandatory) {
            // The token endpoint has no use for the state so we don't send it back, but we are using it
            // for CSRF detection client side...
            if (preservedState == null) {
                throw new InvalidRequestException(
                        "Possible CSRF detected - state parameter was required but no state could be found");
            }
        }

        // Extracting the redirect URI from a saved request should ignore the current URI, so it's not simply a call to
        // resource.getRedirectUri()
        String redirectUri = null;
        // Get the redirect uri from the stored state
        if (preservedState instanceof String) {
            // Use the preserved state in preference if it is there
            // TODO: treat redirect URI as a special kind of state (this is a historical mini hack)
            redirectUri = String.valueOf(preservedState);
        } else {
            redirectUri = resource.getRedirectUri(request);
        }

        if (redirectUri != null && !"NONE".equals(redirectUri)) {
            form.set("redirect_uri", redirectUri);
        }

        return form;

    }

    protected UserRedirectRequiredException getRedirectForAuthorization(WechatResourceDetails resource,
                                                                      AccessTokenRequest request) {

        // we don't have an authorization code yet. So first get that.
        TreeMap <String, String> requestParameters = new TreeMap <String, String>();
        requestParameters.put("response_type", "code"); // oauth2 spec, section 3
        requestParameters.put("appid", resource.getClientId());
        // Client secret is not required in the initial authorization request

        String redirectUri = resource.getRedirectUri(request);
        if (redirectUri != null) {
            requestParameters.put("redirect_uri", redirectUri);
        }

        if (resource.isScoped()) {

            StringBuilder builder = new StringBuilder();
            List <String> scope = resource.getScope();

            if (scope != null) {
                Iterator <String> scopeIt = scope.iterator();
                while (scopeIt.hasNext()) {
                    builder.append(scopeIt.next());
                    if (scopeIt.hasNext()) {
                        builder.append(' ');
                    }
                }
            }

            requestParameters.put("scope", builder.toString());
        }

        UserRedirectRequiredException redirectException = new UserRedirectRequiredException(
                resource.getUserAuthorizationUri(), requestParameters);

        String stateKey = stateKeyGenerator.generateKey(resource);
        redirectException.setStateKey(stateKey);
        request.setStateKey(stateKey);
        redirectException.setStateToPreserve(redirectUri);
        request.setPreservedState(redirectUri);

        return redirectException;

    }

看方法名可以知道其作用,不多说了

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_40080972/article/details/91396575
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2021-05-19 02:46:14
  • 阅读 ( 758 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢