一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|JavaScript|易語(yǔ)言|

服務(wù)器之家 - 編程語(yǔ)言 - Java教程 - Java中基于Shiro,JWT實(shí)現(xiàn)微信小程序登錄完整例子及實(shí)現(xiàn)過(guò)程

Java中基于Shiro,JWT實(shí)現(xiàn)微信小程序登錄完整例子及實(shí)現(xiàn)過(guò)程

2021-06-18 13:32EalenXie Java教程

這篇文章主要介紹了Java中基于Shiro,JWT實(shí)現(xiàn)微信小程序登錄完整例子 ,實(shí)現(xiàn)了小程序的自定義登陸,將自定義登陸態(tài)token返回給小程序作為登陸憑證。需要的朋友可以參考下

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

Java中基于Shiro,JWT實(shí)現(xiàn)微信小程序登錄完整例子及實(shí)現(xiàn)過(guò)程

本文是對(duì)接微信小程序自定義登錄的一個(gè)完整例子實(shí)現(xiàn) ,技術(shù)棧為 : springboot+shiro+jwt+jpa+redis。

如果對(duì)該例子比較感興趣或者覺(jué)得言語(yǔ)表達(dá)比較啰嗦,可查看完整的項(xiàng)目地址 : https://github.com/ealenxie/shiro-jwt-applet

主要實(shí)現(xiàn) : 實(shí)現(xiàn)了小程序的自定義登陸,將自定義登陸態(tài)token返回給小程序作為登陸憑證。用戶的信息保存在數(shù)據(jù)庫(kù)中,登陸態(tài)token緩存在redis中。

效果如下 :

1 . 首先從我們的小程序端調(diào)用wx.login() ,獲取臨時(shí)憑證code :

Java中基于Shiro,JWT實(shí)現(xiàn)微信小程序登錄完整例子及實(shí)現(xiàn)過(guò)程

2 . 模擬使用該code,進(jìn)行小程序的登陸獲取自定義登陸態(tài) token,用postman進(jìn)行測(cè)試 :

Java中基于Shiro,JWT實(shí)現(xiàn)微信小程序登錄完整例子及實(shí)現(xiàn)過(guò)程

3 . 調(diào)用我們需要認(rèn)證的接口,并攜帶該token進(jìn)行鑒權(quán),獲取到返回信息  :

Java中基于Shiro,JWT實(shí)現(xiàn)微信小程序登錄完整例子及實(shí)現(xiàn)過(guò)程

前方高能,本例代碼說(shuō)明較多, 以下是主要的搭建流程 :

1 . 首先新建maven項(xiàng)目 shiro-jwt-applet ,pom依賴 ,主要是shiro和jwt的依賴,和springboot的一些基礎(chǔ)依賴。

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<?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,還有你的數(shù)據(jù)庫(kù)和redis

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
## 請(qǐng)自行修改下面信息
spring:
 application:
 name: shiro-jwt-applet
 jpa:
 hibernate:
 ddl-auto: create # 請(qǐng)自行修改 請(qǐng)自行修改 請(qǐng)自行修改
# datasource本地配置
 datasource:
 url: jdbc:mysql://localhost:3306/yourdatabase
 username: yourname
 password: yourpass
 driver-class-name: com.mysql.jdbc.driver
# redis本地配置 請(qǐng)自行配置
 redis:
 database: 0
 host: localhost
 port: 6379
# 微信小程序配置 appid /appsecret
wx:
 applet:
 appid: yourappid
 appsecret: yourappsecret

3 . 定義我們存儲(chǔ)的微信小程序登陸的實(shí)體信息 wxaccount  : 

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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.
 * 實(shí)體 屬性描述 這里只是簡(jiǎn)單示例,你可以自定義相關(guān)用戶信息
 */
@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
 */
}

  和一個(gè)簡(jiǎn)單的dao 訪問(wèn)數(shù)據(jù)庫(kù) wxaccountrepository :

 

?
1
2
3
4
5
6
7
8
9
10
11
12
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> {
 /**
 * 根據(jù)openid查詢用戶信息
 */
 wxaccount findbywxopenid(string wxopenid);
}

