微信小程序授权登录+JWT
**备注:**依赖有点老了,新版0.11.5,若用新版工具类自行封装,为了后台不改动,我只能凑合用了
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version></dependency>
工具类:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import java.util.Date;
import java.util.HashMap;
import java.util.Map;/*** JWT 工具类(兼容 jjwt 0.9.1)* 用于生成和解析 JWT Token*/
@Component
public class JwtUtil {// 令牌秘钥@Value("${token.secret}")private String SECRET_KEY;private static final long EXPIRATION_TIME = 1000L * 60 * 60 * 24 * 7; // 7 天,单位毫秒/*** 生成 JWT Token* @param openId 用户唯一标识,如微信 openid* @return JWT Token 字符串*/public String generateToken(String openId) {Map<String, Object> claims = new HashMap<>();claims.put("openId", openId); // 可放入自定义信息,比如用户ID等return Jwts.builder().setClaims(claims) // 自定义 claims.setSubject(openId) // 主题,通常也放 openid.setIssuedAt(new Date()) // 签发时间.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // 过期时间.signWith(SignatureAlgorithm.HS256, SECRET_KEY) // ✅ 使用字符串密钥.compact();}/*** 从 Token 中解析出 openid* @param token JWT Token* @return openid*/public String getOpenIdFromToken(String token) {Claims claims = Jwts.parser().setSigningKey(SECRET_KEY) // ✅ 使用字符串密钥.parseClaimsJws(token).getBody();return claims.getSubject();}
}
拦截器:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bladex.common.utils.JwtUtil;
import com.bladex.sp.domain.WxUser;
import com.bladex.sp.mapper.WxUserMapper;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Slf4j
@Component
@RequiredArgsConstructor
public class JwtInterceptor implements HandlerInterceptor {@Resourceprivate WxUserMapper userMapper;@Resourceprivate JwtUtil jwtUtil;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//获取请求头中的 AuthorizationString authHeader = request.getHeader("Authorization");if (StrUtil.isBlank(authHeader) || !authHeader.startsWith("Bearer ")) {log.warn("无效的 Token");responseError(response, "Token无效");return false;}//提取 tokenString token = authHeader.substring(7);try {//解析 token 得到 openidString openid = jwtUtil.getOpenIdFromToken(token);//根据 openid 查询用户WxUser wxUser = userMapper.selectWxUserByOpenId(openid);if (wxUser == null) {log.warn("用户不存在,openid: {}", openid);responseError(response, "用户不存在");return false;}//将用户信息存入请求属性中(供 Controller 使用)request.setAttribute("wxUser", wxUser);//✅ 至此,用户已鉴权,放行请求return true;} catch (Exception e) {log.error("Token 解析失败: {}", e.getMessage());responseError(response, "Token无效: " + e.getMessage());return false;}}private void responseError(HttpServletResponse response, String message) throws Exception {JSONObject data = new JSONObject() {{put("code", HttpServletResponse.SC_UNAUTHORIZED);put("message", message);}};response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);response.getWriter().write(JSON.toJSONString(data));response.setCharacterEncoding("utf-8");response.setContentType("application/json");}
}
拦截器注册到 Spring MVC 拦截链,配置类:
import com.bladex.framework.interceptor.JwtInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {private final JwtInterceptor jwtInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(jwtInterceptor).addPathPatterns("/api/**") // 只拦截小程序相关接口.addPathPatterns("/auth/**") // 只拦截小程序相关接口// 如果登录接口不需要拦截,可以排除.excludePathPatterns("/auth/wxLogin");}
}
实体类:
@Data
public class WxUserDTO implements Serializable {private String code;//昵称private String nickName;//头像private String avatarUrl;//性别:0.男 1.女private Integer gender;
}@EqualsAndHashCode(callSuper = true)
@Data
@Builder
public class WxUser extends BaseEntity {//主键idprivate Long id;//openId唯一private String openId;//昵称private String nickName;//头像private String avatar;//性别:0.男 1.女private Integer gender;
}@Data
@Builder
public class WxLoginVO implements Serializable {private String token;private WxUser user;
}
controller:
//微信授权登录@PostMapping("/wxAuthLogin")public AjaxResult wxAuthLogin(@RequestBody WxUserDTO dto) {return AjaxResult.success(authService.wxAuthLogin(dto));}
service:
@Resourceprivate JwtUtil jwtUtil;@Resourceprivate WechatConfig wechatConfig;@Resourceprivate RestTemplate restTemplate;@Resourceprivate WxUserMapper userMapper;//微信授权登录public WxLoginVO wxAuthLogin(WxUserDTO dto) {Assert.isFalse(StringUtils.isBlank(dto.getCode()), "微信授权code不能为空");String url = String.format("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",wechatConfig.getAppId(), wechatConfig.getAppSecret(), dto.getCode());ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);JSONObject body = JSON.parseObject(response.getBody());log.info("微信授权登录返回结果: {}", body);if (body.containsKey("errcode")) {Integer errcode = body.getInteger("errcode");String errmsg = body.getString("errmsg");log.error("微信接口调用失败, errcode: {}, errmsg: {}", errcode, errmsg);throw new RuntimeException("微信授权失败:" + errmsg);}Assert.isTrue(body.containsKey("openid"), "授权失败,错误信息: " + body.getString("errmsg"));String openId = body.getString("openid");WxUser wxUser = userMapper.selectWxUserByOpenId(openId);if (StringUtils.isNull(wxUser)) {WxUser buildUser = WxUser.builder().openId(openId).nickName(dto.getNickName()).avatar(dto.getAvatarUrl()).gender(dto.getGender()).build();buildUser.setCreateTime(DateUtils.getNowDate());int i = userMapper.insertWxUser(buildUser);if (i > 0) {wxUser = buildUser;} else {throw new RuntimeException("创建用户失败");}}String token = jwtUtil.generateToken(openId);return WxLoginVO.builder().token(token).user(wxUser).build();}
其余接口中如果想从请求头获取wxUser信息,只需要在接口中加入注解:
@RequestAttribute("wxUser") WxUser user
示例:
@DeleteMapping("/delReceiveAddress")public AjaxResult delReceiveAddress(Long id, @RequestAttribute("wxUser") WxUser user) {return toAjax(authService.delReceiveAddress(id, user));}
尾部:
@RequestAttribute(“wxUser”) :会自动从拦截器中获取用户信息。因为拦截器中设置了:request.setAttribute(“wxUser”, wxUser),Spring 会自动将该属性注入到方法参数中,无需手动从 request 取。使用该注解可以优雅的取出用户信息
补充新版本依赖和JwtUtil:
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.5</version><scope>runtime</scope>
</dependency>
JwtUtil工具类:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;/*** JWT 工具类:用于生成和解析 Token*/
public class JwtUtil {// 令牌秘钥@Value("${token.secret}")private String SECRET_KEY;private static final long EXPIRATION_TIME = 1000L * 60 * 60 * 24 * 7; // 7 天,单位毫秒private static final Key key = Keys.hmacShaKeyFor(SECRET_KEY.getBytes());/*** 生成 Token* @param openId 用户唯一标识(如微信 openid)* @return JWT Token 字符串*/public static String generateToken(String openId) {Map<String, Object> claims = new HashMap<>();claims.put("openId", openId); // 可以放一些自定义 claimsreturn Jwts.builder().setClaims(claims).setSubject(openId).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)).signWith(key, SignatureAlgorithm.HS256).compact();}/*** 从 Token 中获取 openid(可扩展获取其他信息)* @param token JWT Token* @return openid*/public static String getOpenIdFromToken(String token) {return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody().getSubject();}
}