Java中基于Shiro,JWT实现微信小程序登录完整例子及实现过程
小程序官方流程图如下,官方地址 : :
本文是对接微信小程序自定义登录的一个完整例子实现 ,技术栈为 : 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的一些基础依赖。
<?xml version="1.0" encoding="utf-8"?> <project xmlns="http://maven.apache.org/pom/4.0.0" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xsi:schemalocation="http://maven.apache.org/pom/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelversion>4.0.0</modelversion> <groupid>name.ealen</groupid> <artifactid>shiro-jwt-applet</artifactid> <version>0.0.1-snapshot</version> <packaging>jar</packaging> <name>shiro-wx-jwt</name> <description>demo project for spring boot</description> <parent> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-parent</artifactid> <version>2.0.6.release</version> <relativepath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceencoding>utf-8</project.build.sourceencoding> <project.reporting.outputencoding>utf-8</project.reporting.outputencoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-actuator</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-data-jpa</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-data-redis</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-web</artifactid> </dependency> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-test</artifactid> <scope>test</scope> </dependency> <dependency> <groupid>mysql</groupid> <artifactid>mysql-connector-java</artifactid> </dependency> <dependency> <groupid>org.apache.shiro</groupid> <artifactid>shiro-spring</artifactid> <version>1.4.0</version> </dependency> <dependency> <groupid>com.auth0</groupid> <artifactid>java-jwt</artifactid> <version>3.4.1</version> </dependency> <dependency> <groupid>com.alibaba</groupid> <artifactid>fastjson</artifactid> <version>1.2.47</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-maven-plugin</artifactid> </plugin> </plugins> </build> </project>
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<wxaccount, integer> { /** * 根据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.springframework.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; import 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<realm> allrealm() { list<realm> 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<string, filter> filtermap = new hashmap<>(); filtermap.put("jwt", new jwtfilter()); factorybean.setfilters(filtermap); factorybean.setsecuritymanager(securitymanager); map<string, string> 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<string, string> 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<string>(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<string, string> request) { if (!request.containskey("code") || request.get("code") == null || request.get("code").equals("")) { map<string, string> 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<string, string> 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实现微信小程序登录完整例子及实现过程,希望对大家有所帮助
下一篇: Java中实现双数组Trie树实例