Implémentation de l'authentification WeChat dans une application Spring Boot

Préparation de l'environnement de test WeChat

Pour développer et tester l'intégration, il est nécessaire d'utiliser un compte de test officiel de WeChat via leur plateforme de développement.

Configuration de la validation du serveur

Votre application doit être accessible publiquement pour que les serveurs de WeChat puissent envoyer des requêtes de validation. Vous pouvez utiliser un serveur public ou un outil de tunneling pour exposer votre application locale.

Voici un exemple de contrôleur pour valider le token de WeChat :

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/auth/wechat")
public class WeChatAuthController {

    @GetMapping("/verify")
    public String verifyServer(@RequestParam("echostr") String echoStr) {
        return echoStr;
    }
}

L'URL à configurer dans la console WeChat sera par exemple : https://votre-domaine.com/auth/wechat/verify.

Configuration des domaines OAuth et JS

Dans la console de test, configurez le domaine de sécurité JS et le domaine de redirection d'autorisation de page Web avec l'adresse de votre serveur (sans le préfixe http:// ou https://).

Flux d'authentification OAuth2

Le processus de conneixon suit le standard OAuth2 :

  1. Le client est redirigé vers l'URL d'autorisation WeChat.
  2. L'utilisateur autorise l'application, et WeChat redirige vers votre serveur avec un paramètre code.
  3. Votre serveur échange ce code contre un access_token et un openid.
  4. Enfin, l'access_token est utilisé pour récupérer les informations du profil utilisateur.

Implémentation Backend avec Spring Boot

Dépendances Maven

Ajoutez les dépendances nécessaires pour Spring Web, Spring Data JPA et SpringDoc pour la documentation API.

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springdoc</groupId>
        <artifactId>springdoc-openapi-ui</artifactId>
        <version>1.7.0</version>
    </dependency>
</dependencies>

Classe utilitaire pour l'API WeChat

Création d'un composant pour gérer les appels HTTP vers les endpoints de WeChat en utilisant RestTemplate.

import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;

@Component
public class WeChatOAuthHelper {

    private static final String APP_ID = "YOUR_APP_ID";
    private static final String APP_SECRET = "YOUR_APP_SECRET";
    private static final String AUTH_URL = "https://open.weixin.qq.com/connect/oauth2/authorize";
    private static final String TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token";
    private static final String USER_INFO_URL = "https://api.weixin.qq.com/sns/userinfo";
    
    private final RestTemplate restTemplate;

    public WeChatOAuthHelper(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public String buildAuthRedirectUrl(String callbackUri, String state) {
        String encodedUri = URLEncoder.encode(callbackUri, StandardCharsets.UTF_8);
        return "redirect:" + AUTH_URL + "?appid=" + APP_ID + 
               "&redirect_uri=" + encodedUri + 
               "&response_type=code&scope=snsapi_userinfo&state=" + state + 
               "#wechat_redirect";
    }

    @SuppressWarnings("unchecked")
    public Map<String, Object> fetchAccessToken(String code) {
        String url = TOKEN_URL + "?appid=" + APP_ID + "&secret=" + APP_SECRET + 
                     "&code=" + code + "&grant_type=authorization_code";
        Map<String, Object> response = restTemplate.getForObject(url, Map.class);
        if (response != null && response.containsKey("errcode")) {
            throw new RuntimeException("Échec de l'obtention du token: " + response.get("errmsg"));
        }
        return response;
    }

    public Map<String, Object> fetchUserInfo(String accessToken, String openId) {
        String url = USER_INFO_URL + "?access_token=" + accessToken + "&openid=" + openId + "&lang=fr_FR";
        return restTemplate.getForObject(url, Map.class);
    }
}

Entité de profil utilisateur

Définition de l'entité JPA pour persister les données de l'utilisateur WeChat.

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "wechat_users")
public class WeChatUser {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false)
    private String openId;

    private String unionId;
    private String nickname;
    private String avatarUrl;
    private String accessToken;
    private String refreshToken;
    
    @Column(name = "created_at")
    private LocalDateTime createdAt;

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getOpenId() { return openId; }
    public void setOpenId(String openId) { this.openId = openId; }
    public String getUnionId() { return unionId; }
    public void setUnionId(String unionId) { this.unionId = unionId; }
    public String getNickname() { return nickname; }
    public void setNickname(String nickname) { this.nickname = nickname; }
    public String getAvatarUrl() { return avatarUrl; }
    public void setAvatarUrl(String avatarUrl) { this.avatarUrl = avatarUrl; }
    public String getAccessToken() { return accessToken; }
    public void setAccessToken(String accessToken) { this.accessToken = accessToken; }
    public String getRefreshToken() { return refreshToken; }
    public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; }
    public LocalDateTime getCreatedAt() { return createdAt; }
    public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
}

Contrôleur d'authentification

Mise en place des endpoints REST pour orchestrer le flux de connexion.

import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.Map;

@RestController
@RequestMapping("/auth/wechat")
public class WeChatAuthController {

    private final WeChatOAuthHelper weChatOAuthHelper;
    private final WeChatUserRepository userRepository;

    public WeChatAuthController(WeChatOAuthHelper weChatOAuthHelper, WeChatUserRepository userRepository) {
        this.weChatOAuthHelper = weChatOAuthHelper;
        this.userRepository = userRepository;
    }

    @GetMapping("/verify")
    public String verifyServer(@RequestParam("echostr") String echoStr) {
        return echoStr;
    }

    @GetMapping("/authorize")
    public String initiateLogin(@RequestParam(defaultValue = "default_state") String state) {
        String callbackUrl = "https://your-domain.com/auth/wechat/callback";
        return weChatOAuthHelper.buildAuthRedirectUrl(callbackUrl, state);
    }

    @GetMapping("/callback")
    public String handleCallback(@RequestParam("code") String code, @RequestParam("state") String state) {
        Map<String, Object> tokenData = weChatOAuthHelper.fetchAccessToken(code);
        
        String accessToken = (String) tokenData.get("access_token");
        String refreshToken = (String) tokenData.get("refresh_token");
        String openId = (String) tokenData.get("openid");

        Map<String, Object> userInfo = weChatOAuthHelper.fetchUserInfo(accessToken, openId);

        WeChatUser user = userRepository.findByOpenId(openId).orElse(new WeChatUser());
        user.setOpenId(openId);
        user.setUnionId((String) userInfo.get("unionid"));
        user.setNickname((String) userInfo.get("nickname"));
        user.setAvatarUrl((String) userInfo.get("headimgurl"));
        user.setAccessToken(accessToken);
        user.setRefreshToken(refreshToken);
        user.setCreatedAt(LocalDateTime.now());

        userRepository.save(user);

        return "<h1>Authentification WeChat réussie !</h1>";
    }
}

Tests et Validation

Pour tester le flux, suivez ces étapes :

  1. Abonnez-vous au compte de test via le QR code fourni dans la console WeChat.
  2. Générez un QR code pointant vers l'endpoint /auth/wechat/authorize de votre application en utilisant un générateur de QR code en ligne.
  3. Scannez ce QR code avec l'application mobile WeChat.
  4. L'application vous demandera d'autoriser l'accès à vos informations. Après validation, vous serez redirigé vers l'endpoint de callback qui récupérera le code, l'échangera contre un token, et sauvegardera le profil.

Assurez-vous que le paramètre scope est bien défini sur snsapi_userinfo dans la classe utilitaire pour obtenir les détails complets du profil (surnom, avatar, etc.).

Étiquettes: spring-boot wechat-api oauth2 Java spring-data-jpa

Publié le 14 juillet à 05h38