Java中基于Shiro,JWT实现微信小程序登录完整例子及实现过程

网友投稿 331 2023-01-18


Java中基于Shiro,JWT实现微信小程序登录完整例子及实现过程

小程序官方流程图如下,官方地址 : https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html :

本文是对接微信小程序自定义登录的一个完整例子实现 ,技术栈为 : SpringBoot+Shiro+JWT+JPA+Redis。

如果对该例子比较感兴趣或者觉得言语表达比较啰嗦,可查看完整的项目地址 : https://github.com/EalenXie/shiro-jwt-applet

主要实现 : 实现了小程序的自定义登陆,将自定义登陆态token返回给小程序作为登陆凭证。用户的信息保存在数据库中,登陆态token缓存在redis中。

效果如下 :

1 . 首先从我们的小程序端调用wx.login() ,获取临时凭证code :

2 . 模拟使用该code,进行小程序的登陆获取自定义登陆态 token,用postman进行测试 :

3 . 调用我们需要认证的接口,并携带该token进行鉴权,获取到返回信息  :

前方高能,本例代码说明较多, 以下是主要的搭建流程 :

1 . 首先新建maven项目 shiro-jwt-applet ,pom依赖 ,主要是shiro和jwt的依赖,和SpringBoot的一些基础依赖。

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

name.ealen

shiro-jwt-applet

0.0.1-SNAPSHOT

jar

shiro-wx-jwt

Demo project for Spring Boot

org.springframework.boot

spring-boot-starter-parent

2.0.6.RELEASE

UTF-8

UTF-8

1.8

org.springframework.boot

spring-boot-starter-actuator

org.springframework.boot

spring-boot-starter-data-jpa

org.springframework.boot

spring-boot-starter-data-redis

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-test

test

mysql

mysql-connector-java

org.apache.shiro

shiro-spring

1.4.0

com.auth0

java-jwt

3.4.1

com.alibaba

fastjson

1.2.47

org.springframework.boot

spring-boot-maven-plugin

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

name.ealen

shiro-jwt-applet

0.0.1-SNAPSHOT

jar

shiro-wx-jwt

Demo project for Spring Boot

org.springframework.boot

spring-boot-starter-parent

2.0.6.RELEASE

UTF-8

UTF-8

1.8

org.springframework.boot

spring-boot-starter-actuator

org.springframework.boot

spring-boot-starter-data-jpa

org.springframework.boot

spring-boot-starter-data-redis

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-test

test

mysql

mysql-connector-java

org.apache.shiro

shiro-spring

1.4.0

com.auth0

java-jwt

3.4.1

com.alibaba

fastjson

1.2.47

org.springframework.boot

spring-boot-maven-plugin

2 . 配置你的application.yml ,主要是配置你的小程序appid和secret,还有你的数据库和redis

## 请自行修改下面信息

spring:

application:

name: shiro-jwt-applet

jpa:

hibernate:

ddl-auto: create # 请自行修改 请自行修改 请自行修改

# datasource本地配置

datasource:

url: jdbc:mysql://localhost:3306/yourdatabase

username: yourname

password: yourpass

driver-class-name: com.mysql.jdbc.Driver

# redis本地配置 请自行配置

redis:

database: 0

host: localhost

port: 6379

# 微信小程序配置 appid /appsecret

wx:

applet:

appid: yourappid

appsecret: yourappsecret

3 . 定义我们存储的微信小程序登陆的实体信息 WxAccount  :

package name.ealen.domain.entity;

import org.springframework.format.annotation.DateTimeFormat;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.Id;

import javax.persistence.Table;

import java.util.Date;

/**

* Created by EalenXie on 2018/11/26 10:26.

* 实体 属性描述 这里只是简单示例,你可以自定义相关用户信息

*/

@Entity

@Table

public class WxAccount {

@Id

@GeneratedValue

private Integer id;

private String wxOpenid;

private String sessionKey;

@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")

private Date lastTime;

/**

* 省略getter/setter

*/

}

和一个简单的dao 访问数据库 WxAccountRepository :

package name.ealen.domain.repository;

import name.ealen.domain.entity.WxAccount;

import org.springframework.data.jpa.repository.JpaRepository;

/**

* Created by EalenXie on 2018/11/26 10:32.

*/