4 . 定義我們應(yīng)用的服務(wù)說(shuō)明 wxappletservice :

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package name.ealen.application;
import name.ealen.interfaces.dto.token;
/**
 * created by ealenxie on 2018/11/26 10:40.
 * 微信小程序自定義登陸 服務(wù)說(shuō)明
 */
public interface wxappletservice {
 /**
 * 微信小程序用戶登陸,完整流程可參考下面官方地址,本例中是按此流程開(kāi)發(fā)
 * https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
 * 1 . 我們的微信小程序端傳入code。
 * 2 . 調(diào)用微信code2session接口獲取openid和session_key
 * 3 . 根據(jù)openid和session_key自定義登陸態(tài)(token)
 * 4 . 返回自定義登陸態(tài)(token)給小程序端。
 * 5 . 我們的小程序端調(diào)用其他需要認(rèn)證的api,請(qǐng)?jiān)趆eader的authorization里面攜帶 token信息
 *
 * @param code 小程序端 調(diào)用 wx.login 獲取到的code,用于調(diào)用 微信code2session接口
 * @return token 返回后端 自定義登陸態(tài) token 基于jwt實(shí)現(xiàn)
 */
 public token wxuserlogin(string code);
}

   返回給微信小程序token對(duì)象聲明 token :

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package name.ealen.interfaces.dto;
/**
 * created by ealenxie on 2018/11/26 18:49.
 * dto 返回值token對(duì)象
 */
public class token {
 private string token;
 public token(string token) {
 this.token = token;
 }
 /**
 * 省略getter/setter
 */
}

5. 配置需要的基本組件,resttemplate,redis:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);      //讀取超時(shí)時(shí)間為單位為60秒
 factory.setconnecttimeout(1000 * 10);     //連接超時(shí)時(shí)間設(shè)置為10秒
 return factory;
 }
}

  redis的配置。本例是springboot2.0的寫法(和1.8的版本寫法略有不同):

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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的核心過(guò)濾器配置。繼承了shiro的basichttpauthenticationfilter,并重寫了其鑒權(quán)的過(guò)濾方法 :

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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核心過(guò)濾器配置
 * 所有的請(qǐng)求都會(huì)先經(jīng)過(guò)filter,所以我們繼承官方的basichttpauthenticationfilter,并且重寫鑒權(quán)的方法。
 * 執(zhí)行流程 prehandle->isaccessallowed->isloginattempt->executelogin
 */
public class jwtfilter extends basichttpauthenticationfilter {
 
