【Spring Security OAuth2 自定义授权实现微信小程序登录实现】

在Spring Security OAuth2框架下实现微信小程序登录,需要自定义授权流程。微信小程序登录与传统的OAuth2流程有所不同,主要区别在于微信使用code换取session_key和openid的机制。

实现步骤

1. 添加依赖

首先确保你的项目中包含Spring Security OAuth2和微信相关SDK:

<!-- Spring Security OAuth2 -->
<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.6.8</version>
</dependency>

<!-- 微信小程序Java SDK -->
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-miniapp</artifactId>
    <version>4.1.0</version>
</dependency>

2. 配置微信小程序参数

在application.yml中添加配置:

wechat:
  miniapp:
    appid: your_appid
    secret: your_secret

3. 创建自定义TokenGranter

public class WechatMiniAppTokenGranter extends AbstractTokenGranter {
    
    private static final String GRANT_TYPE = "wechat_miniapp";
    
    private final WxMaService wxMaService;
    
    public WechatMiniAppTokenGranter(AuthorizationServerTokenServices tokenServices,
                                   ClientDetailsService clientDetailsService,
                                   OAuth2RequestFactory requestFactory,
                                   WxMaService wxMaService) {
        super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
        this.wxMaService = wxMaService;
    }
    
    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
        Map<String, String> parameters = new LinkedHashMap<>(tokenRequest.getRequestParameters());
        
        // 获取微信小程序code
        String code = parameters.get("code");
        
        try {
            // 用code换取session_key和openid
            WxMaJscode2SessionResult session = wxMaService.getUserService().getSessionInfo(code);
            String openid = session.getOpenid();
            
            // 根据openid查询或创建用户
            UserDetails userDetails = loadOrCreateUserByOpenid(openid);
            
            // 构建用户认证信息
            UsernamePasswordAuthenticationToken userAuth = new UsernamePasswordAuthenticationToken(
                userDetails, null, userDetails.getAuthorities());
            
            // 构建OAuth2请求
            OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
            
            return new OAuth2Authentication(storedOAuth2Request, userAuth);
        } catch (WxErrorException e) {
            throw new InvalidGrantException("微信登录失败: " + e.getMessage());
        }
    }
    
    private UserDetails loadOrCreateUserByOpenid(String openid) {
        // 实现根据openid查询或创建用户的逻辑
        // 这里应该是你的业务逻辑
        // ...
    }
}

4. 配置授权服务器

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
    private UserDetailsService userDetailsService;
    
    @Autowired
    private WxMaService wxMaService;
    
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        // 添加自定义的TokenGranter
        endpoints.tokenGranter(tokenGranter(endpoints));
        
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }
    
    private TokenGranter tokenGranter(AuthorizationServerEndpointsConfigurer endpoints) {
        List<TokenGranter> granters = new ArrayList<>(Collections.singletonList(endpoints.getTokenGranter()));
        
        // 添加微信小程序授权模式
        granters.add(new WechatMiniAppTokenGranter(
            endpoints.getTokenServices(),
            endpoints.getClientDetailsService(),
            endpoints.getOAuth2RequestFactory(),
            wxMaService
        ));
        
        return new CompositeTokenGranter(granters);
    }
    
    // 其他配置...
}

5. 客户端调用方式

微信小程序前端获取code后,调用后端接口:

wx.login({
  success(res) {
    if (res.code) {
      // 发送code到后端
      wx.request({
        url: 'http://your-server/oauth/token',
        method: 'POST',
        data: {
          grant_type: 'wechat_miniapp',
          code: res.code,
          client_id: 'your_client_id',
          client_secret: 'your_client_secret'
        },
        success(res) {
          console.log('登录成功', res.data);
        }
      });
    }
  }
});

6. 后端响应

成功响应将返回标准的OAuth2令牌:

{
    "access_token": "xxx",
    "token_type": "bearer",
    "refresh_token": "xxx",
    "expires_in": 3600,
    "scope": "read write"
}

安全注意事项

验证客户端凭证:确保只有受信任的客户端可以使用此授权类型

保护用户数据:不要将openid等敏感信息返回给客户端

防止重放攻击:微信的code只能使用一次,确保你的实现符合这一要求

会话管理:合理设置access_token和refresh_token的过期时间

扩展功能

用户信息解密:如果需要获取用户手机号等加密信息,可以实现解密逻辑

多小程序支持:可以通过在请求中添加appid参数来支持多个小程序

JWT令牌:可以考虑使用JWT代替默认的令牌格式,包含更多用户信息

更多推荐