public interface WxAccountRepository extends JpaRepository {

/**

* 根据OpenId查询用户信息

*/

WxAccount findByWxOpenid(String wxOpenId);

}

4 . 定义我们应用的服务说明 WxAppletService :

package name.ealen.application;

import name.ealen.interfaces.dto.Token;

/**

* Created by EalenXie on 2018/11/26 10:40.

* 微信小程序自定义登陆 服务说明

*/

public interface WxAppletService {

/**

* 微信小程序用户登陆,完整流程可参考下面官方地址,本例中是按此流程开发

* https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html

* 1 . 我们的微信小程序端传入code。

* 2 . 调用微信code2session接口获取openid和session_key

* 3 . 根据openid和session_key自定义登陆态(Token)

* 4 . 返回自定义登陆态(Token)给小程序端。

* 5 . 我们的小程序端调用其他需要认证的api,请在header的Authorization里面携带 token信息

*

* @param code 小程序端 调用 wx.login 获取到的code,用于调用 微信code2session接口

* @return Token 返回后端 自定义登陆态 token 基于JWT实现

*/

public Token wxUserLogin(String code);

}

返回给微信小程序token对象声明 Token :

package name.ealen.interfaces.dto;

/**

* Created by EalenXie on 2018/11/26 18:49.

* DTO 返回值token对象

*/

public class Token {

private String token;

public Token(String token) {

this.token = token;

}

/**

* 省略getter/setter

*/

}

5. 配置需要的基本组件,RestTemplate,Redis:

package name.ealen.infrastructure.config;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.http.client.ClientHttpRequestFactory;

import org.springframework.http.client.SimpleClientHttpRequestFactory;

import org.springframework.web.client.RestTemplate;

/**

* Created by EalenXie on 2018-03-23 07:37

* RestTemplate的配置类

*/

@Configuration

public class RestTemplateConfig {

@Bean

public RestTemplate restTemplate(ClientHttpRequestFactory factory) {

return new RestTemplate(factory);

}

@Bean

public ClientHttpRequestFactory simpleClientHttpRequestFactory() {

SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();

factory.setReadTimeout(1000 * 60); //读取超时时间为单位为60秒

factory.setConnectTimeout(1000 * 10); //连接超时时间设置为10秒

return factory;

}

}

Redis的配置。本例是Springboot2.0的写法(和1.8的版本写法略有不同):

package name.ealen.infrastructure.config;

import org.springframework.cache.CacheManager;

import org.springframework.cache.annotation.EnableCaching;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.data.redis.cache.RedisCacheManager;

import org.springfhttp://ramework.data.redis.connection.RedisConnectionFactory;

/**

* Created by EalenXie on 2018-03-23 07:37

* Redis的配置类

*/

@Configuration

@EnableCaching

public class RedisConfig {

@Bean

public CacheManager cacheManager(RedisConnectionFactory factory) {

return RedisCacheManager.create(factory);

}

}

6. JWT的核心过滤器配置。继承了Shiro的BasicHttpAuthenticationFilter,并重写了其鉴权的过滤方法 :

package name.ealen.infrastructure.config.jwt;

import name.ealen.domain.vo.JwtToken;

import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;

import org.springframework.http.HttpStatus;

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

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

/**

* Created by EalenXie on 2018/11/26 10:26.

* JWT核心过滤器配置

* 所有的请求都会先经过Filter,所以我们继承官方的BasicHttpAuthenticationFilter,并且重写鉴权的方法。

* 执行流程 preHandle->isAccessAllowed->isLoginAttempt->executeLogin

*/

public class JwtFilter extends BasicHttpAuthenticationFilter {

/**

* 判断用户是否想要进行 需要验证的操作

* 检测header里面是否包含Authorization字段即可

*/

@Override

protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {

String auth = getAuthzHeader(request);

return auth != null && !auth.equals("");

}

/**

* 此方法调用登陆,验证逻辑

*/

@Override

protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {

if (isLoginAttempt(request, response)) {

JwtToken token = new JwtToken(getAuthzHeader(request));

getSubject(request, response).login(token);

}

return true;

}

/**

* 提供跨域支持

*/

@Override

protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {

HttpServletRequest httpServletRequest = (HttpServletRequest) request;

HttpServletResponse httpServletResponse = (HttpServletResponse) response;

httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));

httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");

httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));

// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态

if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {

httpServletResponse.setStatus(HttpStatus.OK.value());

return false;

}

return super.preHandle(request, response);

}

}

JWT的核心配置(包含Token的加密创建,JWT续期,解密验证) :

package name.ealen.infrastructure.config.jwt;

import com.auth0.jwt.JWT;

import com.auth0.jwt.JWTVerifier;

import com.auth0.jwt.algorithms.Algorithm;

import com.auth0.jwt.exceptions.JWTDecodeException;

import name.ealen.domain.entity.WxAccount;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.redis.core.StringRedisTemplate;

import org.springframework.stereotype.Component;

import java.util.Date;

import java.util.UUID;

ihttp://mport java.util.concurrent.TimeUnit;

/**

* Created by EalenXie on 2018/11/22 17:16.

*/

@Component

public class JwtConfig {

/**

* JWT 自定义密钥 我这里写死的

*/

private static final String SECRET_KEY = "5371f568a45e5ab1f442c38e0932aef24447139b";

/**

* JWT 过期时间值 这里写死为和小程序时间一致 7200 秒,也就是两个小时

*/

private static long expire_time = 7200;

@Autowired

private StringRedisTemplate redisTemplate;

/**

* 根据微信用户登陆信息创建 token

* 注 : 这里的token会被缓存到redis中,用作为二次验证

* redis里面缓存的时间应该和jwt token的过期时间设置相同

*

* @param wxAccount 微信用户信息

* @return 返回 jwt token

*/

public String createTokenByWxAccount(WxAccount wxAccount) {

String jwtId = UUID.randomUUID().toString(); //JWT 随机ID,做为验证的key

//1 . 加密算法进行签名得到token

Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);

String token = JWT.create()

.withClaim("wxOpenId", wxAccount.getWxOpenid())

.withClaim("sessionKey", wxAccount.getSessionKey())

.withClaim("jwt-id", jwtId)

.withExpiresAt(new Date(System.currentTimeMillis() + expire_time*1000)) //JWT 配置过期时间的正确姿势

.sign(algorithm);

//2 . Redis缓存JWT, 注 : 请和JWT过期时间一致

redisTemplate.opsForValue().set("JWT-SESSION-" + jwtId, token, expire_time, TimeUnit.SECONDS);

return token;

}

/**

* 校验token是否正确

* 1 . 根据token解密,解密出jwt-id , 先从redis中查找出redisToken,匹配是否相同

* 2 . 然后再对redisToken进行解密,解密成功则 继续流程 和 进行token续期

*

* @param token 密钥

* @return 返回是否校验通过

*/

public boolean verifyToken(String token) {

try {

//1 . 根据token解密,解密出jwt-id , 先从redis中查找出redisToken,匹配是否相同

String redisToken = redisTemplate.opsForValue().get("JWT-SESSION-" + getJwtIdByToken(token));

if (!redisToken.equals(token)) return false;

//2 . 得到算法相同的JWTVerifier

Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);

JWTVerifier verifier = JWT.require(algorithm)

.withClaim("wxOpenId", getWxOpenIdByToken(redisToken))

.withClaim("sessionKey", getSessionKeyByToken(redisToken))

.withClaim("jwt-id", getJwtIdByToken(redisToken))

.acceptExpiresAt(System.currentTimeMillis() + expire_time*1000 ) //JWT 正确的配置续期姿势

.build();

//3 . 验证token

verifier.verify(redisToken);

//4 . Redis缓存JWT续期

redisTemplate.opsForValue().set("JWT-SESSION-" + getJwtIdByToken(token), redisToken, expire_time, TimeUnit.SECONDS);

return true;

} catch (Exception e) { //捕捉到任何异常都视为校验失败

return false;

}

}

/**

* 根据Token获取wxOpenId(注意坑点 : 就算token不正确,也有可能解密出wxOpenId,同下)

*/

public String getWxOpenIdByToken(String token) throws JWTDecodeException {

return JWT.decode(token).getClaim("wxOpenId").asString();

}

/**

* 根据Token获取sessionKey

*/

public String getSessionKeyByToken(String token) throws JWTDecodeException {

return JWT.decode(token).getClaim("sessionKey").asString();

}

/**

* 根据Token 获取jwt-id

*/

private String getJwtIdByToken(String token) throws JWTDecodeException {

return JWT.decode(token).getClaim("jwt-id").asString();

}

}

7 . 自定义Shiro的Realm配置,Realm是自定义登陆及授权的逻辑配置 :

package name.ealen.infrastructure.config.shiro;

import name.ealen.domain.vo.JwtToken;

import name.ealen.infrastructure.config.jwt.JwtConfig;

import org.apache.shiro.authc.AuthenticationException;

import org.apache.shiro.authc.AuthenticationInfo;

import org.apache.shiro.authc.AuthenticationToken;

import org.apache.shiro.authc.SimpleAuthenticationInfo;

import org.apache.shiro.authc.credential.CredentialsMatcher;

import org.apache.shiro.authz.AuthorizationInfo;

import org.apache.shiro.authz.SimpleAuthorizationInfo;

import org.apache.shiro.realm.AuthorizingRealm;

import org.apache.shiro.realm.Realm;

import org.apache.shiro.subject.PrincipalCollection;

import org.springframework.stereotype.Component;

import javax.annotation.Resource;

import java.util.Collections;

import java.util.LinkedList;

import java.util.List;

/**

* Created by EalenXie on 2018/11/26 12:12.

* Realm 的一个配置管理类 allRealm()方法得到所有的realm

*/

@Component

public class ShiroRealmConfig {

@Resource

private JwtConfig jwtConfig;

/**

* 配置所有自定义的realm,方便起见,应对可能有多个realm的情况

*/

public List allRealm() {

List realmList = new LinkedList<>();

AuthorizingRealm jwtRealm = jwtRealm();

realmList.add(jwtRealm);

return Collections.unmodifiableList(realmList);

}

/**

* 自定义 JWT的 Realm

* 重写 Realm 的 supports() 方法是通过 JWT 进行登录判断的关键

*/

private AuthorizingRealm jwtRealm() {

AuthorizingRealm jwtRealm = new AuthorizingRealm() {

/**

* 注意坑点 : 必须重写此方法,不然Shiro会报错

* 因为创建了 JWTToken 用于替换Shiro原生 token,所以必须在此方法中显式的进行替换,否则在进行判断时会一直失败

*/

@Override

public boolean supports(AuthenticationToken token) {

return token instanceof JwtToken;

}

@Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

return new SimpleAuthorizationInfo();

}

/**

* 校验 验证token逻辑

*/

@Override

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {

String jwtToken = (String) token.getCredentials();

String wxOpenId = jwtConfig.getWxOpenIdByToken(jwtToken);

String sessionKey = jwtConfig.getSessionKeyByToken(jwtToken);

if (wxOpenId == null || wxOpenId.equals(""))

throw new AuthenticationException("user account not exits , please check your token");

if (sessionKey == null || sessionKey.equals(""))

throw new AuthenticationException("sessionKey is invalid , please check your token");

if (!jwtConfig.verifyToken(jwtToken))

throw new AuthenticationException("token is invalid , please check your token");

return new SimpleAuthenticationInfo(token, token, getName());

}

};

jwtRealm.setCredentialsMatcher(credentialsMatcher());

return jwtRealm;

}

/**

* 注意坑点 : 密码校验 , 这里因为是JWT形式,就无需密码校验和加密,直接让其返回为true(如果不设置的话,该值默认为false,即始终验证不通过)

*/

private CredentialsMatcher credentialsMatcher() {

return (token, info) -> true;

}

}

Shiro的核心配置,包含配置Realm :

package name.ealen.infrastructure.config.shiro;

import name.ealen.infrastructure.config.jwt.JwtFilter;

import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;

import org.apache.shiro.mgt.DefaultSubjectDAO;

import org.apache.shiro.spring.LifecycleBeanPostProcessor;

import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;

import org.apache.shiro.web.mgt.DefaultWebSecurityManager;

import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;

import java.util.HashMap;

import java.util.Map;

/**

* Created by EalenXie on 2018/11/22 18:28.

*/

@Configuration

public class ShirConfig {

/**

* SecurityManager,安全管理器,所有与安全相关的操作都会与之进行交互;

* 它管理着所有Subject,所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager

* DefaultWebSecurityManager :

* 会创建默认的DefaultSubjectDAO(它又会默认创建DefaultSessionStorageEvaluator)

* 会默认创建DefaultWebSubjectFactory

* 会默认创建ModularRealmAuthenticator

*/

@Bean

public DefaultWebSecurityManager securityManager(ShiroRealmConfig shiroRealmConfig) {

DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

securityManager.setRealms(shiroRealmConfig.allRealm()); //设置realm

DefaultSubjectDAO subjectDAO = (DefaultSubjectDAO) securityManager.getSubjectDAO();

// 关闭自带session

DefaultSessionStorageEvaluator evaluator = (DefaultSessionStorageEvaluator) subjectDAO.getSessionStorageEvaluator();

evaluator.setSessionStorageEnabled(Boolean.FALSE);

subjectDAO.setSessionStorageEvaluator(evaluator);

return securityManager;

}

/**

* 配置Shiro的访问策略

*/

@Bean

public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager) {

ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();

Map filterMap = new HashMap<>();

filterMap.put("jwt", new JwtFilter());

factoryBean.setFilters(filterMap);

factoryBean.setSecurityManager(securityManager);

Map filterRuleMap = new HashMap<>();

//登陆相关api不需要被过滤器拦截

filterRuleMap.put("/api/wx/user/login/**", "anon");

filterRuleMap.put("/api/response/**", "anon");

// 所有请求通过JWT Filter

filterRuleMap.put("/**", "jwt");

factoryBean.setFilterChainDefinitionMap(filterRuleMap);

return factoryBean;

}

/**

* 添加注解支持

*/

@Bean

@DependsOn("lifecycleBeanPostProcessor")

public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {

DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();

defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); // 强制使用cglib,防止重复代理和可能引起代理出错的问题

return defaultAdvisorAutoProxyCreator;

}

/**

* 添加注解依赖

*/

@Bean

public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {

return new LifecycleBeanPostProcessor();

}

/**

* 开启注解验证

*/

@Bean

public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {

AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();

authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);

return authorizationAttributeSourceAdvisor;

}

}

用于Shiro鉴权的JwtToken对象 :

package name.ealen.domain.vo;

import org.apache.shiro.authc.AuthenticationToken;

/**

* Created by EalenXie on 2018/11/22 18:21.

* 鉴权用的token vo ,实现 AuthenticationToken

*/

public class JwtToken implements AuthenticationToken {

private String token;

public JwtToken(String token) {

this.token = token;

}

@Override

public Object getPrincipal() {

return token;

}

@Override

public Object getCredentials() {

return token;

}

public String getToken() {

return token;

}

public void setToken(String token) {

this.token = token;

}

}

8 . 实现实体的行为及业务逻辑,此例主要是调用微信接口code2session和创建返回token :

package name.ealen.domain.service;

import name.ealen.application.WxAppletService;

import name.ealen.domain.entity.WxAccount;

import name.ealen.domain.repository.WxAccountRepository;

import name.ealen.domain.vo.Code2SessionResponse;

import name.ealen.infrastructure.config.jwt.JwtConfig;

import name.ealen.infrastructure.util.HttpUtil;

import name.ealen.infrastructure.util.JSONUtil;

import name.ealen.interfaces.dto.Token;

import org.apache.shiro.authc.AuthenticationException;

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.stereotype.Service;

import org.springframework.util.LinkedMultiValueMap;

import org.springframework.util.MultiValueMap;

import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

import java.net.URI;

import java.util.Date;

/**

* Created by EalenXie on 2018/11/26 10:50.

* 实体 行为描述

*/

@Service

public class WxAccountService implements WxAppletService {

@Resource

private RestTemplate restTemplate;

@Value("${wx.applet.appid}")

private String appid;

@Value("${wx.applet.appsecret}")

private String appSecret;

@Resource

private WxAccountRepository wxAccountRepository;

@Resource

private JwtConfig jwtConfig;

/**

* 微信的 code2session 接口 获取微信用户信息

* 官方说明 : https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/code2Session.html

*/

private String code2Session(String jsCode) {

String code2SessionUrl = "https://api.weixin.qq.com/sns/jscode2session";

MultiValueMap params = new LinkedMultiValueMap<>();

params.add("appid", appid);

params.add("secret", appSecret);

params.add("js_code", jsCode);

params.add("grant_type", "authorization_code");

URI code2Session = HttpUtil.getURIwithParams(code2SessionUrl, params);

return restTemplate.exchange(code2Session, HttpMethod.GET, new HttpEntity(new HttpHeaders()), String.class).getBody();

}

/**

* 微信小程序用户登陆,完整流程可参考下面官方地址,本例中是按此流程开发

* https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html

*

* @param code 小程序端 调用 wx.login 获取到的code,用于调用 微信code2session接口

* @return 返回后端 自定义登陆态 token 基于JWT实现

*/

@Override

public Token wxUserLogin(String code) {

//1 . code2session返回JSON数据

String resultJson = code2Session(code);

//2 . 解析数据

Code2SessionResponse response = JSONUtil.jsonString2Object(resultJson, Code2SessionResponse.class);

if (!response.getErrcode().equals("0"))

throw new AuthenticationException("code2session失败 : " + response.getErrmsg());

else {

//3 . 先从本地数据库中查找用户是否存在

WxAccount wxAccount = wxAccountRepository.findByWxOpenid(response.getOpenid());

if (wxAccount == null) {

wxAccount = new WxAccount();

wxAccount.setWxOpenid(response.getOpenid()); //不存在就新建用户

}

//4 . 更新sessionKey和 登陆时间

wxAccount.setSessionKey(response.getSession_key());

wxAccount.setLastTime(new Date());

wxAccountRepository.save(wxAccount);

//5 . JWT 返回自定义登陆态 Token

String token = jwtConfig.createTokenByWxAccount(wxAccount);

return new Token(token);

}

}

}

小程序code2session接口的返回VO对象Code2SessionResponse :

package name.ealen.domain.vo;

/**

* 微信小程序 Code2Session 接口返回值 对象

* 具体可以参考小程序官方API说明 : https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/code2Session.html

*/

public class Code2SessionResponse {

private String openid;

private String session_key;

private String unionid;

private String errcode = "0";

private String errmsg;

private int expires_in;

/**

* 省略getter/setter

*/

}

9.  定义我们的接口信息WxAppletController,此例包含一个登录获取token的api和一个需要认证的测试api :

package name.ealen.interfaces.facade;

import name.ealen.application.WxAppletService;

import org.apache.shiro.authz.annotation.RequiresAuthentication;

import org.springframework.http.HttpStatus;

import org.springframework.http.ResponseEntity;

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

import javax.annotation.Resource;

import java.util.HashMap;

import java.util.Map;

/**

* Created by EalenXie on 2018/11/26 10:44.

* 小程序后台 某 API

*/

@RestController

public class WxAppletController {

@Resource

private WxAppletService wxAppletService;

/**

* 微信小程序端用户登陆api

* 返回给小程序端 自定义登陆态 token

*/

@PostMapping("/api/wx/user/login")

public ResponseEntity wxAppletLoginApi(@RequestBody Map request) {

if (!request.containsKey("code") || request.get("code") == null || request.get("code").equals("")) {

Map result = new HashMap<>();

result.put("msg", "缺少参数code或code不合法");

return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);

} else {

return new ResponseEntity<>(wxAppletService.wxUserLogin(request.get("code")), HttpStatus.OK);

}

}

/**

* 需要认证的测试接口 需要 @RequiresAuthentication 注解,则调用此接口需要 header 中携带自定义登陆态 authorization

*/

@RequiresAuthentication

@PostMapping("/sayHello")

public ResponseEntity sayHello() {

Map result = new HashMap<>();

result.put("words", "hello World");

return new ResponseEntity<>(result, HttpStatus.OK);

}

}

10 . 运行主类,检查与数据库和redis的连接,进行测试 :

package name.ealen;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

/**

* Created by EalenXie on 2018/11/26 10:25.

*/

@SpringBootApplication

public class ShiroJwtAppletApplication {

public static void main(String[] args) {

SpringApplication.run(ShiroJwtAppletApplication.class, args);

}

总结

以上所述是给大家介绍的Java中基于Shiro,JWT实现微信小程序登录完整例子及实现过程,希望对大家有所帮助,如果大家有任何疑问请给我留言,会及时回复大家的。在此也非常感谢大家对我们网站的支持!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Spring Boot2.0实现静态资源版本控制详解
下一篇:接口注册管理工具(接口管理软件)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~