 /**
 * 判斷用戶是否想要進(jìn)行 需要驗(yàn)證的操作
 * 檢測(cè)header里面是否包含authorization字段即可
 */
 @override
 protected boolean isloginattempt(servletrequest request, servletresponse response) {
 string auth = getauthzheader(request);
 return auth != null && !auth.equals("");
 
 }
 /**
 * 此方法調(diào)用登陸,驗(yàn)證邏輯
 */
 @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"));
 // 跨域時(shí)會(huì)首先發(fā)送一個(gè)option請(qǐng)求,這里我們給option請(qǐng)求直接返回正常狀態(tài)
 if (httpservletrequest.getmethod().equals(requestmethod.options.name())) {
  httpservletresponse.setstatus(httpstatus.ok.value());
  return false;
 }
 return super.prehandle(request, response);
 }
}

  jwt的核心配置(包含token的加密創(chuàng)建,jwt續(xù)期,解密驗(yàn)證) :

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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 過(guò)期時(shí)間值 這里寫死為和小程序時(shí)間一致 7200 秒,也就是兩個(gè)小時(shí)
 */
 private static long expire_time = 7200;
 @autowired
 private stringredistemplate redistemplate;
 /**
 * 根據(jù)微信用戶登陸信息創(chuàng)建 token
 * 注 : 這里的token會(huì)被緩存到redis中,用作為二次驗(yàn)證
 * redis里面緩存的時(shí)間應(yīng)該和jwt token的過(guò)期時(shí)間設(shè)置相同
 *
 * @param wxaccount 微信用戶信息
 * @return 返回 jwt token
 */
 public string createtokenbywxaccount(wxaccount wxaccount) {
 string jwtid = uuid.randomuuid().tostring();   //jwt 隨機(jī)id,做為驗(yàn)證的key
 //1 . 加密算法進(jìn)行簽名得到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 配置過(guò)期時(shí)間的正確姿勢(shì)
  .sign(algorithm);
 //2 . redis緩存jwt, 注 : 請(qǐng)和jwt過(guò)期時(shí)間一致
 redistemplate.opsforvalue().set("jwt-session-" + jwtid, token, expire_time, timeunit.seconds);
 return token;
 }
 /**
 * 校驗(yàn)token是否正確
 * 1 . 根據(jù)token解密,解密出jwt-id , 先從redis中查找出redistoken,匹配是否相同
 * 2 . 然后再對(duì)redistoken進(jìn)行解密,解密成功則 繼續(xù)流程 和 進(jìn)行token續(xù)期
 *
 * @param token 密鑰
 * @return 返回是否校驗(yàn)通過(guò)
 */
 public boolean verifytoken(string token) {
 try {
  //1 . 根據(jù)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 正確的配置續(xù)期姿勢(shì)
   .build();
  //3 . 驗(yàn)證token
  verifier.verify(redistoken);
  //4 . redis緩存jwt續(xù)期
  redistemplate.opsforvalue().set("jwt-session-" + getjwtidbytoken(token), redistoken, expire_time, timeunit.seconds);
  return true;
 } catch (exception e) { //捕捉到任何異常都視為校驗(yàn)失敗
  return false;
 }
 }
 /**
 * 根據(jù)token獲取wxopenid(注意坑點(diǎn) : 就算token不正確,也有可能解密出wxopenid,同下)
 */
 public string getwxopenidbytoken(string token) throws jwtdecodeexception {
 return jwt.decode(token).getclaim("wxopenid").asstring();
 }
 /**
 * 根據(jù)token獲取sessionkey
 */
 public string getsessionkeybytoken(string token) throws jwtdecodeexception {
 return jwt.decode(token).getclaim("sessionkey").asstring();
 }
 /**
 * 根據(jù)token 獲取jwt-id
 */
 private string getjwtidbytoken(string token) throws jwtdecodeexception {
 return jwt.decode(token).getclaim("jwt-id").asstring();
 }
}

7 . 自定義shiro的realm配置,realm是自定義登陸及授權(quán)的邏輯配置 :

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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 的一個(gè)配置管理類 allrealm()方法得到所有的realm
 */
