×

小程序授权登录 Java 怎么实现?从流程到代码手把手讲清楚

作者:Terry2025.09.15来源:Web前端之家浏览:55评论:0

image.png

现在做小程序开发,授权登录几乎是标配功能,用户点一下授权按钮,就能快速登录小程序,背后其实是前端和 Java 后端的一系列交互逻辑,很多刚接触的同学会疑惑:小程序授权登录和普通网页登录有啥区别?Java 后端要怎么对接微信的授权流程?代码里又该怎么处理安全、用户信息关联这些问题?今天就从流程到代码,把小程序授权登录结合 Java 实现的思路掰开了讲,不管是新手还是想优化现有逻辑的同学,都能找到有用的点。

小程序授权登录的核心流程是怎样的?

得先把整体流程理清楚,不然代码写起来没方向,以微信小程序为例(现在大部分小程序授权登录都是基于微信生态,其他平台逻辑类似,换接口就行),授权登录不是简单的“点按钮→登录成功”,而是前端和后端配合,还要调用微信官方接口的过程。

前端小程序端要做什么?

  1. 引导用户授权与获取 code:通过 <button open - type="getUserInfo"> 这类组件引导用户授权头像、昵称等信息(若需用户信息);同时调用 wx.login() 获取临时登录凭证 code,这个 code 有效期短(一般几分钟)且仅能使用一次。

  2. 传递 code 给后端:前端拿到 code 后,通过 wx.request 等方式将 code 发送给后端接口,这是后端与微信服务器交互的“钥匙”。

微信服务器扮演什么角色?

后端拿到 code 后,需调用微信的 auth.code2Session 接口(地址为 https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=CODE&grant_type=authorization_code),传入小程序的 appidsecret(在微信公众平台后台查看)和前端传的 code,调用成功后,微信会返回 openid(用户在该小程序的唯一标识)、session_key(会话密钥,用于解密用户信息)、unionid(若用户授权且满足条件,多小程序 / 公众号下的唯一标识)等关键信息。

Java 后端要做什么?

拿到 openidsession_key 后,后端需完成这些操作:

  • 检查用户注册状态:查询数据库,看 openid 对应的用户是否存在,新用户需创建用户信息(结合前端传的用户头像、昵称或后续完善信息);老用户则直接关联。

  • 生成自定义登录态:比如用 JWT 生成 token,将用户标识(openid 或用户 id)加密到 token 里返回给前端,前端后续请求携带该 token,后端验证 token 以识别用户。

  • 安全存储 session_keysession_key 用于解密用户敏感信息(如手机号),需安全存储,一般存于 Redis 并与用户登录态关联,设置有效期。

Java 后端要处理哪些关键环节?

知道整体流程后,来看 Java 后端每个环节的细节,这是实现的核心,需把每个步骤的逻辑和注意事项讲透。

接收前端传来的 code

前端通过接口把 code 发过来,Java 后端用 Controller 层接收,示例代码如下:

@PostMapping("/login")
public Result login(@RequestBody LoginRequest request) {
    String code = request.getCode();
    // 后续处理 code
    return Result.success(...);
}

这里要注意参数校验,code 不能为空,否则直接返回错误,避免无效请求进入后续步骤。

调用微信 code2Session 接口

后端拿到 code 后,需主动调用微信接口,这一步要处理 HTTP 请求和可能的网络异常、参数错误,可自行封装 HTTP 工具类,或使用 Spring 的 RestTemplate、OkHttp 等库。

RestTemplate 的示例:先拼接请求 URL

String url = "https://api.weixin.qq.com/sns/jscode2session?appid={appid}&secret={secret}&js_code={code}&grant_type=authorization_code";
Map<String, String> params = new HashMap<>();
params.put("appid", wxAppId); // 从配置文件读 appid
params.put("secret", wxSecret); // 读 secret
params.put("code", code);

然后发起 GET 请求并解析返回结果,微信返回的是 JSON,成功时结构如:

{  
  "openid": "xxx",  
  "session_key": "xxx",  
  "unionid": "xxx"  
}

失败时会返回 errcodeerrmsg,如 code 无效时 errcode = 40029,所以后端要处理两种情况:成功则获取 openid 等信息,失败则返回错误给前端。

这里要注意网络稳定性:微信接口偶尔可能超时,可加重试机制(如用 Spring 的 Retry 注解)或记录日志以便排查。appidsecret 要保密,勿硬编码在代码里,应放在配置文件(如 application.yml),用 @Value 注入。

处理用户信息与数据库关联

