# wxServer **Repository Path**: yc59717/wx-server ## Basic Information - **Project Name**: wxServer - **Description**: No description available - **Primary Language**: Unknown - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 2 - **Created**: 2024-05-18 - **Last Updated**: 2026-04-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Spring Boot + Spring Security + jwt实现微信小程序的后端登录 ## 1.微信小程序登录的流程 ![输入图片说明](https://foruda.gitee.com/images/1716026887959640998/4cb735c3_12402038.png "image-20240518170447898.png") 根据官网给的流程图,可以知道微信小程序的登录流程如下: 1. 小程序前端通过wx.login()获取到code值,将code值传递给后端服务器 2. 后端服务程序携带小程序的appid+appsecrete+code以及grant_type=authorization_code向微信接口服务发起请求,实例url为`https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code ` 3. 拿到返回的openid和session_key后,小程序的后端服务程序应该返回给一个自定义登录状态,在本次项目中,我们返回的是一个jwt 的 token 4. 随后小程序前端,就可以携带token来与后端服务程序进行数据请求了 微信小程序前端获取code值的示例为: ```js wx.login({ success (res) { if (res.code) { //发起网络请求 wx.request({ url: 'https://example.com/onLogin', data: { code: res.code } }) } else { console.log('登录失败!' + res.errMsg) } } }) ``` ## 2.创建后端项目 直接创建一个`springboot`项目,然后导入如下依赖 ```xml org.springframework.boot spring-boot-starter-data-jpa org.springframework.boot spring-boot-starter-web com.mysql mysql-connector-j runtime org.springframework.boot spring-boot-starter-test test com.alibaba fastjson 1.2.78 org.springframework.boot spring-boot-starter-security io.jsonwebtoken jjwt 0.9.1 ``` 以上是项目所必须的依赖,包括jpa、security、jwt等。 ### 相关配置 由于需要连接数据库和使用jpa,配置文件要进行相应配置,这里把用到的全部配置都放到这里 ``` server: port: 8081 spring: application: name: wx-server datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/wxserver?useUnicode=true&characterEncoding=UTF-8 username: root password: 123456 jpa: hibernate: ddl-auto: update wx: appId: wxxxxxxxxxxxxx appSecret: 1cxxxxxxxxxxx loginUrl: https://api.weixin.qq.com/sns/jscode2session jwt: secretKey: wxminiappsecretkey expirationTime: 3600000 ``` 其中wx:开头的配置,都是在请求微信接口要用到的相关数据,jwt开头的配置是生成jwt的token是使用的密钥和过期时间。 创建一个jwt的工具类,用于生成和解析jwt的token ```java 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; @Component public class JwtUtil { private static String SECRET_KEY; private static long EXPIRATION_TIME; @Value("${jwt.secretKey}") public void setSecretKey(String secretKey) { SECRET_KEY = secretKey; } @Value("${jwt.expirationTime}") public void setExpirationTime(long expirationTime) { EXPIRATION_TIME = expirationTime; } // 生成token public static String generateToken(String subject) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + EXPIRATION_TIME); // 1小时后过期,3600000 return Jwts.builder() .setSubject(subject) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } // 解析token public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); } } ``` 创建一个`JwtAuthenticationFilter`类,用于在每个请求中检查JWT ```java import com.ycc.config.JwtAuthenticationToken; import com.ycc.utils.JwtUtil; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Date; public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 从请求头中获取token String header = request.getHeader("Authorization"); if (header != null && header.startsWith("Bearer ")) { String token = header.substring(7); // 去掉"Bearer "前缀 try { Claims claims = JwtUtil.parseToken(token); if (claims.getExpiration().before(new Date())) { // 判断token是否过期 throw new RuntimeException("Token expired"); } // 生成Authentication对象,保存到SecurityContextHolder中 Authentication authentication = new JwtAuthenticationToken(claims); SecurityContextHolder.getContext().setAuthentication(authentication); } catch (ExpiredJwtException e) { throw new RuntimeException("Token expired"); } } filterChain.doFilter(request, response); } } ``` 创建一个`JwtAuthenticationToken`类,用于表示一个通过JWT认证的用户。 ```java import org.springframework.security.authentication.AbstractAuthenticationToken; public class JwtAuthenticationToken extends AbstractAuthenticationToken { private final Object principal; public JwtAuthenticationToken(Object principal) { super(null); this.principal = principal; setAuthenticated(true); } @Override public Object getCredentials() { return null; } @Override public Object getPrincipal() { return principal; } } ``` 创建微信认证的接口,并调用wxLoginService ```java import com.ycc.service.WxLoginService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import java.util.Map; @RestController public class WxLoginController { @Autowired private WxLoginService wxLoginService; @PostMapping("/wxlogin") public String wxLogin(@RequestBody Map req) { return wxLoginService.wxLogin(req); } } ``` 相应的WxLoginService的实现类来实现具体的远程调用微信接口获取信息,并验证用户是否是新用户,最后返回token ```java import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.ycc.dao.UserRepository; import com.ycc.model.entity.User; import com.ycc.service.WxLoginService; import com.ycc.utils.JwtUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import java.util.HashMap; import java.util.Map; @Service public class WxLoginServiceImpl implements WxLoginService { @Value("${wx.appId}") private String appId; @Value("${wx.appSecret}") private String appSecret; @Value("${wx.loginUrl}") private String wxLoginUrl; @Autowired private UserRepository userRepository; @Override public String wxLogin(Map req) { String code = req.get("code"); String url = wxLoginUrl + "?appid=" + appId + "&secret=" + appSecret + "&js_code=" + code + "&grant_type=authorization_code"; RestTemplate restTemplate = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); HttpEntity entity = new HttpEntity<>("parameters", headers); ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); // 解析微信服务器的响应 String responseBody = response.getBody(); //提取openid和session_key Map wxResponse = parseWxResponse(responseBody); String openid = wxResponse.get("openid"); String session_key = wxResponse.get("session_key"); // 检查数据库中是否已经有一个与这个openid关联的用户 User user = userRepository.findByOpenid(openid); if (user == null) { // 如果没有,创建一个新的用户 user = new User(); user.setOpenid(openid); user.setPassword("123456"); } // 更新用户的session_key user.setSessionKey(session_key); // 保存用户 User save = userRepository.save(user); // 1.会话密钥 session_key 是对用户数据进行 加密签名 的密钥。 // 为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥。 // 2.临时登录凭证 code 只能使用一次,5分钟未被使用自动过期。 // 根据用户的openId生成token返回给前端 return JwtUtil.generateToken(save.getOpenid()); } private Map parseWxResponse(String responseBody) { Map wxResponse = new HashMap<>(); JSONObject jsonObject = JSON.parseObject(responseBody); wxResponse.put("openid", jsonObject.getString("openid")); wxResponse.put("session_key", jsonObject.getString("session_key")); return wxResponse; } } ``` 最后就是创建一个`SecurityConfig`类,用于配置Spring Security。 ```java import com.ycc.filter.JwtAuthenticationFilter; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() // 设置会话管理策略为无状态(STATELESS) .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .httpBasic().disable() // 禁用HTTP Basic认证 // 添加自定义的JwtAuthenticationFilter .addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) .authorizeRequests() .antMatchers("/wxlogin").permitAll() // 开发微信登录接口,不需要认证 .anyRequest().authenticated(); } } ``` ### 3.测试 #### 测试微信登录 携带从微信小程序前端获取到的code值,来请求登录接口。成功的实现登录,获取到token ![输入图片说明](https://foruda.gitee.com/images/1716026842149414605/d1d6c29c_12402038.png "image-20240518175516664.png") 测试接待token请求测试接口,成功的请求到了测试接口的返回值。 ![输入图片说明](https://foruda.gitee.com/images/1716026860187727731/ef640051_12402038.png "image-20240518175646782.png")