@component
public class shirorealmconfig {
 @resource
 private jwtconfig jwtconfig;
 /**
 * 配置所有自定義的realm,方便起見(jiàn),應(yīng)對(duì)可能有多個(gè)realm的情況
 */
 public list<realm> allrealm() {
 list<realm> realmlist = new linkedlist<>();
 authorizingrealm jwtrealm = jwtrealm();
 realmlist.add(jwtrealm);
 return collections.unmodifiablelist(realmlist);
 }
 /**
 * 自定義 jwt的 realm
 * 重寫 realm 的 supports() 方法是通過(guò) jwt 進(jìn)行登錄判斷的關(guān)鍵
 */
 private authorizingrealm jwtrealm() {
 authorizingrealm jwtrealm = new authorizingrealm() {
  /**
  * 注意坑點(diǎn) : 必須重寫此方法,不然shiro會(huì)報(bào)錯(cuò)
  * 因?yàn)閯?chuàng)建了 jwttoken 用于替換shiro原生 token,所以必須在此方法中顯式的進(jìn)行替換,否則在進(jìn)行判斷時(shí)會(huì)一直失敗
  */
  @override
  public boolean supports(authenticationtoken token) {
  return token instanceof jwttoken;
  }
  @override
  protected authorizationinfo dogetauthorizationinfo(principalcollection principals) {
  return new simpleauthorizationinfo();
  }
  /**
  * 校驗(yàn) 驗(yàn)證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;
 }
 /**
 * 注意坑點(diǎn) : 密碼校驗(yàn) , 這里因?yàn)槭莏wt形式,就無(wú)需密碼校驗(yàn)和加密,直接讓其返回為true(如果不設(shè)置的話,該值默認(rèn)為false,即始終驗(yàn)證不通過(guò))
 */
 private credentialsmatcher credentialsmatcher() {
 return (token, info) -> true;
 }
}

  shiro的核心配置,包含配置realm :

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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,安全管理器,所有與安全相關(guān)的操作都會(huì)與之進(jìn)行交互;
 * 它管理著所有subject,所有subject都綁定到securitymanager,與subject的所有交互都會(huì)委托給securitymanager
 * defaultwebsecuritymanager :
 * 會(huì)創(chuàng)建默認(rèn)的defaultsubjectdao(它又會(huì)默認(rèn)創(chuàng)建defaultsessionstorageevaluator)
 * 會(huì)默認(rèn)創(chuàng)建defaultwebsubjectfactory
 * 會(huì)默認(rèn)創(chuàng)建modularrealmauthenticator
 */
 @bean
 public defaultwebsecuritymanager securitymanager(shirorealmconfig shirorealmconfig) {
 defaultwebsecuritymanager securitymanager = new defaultwebsecuritymanager();
 securitymanager.setrealms(shirorealmconfig.allrealm()); //設(shè)置realm
 defaultsubjectdao subjectdao = (defaultsubjectdao) securitymanager.getsubjectdao();
 // 關(guān)閉自帶session
 defaultsessionstorageevaluator evaluator = (defaultsessionstorageevaluator) subjectdao.getsessionstorageevaluator();
 evaluator.setsessionstorageenabled(boolean.false);
 subjectdao.setsessionstorageevaluator(evaluator);
 return securitymanager;
 }
 /**
 * 配置shiro的訪問(wèn)策略
 */
 @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<>();
 //登陸相關(guān)api不需要被過(guò)濾器攔截
 filterrulemap.put("/api/wx/user/login/**", "anon");
 filterrulemap.put("/api/response/**", "anon");
 // 所有請(qǐng)求通過(guò)jwt filter
 filterrulemap.put("/**", "jwt");
 factorybean.setfilterchaindefinitionmap(filterrulemap);
 return factorybean;
 }
 /**
 * 添加注解支持
 */
 @bean
 @dependson("lifecyclebeanpostprocessor")
 public defaultadvisorautoproxycreator defaultadvisorautoproxycreator() {
 defaultadvisorautoproxycreator defaultadvisorautoproxycreator = new defaultadvisorautoproxycreator();
 defaultadvisorautoproxycreator.setproxytargetclass(true); // 強(qiáng)制使用cglib,防止重復(fù)代理和可能引起代理出錯(cuò)的問(wèn)題
 return defaultadvisorautoproxycreator;
 }
 /**
 * 添加注解依賴
 */
 @bean
 public lifecyclebeanpostprocessor lifecyclebeanpostprocessor() {
 return new lifecyclebeanpostprocessor();
 }
 
 /**
 * 開(kāi)啟注解驗(yàn)證
 */
 @bean
 public authorizationattributesourceadvisor authorizationattributesourceadvisor(defaultwebsecuritymanager securitymanager) {
 authorizationattributesourceadvisor authorizationattributesourceadvisor = new authorizationattributesourceadvisor();
 authorizationattributesourceadvisor.setsecuritymanager(securitymanager);
 return authorizationattributesourceadvisor;
 }
}

  用于shiro鑒權(quán)的jwttoken對(duì)象 :

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package name.ealen.domain.vo;
import org.apache.shiro.authc.authenticationtoken;
/**
 * created by ealenxie on 2018/11/22 18:21.
 * 鑒權(quán)用的token vo ,實(shí)現(xiàn) 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 . 實(shí)現(xiàn)實(shí)體的行為及業(yè)務(wù)邏輯,此例主要是調(diào)用微信接口code2session和創(chuàng)建返回token :   

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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.
 * 實(shí)體 行為描述
 */
@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 接口 獲取微信用戶信息
 * 官方說(shuō)明 : 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();
 }
 /**
 * 微信小程序用戶登陸,完整流程可參考下面官方地址,本例中是按此流程開(kāi)發(fā)
 * https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html
 *
 * @param code 小程序端 調(diào)用 wx.login 獲取到的code,用于調(diào)用 微信code2session接口
 * @return 返回后端 自定義登陸態(tài) token 基于jwt實(shí)現(xiàn)
 */
 @override
 public token wxuserlogin(string code) {
 //1 . code2session返回json數(shù)據(jù)
 string resultjson = code2session(code);
 //2 . 解析數(shù)據(jù)
 code2sessionresponse response = jsonutil.jsonstring2object(resultjson, code2sessionresponse.class);
 if (!response.geterrcode().equals("0"))
  throw new authenticationexception("code2session失敗 : " + response.geterrmsg());
 else {
  //3 . 先從本地?cái)?shù)據(jù)庫(kù)中查找用戶是否存在
  wxaccount wxaccount = wxaccountrepository.findbywxopenid(response.getopenid());
  if (wxaccount == null) {
  wxaccount = new wxaccount();
  wxaccount.setwxopenid(response.getopenid()); //不存在就新建用戶
  }
  //4 . 更新sessionkey和 登陸時(shí)間
  wxaccount.setsessionkey(response.getsession_key());
  wxaccount.setlasttime(new date());
  wxaccountrepository.save(wxaccount);
  //5 . jwt 返回自定義登陸態(tài) token
  string token = jwtconfig.createtokenbywxaccount(wxaccount);
  return new token(token);
 }
 }
}

  小程序code2session接口的返回vo對(duì)象code2sessionresponse :

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package name.ealen.domain.vo;
/**
 * 微信小程序 code2session 接口返回值 對(duì)象
 * 具體可以參考小程序官方api說(shuō)明 : 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,此例包含一個(gè)登錄獲取token的api和一個(gè)需要認(rèn)證的測(cè)試api :

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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.
 * 小程序后臺(tái) 某 api
 */
@restcontroller
public class wxappletcontroller {
 @resource
 private wxappletservice wxappletservice;
 /**
 * 微信小程序端用戶登陸api
 * 返回給小程序端 自定義登陸態(tài) 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", "缺少參數(shù)code或code不合法");
  return new responseentity<>(result, httpstatus.bad_request);
 } else {
  return new responseentity<>(wxappletservice.wxuserlogin(request.get("code")), httpstatus.ok);
 }
 }
 /**
 * 需要認(rèn)證的測(cè)試接口 需要 @requiresauthentication 注解,則調(diào)用此接口需要 header 中攜帶自定義登陸態(tài) 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 . 運(yùn)行主類,檢查與數(shù)據(jù)庫(kù)和redis的連接,進(jìn)行測(cè)試 : 

 

?
1
2
3
4
5
6
7
8
9
10
11
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);
 }

總結(jié)

以上所述是小編給大家介紹的java中基于shiro,jwt實(shí)現(xiàn)微信小程序登錄完整例子及實(shí)現(xiàn)過(guò)程,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)服務(wù)器之家網(wǎng)站的支持!

原文鏈接:https://www.cnblogs.com/ealenxie/p/10031569.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 韩国免费视频 | 99在线精品免费视频 | 好性20岁| 国内自拍网红在综合图区 | 国产精品午夜剧场 | 欧美在线播放成人免费 | 无码乱人伦一区二区亚洲一 | 欧美久久综合网 | 国产va免费精品高清在线 | 成年极品漫画在线观看 | 猫扑俩性 | 关晓彤一级做a爰片性色毛片 | 美女张开下身让男人桶 | 2021国产精品视频一区 | www.伊人| 亚洲免费视频一区 | 久久热这里面只有精品 | 男人搡女人视频免费看 | 北条麻妃黑人正在播放 | 91久久线看在观草草青青 | 99久久国产视频 | 亚欧视频在线观看 | 99热人人| 黄色a∨| xvideoscom极品肌肉警察 | 男人叼女人的痛爽视频免费 | 国产精品免费久久久久影院小说 | gaychinese男男2022| 俺去俺来也在线www色官网 | 日韩精品视频在线播放 | 欧美式禁忌 | 性free非洲老妇 | 久久国产精品福利影集 | 色综合天天综合网站中国 | 欧美日本一道高清二区三区 | 欧美日韩精品一区二区三区高清视频 | 国色天香论坛社区在线视频 | 手机av影院| 欧美精品一线二线大片 | 金莲一级淫片aaaaaa | 国产精品成人在线播放 |