拿到 openid 后,要查询数据库中是否有该用户,假设数据库有 user 表,包含 openidnicknameavatar 等字段。

  • 新用户:若没查到 openid 对应的用户,说明是首次登录,若前端传了用户昵称、头像(如用户授权时传递),则将这些信息与 openid 一起插入数据库创建新用户。

  • 老用户:若查到了,就获取用户的 id 等信息,用于后续生成登录态。

这里有个细节:是否需要强制用户授权头像昵称? 现在微信小程序的 getUserInfo 接口已不推荐,改用 getProfile 接口(需用户主动触发),所以若仅做登录,用 code + openid 即可;若要获取用户信息,需引导用户授权,后端再存储信息。

生成并返回自定义登录态

用户信息确定后,后端要生成登录态给前端,让前端后续请求能证明“我是我”,常用做法是用 JWT(JSON Web Token)。

JWT 原理是将用户标识(如用户 id、openid)加密成 token,包含头部、载荷、签名,生成时用密钥(后端保存,勿泄露),验证时用同一密钥解密。

代码示例(用 JJWT 库):

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
public class JwtUtil {
    private static final String SECRET = "your - secret - key"; // 配置文件里的密钥
    private static final long EXPIRATION = 3600 * 1000; // 1 小时有效期
    public static String generateToken(String subject) {
        return Jwts.builder()
               .setSubject(subject) // 存用户 id 或 openid
               .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
               .signWith(SignatureAlgorithm.HS256, SECRET)
               .compact();
    }
    public static String getSubject(String token) {
        return Jwts.parser()
               .setSigningKey(SECRET)
               .parseClaimsJws(token)
               .getBody()
               .getSubject();
    }
}

生成 token 后返回给前端,前端存到 storage 里,每次请求时放在请求头(如 Authorization: Bearer xxx),后端拦截器或过滤器验证 token,解析出用户标识,就能知道是哪个用户在请求。

安全存储 session_key

session_key 是解密用户敏感信息(如手机号,需前端用 session_key 加密后传给后端,后端解密)的关键,必须安全存储,不能明文存在数据库或日志里。

推荐做法:将 session_key 存到 Redis,key 可结合 openid 或用户 id,设置与 code 相同的有效期(因 code 仅能使用一次,session_key 也有有效期,一般与 code 同步),这样下次用户请求解密信息时,从 Redis 取 session_key,用完删除或过期自动删除,降低风险。

示例(用 RedisTemplate):

@Autowired
private RedisTemplate<String, String> redisTemplate;
// 存储 session_key
redisTemplate.opsForValue().set("session_key:" + openid, sessionKey, 5, TimeUnit.MINUTES);
// 获取 session_key
String sessionKey = redisTemplate.opsForValue().get("session_key:" + openid);

具体代码怎么写?分模块拆解

光讲逻辑不够,要有实际代码示例,把各个模块串起来,下面分配置、工具类、Controller、Service 来讲。

配置文件(application.yml)

把小程序的 appidsecret,还有 JWT 密钥、Redis 配置(若用 Redis)放在这里:

wx:
  miniapp:
    appid: xxxxxxxx # 微信公众平台的 appid
    secret: xxxxxxxx # 对应的 secret
jwt:
  secret: your - jwt - secret # JWT 签名密钥
redis:
  host: localhost
  port: 6379

然后用 @ConfigurationProperties 或者 @Value 注入到代码里:

@Component
@ConfigurationProperties(prefix = "wx.miniapp")
public class WxConfig {
    private String appid;
    private String secret;
    // get/set 方法
}

HTTP 请求工具类(调用微信接口)

写个通用工具类,发起 GET 请求并解析 JSON,这里用 RestTemplate

import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
public class WxHttpUtil {
    private static final RestTemplate restTemplate = new RestTemplate();
    public static <T> T get(String url, Class<T> responseType) {
        ResponseEntity<T> response = restTemplate.exchange(url, HttpMethod.GET, null, responseType);
        return response.getBody();
    }
}

封装微信返回的实体类

code2Session 返回的结果可用实体类接收:

@Data
public class WxCode2SessionResult {
    private String openid;
    private String session_key;
    private String unionid;
    private Integer errcode;
    private String errmsg;
}

Service 层处理业务逻辑

Service 负责调用微信接口、查数据库、生成 token

@Service
public class AuthService {
    @Autowired
    private WxConfig wxConfig;
    @Autowired
    private UserMapper userMapper; // 假设 MyBatis 的 Mapper
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Value("${jwt.secret}")
    private String jwtSecret;
    public LoginResponse login(String code, UserInfoDTO userInfo) {
        // 1. 调用微信 code2Session 接口
        String url = String.format("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
                wxConfig.getAppid(), wxConfig.getSecret(), code);
        WxCode2SessionResult result = WxHttpUtil.get(url, WxCode2SessionResult.class);
        if (result.getErrcode() != null && result.getErrcode() != 0) {
            throw new BusinessException("微信授权失败:" + result.getErrmsg());
        }
        String openid = result.getOpenid();
        String sessionKey = result.getSession_key();
        // 2. 处理用户信息:查库或新增
        User user = userMapper.findByOpenid(openid);
        if (user == null) {
            // 新用户,插入数据库
            user = new User();
            user.setOpenid(openid);
            user.setNickname(userInfo.getNickname());
            user.setAvatar(userInfo.getAvatar());
            userMapper.insert(user);
        }
        // 3. 存储 session_key 到 Redis
        redisTemplate.opsForValue().set("session_key:" + openid, sessionKey, 5, TimeUnit.MINUTES);
        // 4. 生成 JWT token
        String token = Jwts.builder()
               .setSubject(String.valueOf(user.getId())) // 存用户 id
               .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))
               .signWith(SignatureAlgorithm.HS256, jwtSecret)
               .compact();
        return new LoginResponse(token, user);
    }
}

Controller 层接收请求

Controller 负责接收前端参数,调用 Service:

@RestController
@RequestMapping("/auth")
public class AuthController {
    @Autowired
    private AuthService authService;
    @PostMapping("/login")
    public Result login(@RequestBody LoginRequest request) {
        String code = request.getCode();
        UserInfoDTO userInfo = request.getUserInfo(); // 前端传的用户信息
        LoginResponse response = authService.login(code, userInfo);
        return Result.success(response);
    }
}

拦截器验证 JWT(可选,处理后续请求)

若要验证后续请求的 token,写个拦截器:

public class JwtInterceptor implements HandlerInterceptor {
    @Value("${jwt.secret}")
    private String jwtSecret;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("Authorization");
        if (token == null || !token.startsWith("Bearer ")) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }
        token = token.substring(7); // 去掉 Bearer 
        try {
            Claims claims = Jwts.parser()
                   .setSigningKey(jwtSecret)
                   .parseClaimsJws(token)
                   .getBody();
            String userId = claims.getSubject();
            // 把用户 id 放到请求属性里,后续 Controller 可以获取
            request.setAttribute("userId", userId);
            return true;
        } catch (Exception e) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }
    }
}

然后在配置类里注册拦截器:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JwtInterceptor())
               .addPathPatterns("/**") // 拦截所有请求,除了登录接口
               .excludePathPatterns("/auth/login");
    }
}

常见问题怎么解决?

做小程序授权登录时,总会碰到各种坑,这里列几个高频问题和解决思路。

code 失效或重复使用怎么办?

code 有效期短(几分钟)且仅能使用一次,所以前端要确保每次登录都重新调用 wx.login() 获取新 code,后端拿到 code 后立即调用 code2Session,勿存储复用,若前端传的 code 已被使用过,微信返回 errcode = 40029,后端要捕获该错误,返回给前端“请重新授权登录”,让前端重新获取 code

用 session_key 解密用户信息失败?

比如前端用 session_key 加密了手机号,传给后端解密时失败,原因可能是:

  • session_key 过期或已被销毁:因 session_keycode 一一对应,code 用过后 session_key 也会失效,所以要确保解密时 session_key 有效,最好存在 Redis 里,设置与 code 相同的有效期,解密后删除。

  • 加密方式不对:前端要用微信提供的加密算法(如 AES - 128 - CBC),后端解密时要严格按微信要求,如填充方式、IV 向量等,可参考微信官方文档的“加密数据解密算法”部分,用官方提供的示例代码改造。

分布式系统下登录态怎么共享?

若后端是集群部署,多个服务器实例,JWT 本身是自包含的,只要所有服务器用同一个 jwt.secret,就能解密,但如果用 Redis 存 session_key,要确保 Redis 是共享的(如用 Redis 集群),这样不同服务器实例都能拿到 session_key

授权登录后怎么结合权限控制?

比如某些接口需要用户是 VIP 才能访问,可以在 JWT 的载荷里加入用户的角色、权限信息,或在数据库里存用户权限,每次请求时,拦截器拿到用户 id 后,查数据库或 Redis 里的权限信息,判断是否有权限访问。

做好这几步,小程序授权登录稳了

小程序授权登录结合 Java 后端实现,核心是理解“前端拿 code→后端换 openid 和 session_key→关联用户→生成自定义登录态”这个流程,代码实现时,要

您的支持是我们创作的动力!
温馨提示:本文作者系Terry ,经Web前端之家编辑修改或补充,转载请注明出处和本文链接:
https://www.jiangweishan.com/article/xiaoxhengcusdjfj235.html

网友评论文明上网理性发言 已有0人参与

发表评论: