drew hace 6 años
padre
commit
94552cb333

+ 12 - 0
src/main/java/com/izouma/awesomeAdmin/security/JwtConfig.java

@@ -0,0 +1,12 @@
+package com.izouma.awesomeAdmin.security;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "jwt")
+public class JwtConfig {
+    private String secret;
+    private String header;
+    private Long   expiration;
+}

+ 42 - 26
src/main/java/com/izouma/awesomeAdmin/security/JwtTokenUtil.java

@@ -6,7 +6,12 @@ import io.jsonwebtoken.Clock;
 import io.jsonwebtoken.Jwts;
 import io.jsonwebtoken.SignatureAlgorithm;
 import io.jsonwebtoken.impl.DefaultClock;
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.stereotype.Component;
 
@@ -19,17 +24,20 @@ import java.util.function.Function;
 @Component
 public class JwtTokenUtil implements Serializable {
 
-    static final String CLAIM_KEY_USERNAME = "sub";
-    static final String CLAIM_KEY_CREATED = "iat";
     private static final long serialVersionUID = -3301605591108950415L;
-    @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "It's okay here")
-    private Clock clock = DefaultClock.INSTANCE;
 
-    @Value("${jwt.secret}")
-    private String secret;
+    static final String CLAIM_KEY_USERNAME = "sub";
+    static final String CLAIM_KEY_CREATED  = "iat";
+
+    @SuppressFBWarnings(value = "SE_BAD_FIELD", justification = "It's okay here")
+    private Clock        clock = DefaultClock.INSTANCE;
+    private JwtConfig    jwtConfig;
+    private CacheManager cacheManager;
 
-    @Value("${jwt.expiration}")
-    private Long expiration;
+    public JwtTokenUtil(JwtConfig jwtConfig, CacheManager cacheManager) {
+        this.jwtConfig = jwtConfig;
+        this.cacheManager = cacheManager;
+    }
 
     public String getUsernameFromToken(String token) {
         return getClaimFromToken(token, Claims::getSubject);
@@ -50,9 +58,9 @@ public class JwtTokenUtil implements Serializable {
 
     private Claims getAllClaimsFromToken(String token) {
         return Jwts.parser()
-            .setSigningKey(secret)
-            .parseClaimsJws(token)
-            .getBody();
+                .setSigningKey(jwtConfig.getSecret())
+                .parseClaimsJws(token)
+                .getBody();
     }
 
     private Boolean isTokenExpired(String token) {
@@ -71,7 +79,11 @@ public class JwtTokenUtil implements Serializable {
 
     public String generateToken(UserDetails userDetails) {
         Map<String, Object> claims = new HashMap<>();
-        return doGenerateToken(claims, userDetails.getUsername());
+        String token = doGenerateToken(claims, userDetails.getUsername());
+        Cache cache = cacheManager.getCache("token");
+        cache.evict(userDetails.getUsername());
+        cache.put(userDetails.getUsername(), token);
+        return token;
     }
 
     private String doGenerateToken(Map<String, Object> claims, String subject) {
@@ -79,18 +91,18 @@ public class JwtTokenUtil implements Serializable {
         final Date expirationDate = calculateExpirationDate(createdDate);
 
         return Jwts.builder()
-            .setClaims(claims)
-            .setSubject(subject)
-            .setIssuedAt(createdDate)
-            .setExpiration(expirationDate)
-            .signWith(SignatureAlgorithm.HS512, secret)
-            .compact();
+                .setClaims(claims)
+                .setSubject(subject)
+                .setIssuedAt(createdDate)
+                .setExpiration(expirationDate)
+                .signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret())
+                .compact();
     }
 
     public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
         final Date created = getIssuedAtDateFromToken(token);
         return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
-            && (!isTokenExpired(token) || ignoreTokenExpiration(token));
+                && (!isTokenExpired(token) || ignoreTokenExpiration(token));
     }
 
     public String refreshToken(String token) {
@@ -102,24 +114,28 @@ public class JwtTokenUtil implements Serializable {
         claims.setExpiration(expirationDate);
 
         return Jwts.builder()
-            .setClaims(claims)
-            .signWith(SignatureAlgorithm.HS512, secret)
-            .compact();
+                .setClaims(claims)
+                .signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret())
+                .compact();
     }
 
     public Boolean validateToken(String token, UserDetails userDetails) {
+        String tokenCache = cacheManager.getCache("token").get(userDetails.getUsername(), String.class);
+        if (StringUtils.isEmpty(tokenCache) || !token.equals(tokenCache)) {
+            return false;
+        }
         JwtUser user = (JwtUser) userDetails;
         final String username = getUsernameFromToken(token);
         final Date created = getIssuedAtDateFromToken(token);
         //final Date expiration = getExpirationDateFromToken(token);
         return (
-            username.equals(user.getUsername())
-                && !isTokenExpired(token)
-                && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())
+                username.equals(user.getUsername())
+                        && !isTokenExpired(token)
+                        && !isCreatedBeforeLastPasswordReset(created, user.getLastPasswordResetDate())
         );
     }
 
     private Date calculateExpirationDate(Date createdDate) {
-        return new Date(createdDate.getTime() + expiration * 1000);
+        return new Date(createdDate.getTime() + jwtConfig.getExpiration() * 1000);
     }
 }

+ 2 - 0
src/main/java/com/izouma/awesomeAdmin/security/WebSecurityConfig.java

@@ -2,6 +2,7 @@ package com.izouma.awesomeAdmin.security;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.http.HttpMethod;
 import org.springframework.security.authentication.AuthenticationManager;
@@ -18,6 +19,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
 
 @EnableWebSecurity
 @EnableGlobalMethodSecurity(prePostEnabled = true)
+@EnableConfigurationProperties({JwtConfig.class})
 public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
     @Autowired

+ 50 - 0
src/main/java/com/izouma/awesomeAdmin/web/WxController.java

@@ -4,8 +4,12 @@ import com.alibaba.fastjson.JSONObject;
 import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
 import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
+import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
+import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
+import com.github.binarywang.wxpay.constant.WxPayConstants;
 import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.binarywang.wxpay.service.WxPayService;
+import com.izouma.awesomeAdmin.exception.BusinessException;
 import io.swagger.annotations.ApiOperation;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -14,8 +18,12 @@ import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.mp.api.WxMpService;
 import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
 import me.chanjar.weixin.mp.bean.result.WxMpUser;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.core.env.Environment;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.ModelAndView;
 
+import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 
@@ -26,6 +34,48 @@ import java.io.IOException;
 public class WxController {
     private WxMpService  wxMpService;
     private WxPayService wxPayService;
+    private Environment  env;
+
+    @RequestMapping("/testMp")
+    public ModelAndView testPay(@RequestParam(required = false) String code, HttpServletRequest request,
+                                HttpServletResponse response) throws IOException {
+        try {
+            WxMpOAuth2AccessToken accessToken = wxMpService.oauth2getAccessToken(code);
+            WxMpUser wxMpUser = wxMpService.oauth2getUserInfo(accessToken, null);
+            ModelAndView mav = new ModelAndView("WxMpTest");
+            mav.addObject("user", wxMpUser);
+            return mav;
+        } catch (WxErrorException e) {
+            String url = wxMpService.oauth2buildAuthorizationUrl(env.getProperty("general.host")
+                    + request.getServletPath(), "snsapi_userinfo", null);
+            log.info("微信重定向: {}", url);
+            response.sendRedirect(url);
+        }
+        ModelAndView mav = new ModelAndView("Error");
+        mav.addObject("title", "redirect");
+        mav.addObject("body", "redirect");
+        return mav;
+    }
+
+    @GetMapping("/testPay")
+    public WxPayMpOrderResult testPayApi(@RequestParam String openId, @RequestParam Integer totalFee) {
+        WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
+        request.setBody("测试微信支付");
+        request.setDetail("测试微信支付");
+        request.setOutTradeNo(RandomStringUtils.randomAlphanumeric(32));
+        request.setTotalFee(totalFee);
+        request.setSpbillCreateIp("180.102.110.170");
+        request.setNotifyUrl(env.getProperty("wx.pay.notifyUrl"));
+        request.setTradeType(WxPayConstants.TradeType.JSAPI);
+        request.setOpenid(openId);
+        request.setSignType("MD5");
+        try {
+            return wxPayService.createOrder(request);
+        } catch (WxPayException e) {
+            log.error("unifiedOrder error", e);
+            throw new BusinessException(e.getMessage());
+        }
+    }
 
     @GetMapping("/greet")
     public String greetUser(@RequestParam String code) {

+ 2 - 4
src/main/resources/application.yaml

@@ -44,10 +44,6 @@ jwt:
     secret: XvAD0kboD76Dpebm
     header: Authorization
     expiration: 2592000 #30days
-    route:
-        authentication:
-            path: /auth
-            refresh: /refresh
 wx:
     mp:
         app_id: wx2375cba2eec2c479
@@ -76,6 +72,8 @@ aliyun:
     oss-end-point: oss-cn-hangzhou.aliyuncs.com
     oss-bucket-name: ticket-exchange
     oss-domain: https://ticket-exchange.oss-cn-hangzhou.aliyuncs.com
+general:
+    host: http://art.izouma.com
 ---
 
 spring:

+ 12 - 0
src/main/resources/templates/Error.ftl

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="zh-cmn-Hans">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scaleable=no">
+    <meta http-equiv="X-UA-Compatible" content="ie=edge">
+    <title>${title}</title>
+</head>
+<body>
+${body}
+</body>
+</html>

+ 288 - 0
src/main/resources/templates/WxMpTest.ftl

@@ -0,0 +1,288 @@
+<!DOCTYPE html>
+<html lang="zh-cmn-Hans">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scaleable=no">
+    <meta http-equiv="X-UA-Compatible" content="ie=edge">
+    <link rel="stylesheet" type="text/css" href="https://res.wx.qq.com/open/libs/weui/2.1.4/weui.min.css"/>
+    <script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
+    <script src="https://cdn.bootcss.com/vConsole/3.3.4/vconsole.min.js"></script>
+    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
+    <title>微信测试页面</title>
+    <style>
+        body {
+            margin: 0;
+            background-color: #ededed;
+        }
+
+        .page__hd {
+            padding: 40px;
+        }
+
+        .page__title {
+            text-align: left;
+            font-size: 20px;
+            font-weight: 400;
+        }
+
+        .page__desc {
+            margin-top: 4px;
+            color: rgba(0, 0, 0, .5);
+            text-align: left;
+            font-size: 14px;
+        }
+    </style>
+</head>
+
+<body>
+<div class="page__hd">
+    <div class="page__title">微信测试页面</div>
+    <div class="page__desc" id="url"></div>
+</div>
+<div class="weui-form-preview">
+    <div class="weui-form-preview__hd">
+        <div class="weui-form-preview__item">
+            <div class="weui-form-preview__label">昵称</div>
+            <div class="weui-form-preview__value">
+                <img id="avatar" src="${user.headImgUrl}"
+                     style="width:30px;vertical-align: middle;border-radius: 2px;">&nbsp;<span
+                        style="vertical-align: middle;">${user.nickname}</span></div>
+        </div>
+    </div>
+    <div class="weui-form-preview__bd">
+        <div class="weui-form-preview__item">
+            <label class="weui-form-preview__label">性别</label>
+            <span class="weui-form-preview__value">${user.sexDesc}</span>
+        </div>
+        <div class="weui-form-preview__item">
+            <label class="weui-form-preview__label">国家</label>
+            <span class="weui-form-preview__value">${user.country}</span>
+        </div>
+        <div class="weui-form-preview__item">
+            <label class="weui-form-preview__label">地区</label>
+            <span class="weui-form-preview__value">${user.province}</span>
+        </div>
+        <div class="weui-form-preview__item">
+            <label class="weui-form-preview__label">城市</label>
+            <span class="weui-form-preview__value">${user.city}</span>
+        </div>
+    </div>
+    <div class="weui-form-preview__ft">
+        <div class="weui-form-preview__bd">
+            <span class="weui-form-preview__label" id="openId">${user.openId}</span>
+        </div>
+    </div>
+</div>
+
+<div class="weui-form__control-area">
+    <div class="weui-cells__group weui-cells__group_form">
+        <div class="weui-cells__title">微信支付测试</div>
+        <div class="weui-cells weui-cells_form">
+            <div class="weui-cell weui-cell_active">
+                <div class="weui-cell__hd"><label class="weui-label">支付金额</label></div>
+                <div class="weui-cell__bd">
+                    <input id="totalFee" class="weui-input" placeholder="填写支付金额" value="0.01" type="number">
+                </div>
+            </div>
+            <div class="weui-cell">
+                <button onclick="pay()" class="weui-btn weui-btn_primary">立即支付</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div class="weui-form__control-area">
+    <div class="weui-cells__group weui-cells__group_form">
+        <div class="weui-cells__title">分享测试</div>
+        <div class="weui-cells weui-cells_form">
+            <div class="weui-cell weui-cell_active">
+                <div class="weui-cell__hd"><label class="weui-label">分享标题</label></div>
+                <div class="weui-cell__bd">
+                    <input id="shareTitle" class="weui-input" placeholder="填写分享标题">
+                </div>
+            </div>
+            <div class="weui-cell weui-cell_active">
+                <div class="weui-cell__hd"><label class="weui-label">分享内容</label></div>
+                <div class="weui-cell__bd">
+                    <input id="shareContent" class="weui-input" placeholder="填写分享内容">
+                </div>
+            </div>
+            <div class="weui-cell weui-cell_active">
+                <div class="weui-cell__hd"><label class="weui-label">分享链接</label></div>
+                <div class="weui-cell__bd">
+                    <input id="shareLink" class="weui-input" placeholder="填写分享链接">
+                </div>
+            </div>
+            <div class="weui-cell">
+                <button onclick="setupShare()" class="weui-btn weui-btn_primary">设置</button>
+            </div>
+        </div>
+    </div>
+</div>
+
+<div class="weui-form__control-area">
+    <div class="weui-cells__group weui-cells__group_form">
+        <div class="weui-cells__title">扫码测试</div>
+        <div class="weui-cells weui-cells_form">
+            <div class="weui-cell weui-cell_active weui-cell_switch">
+                <div class="weui-cell__bd">扫描结果由微信处理</div>
+                <div class="weui-cell__ft">
+                    <input id="switchCP" class="weui-switch" type="checkbox" checked="checked">
+                </div>
+            </div>
+            <div class="weui-cell weui-cell_active">
+                <div class="weui-cell__hd"><label class="weui-label">扫码结果</label></div>
+                <div class="weui-cell__bd" id="scanResult">
+                </div>
+            </div>
+            <div class="weui-cell">
+                <button onclick="scan()" class="weui-btn weui-btn_primary">立即扫码</button>
+            </div>
+        </div>
+    </div>
+</div>
+<div id="successToast" style="display: none;">
+    <div class="weui-mask_transparent"></div>
+    <div class="weui-toast">
+        <i class="weui-icon-success-no-circle weui-icon_toast"></i>
+        <p class="weui-toast__content" id="successMsg">已完成</p>
+    </div>
+</div>
+<div id="loadingToast" style="display: none;">
+    <div class="weui-mask_transparent"></div>
+    <div class="weui-toast">
+        <i class="weui-loading weui-icon_toast"></i>
+        <p class="weui-toast__content" id="loadingMsg">数据加载中</p>
+    </div>
+</div>
+<div id="dialog" style="display: none;">
+    <div class="weui-mask"></div>
+    <div class="weui-dialog">
+        <div class="weui-dialog__hd" id="dialogHeader"><strong class="weui-dialog__title"
+                                                               id="dialogHeaderContent">弹窗标题</strong></div>
+        <div class="weui-dialog__bd" id="dialogBody">弹窗内容,告知当前状态、信息和解决方法,描述文字尽量控制在三行内</div>
+        <div class="weui-dialog__ft" id="dialogFooter">
+            <a href="javascript:;" class="weui-dialog__btn weui-dialog__btn_default" id="dialogCancel">辅助操作</a>
+            <a href="javascript:;" class="weui-dialog__btn weui-dialog__btn_primary" id="dialogConfirm">主操作</a>
+        </div>
+    </div>
+</div>
+</body>
+<script>
+    var vConsole = new VConsole();
+    $('#url').html(location.origin + location.pathname);
+    $.get('/wx/jsapiSign', {url: encodeURI(window.location.href.split('#')[0]),}, function (res) {
+        res.debug = true;
+        res.jsApiList = [
+            'chooseWXPay',
+            'updateAppMessageShareData',
+            'updateTimelineShareData',
+            'hideAllNonBaseMenuItem',
+            'scanQRCode',
+        ];
+        wx.config(res);
+    });
+    wx.ready(function (res) {
+        console.log('jssdk ready', res);
+    });
+    wx.error(function (res) {
+        console.log('jssdk error', res);
+    });
+
+    function pay() {
+        $.get('/wx/testPay', {
+            openId: $('#openId').html(),
+            totalFee: $('#totalFee').val() * 100
+        }, function (res) {
+            var config = {
+                appId: res.appId,
+                timestamp: res.timeStamp,
+                nonceStr: res.nonceStr,
+                package: res.packageValue,
+                signType: res.signType,
+                paySign: res.paySign
+            };
+            config.success = function () {
+                showSuccess('支付成功');
+            };
+            wx.chooseWXPay(config);
+        });
+    }
+
+    function setupShare() {
+        wx.updateAppMessageShareData({
+            title: $('#shareTitle').val(),
+            desc: $('#shareContent').val(),
+            link: $('#shareLink').val(),
+            imgUrl: $('#avatar').attr('src'),
+            success: function () {
+                showSuccess('设置成功1');
+            }
+        });
+        wx.updateTimelineShareData({
+            title: $('#shareTitle').val(),
+            link: $('#shareLink').val(),
+            imgUrl: $('#avatar').attr('src'),
+            success: function () {
+                showSuccess('设置成功2');
+            }
+        });
+    }
+
+    function scan() {
+        wx.scanQRCode({
+            needResult: $('#switchCP')[0].checked ? 0 : 1,
+            scanType: ["qrCode", "barCode"],
+            success: function (res) {
+                $('#scanResult').html(res.resultStr);
+            }
+        });
+    }
+
+    function showDialog(options) {
+        $('#dialogHeader').show();
+        $('#dialogHeaderContent').html(options.title || '提示');
+        if (options.showCancel === undefined) {
+            options.showCancel = false;
+        }
+        if (options.showCancel) {
+            $('#dialogCancel').show();
+            $('#dialogCancel').html(options.cancelText || '取消');
+            $('#dialogCancel').on('click', function () {
+                options.cancel ? options.cancel() : hideDialog();
+            })
+        } else {
+            $('#dialogCancel').hide();
+        }
+        $('#dialogConfirm').html(options.cancelText || '确定');
+        $('#dialogConfirm').on('click', function () {
+            options.confirm ? options.confirm() : hideDialog();
+        });
+        $('#dialogBody').html(options.message);
+        $('#dialog').show(200);
+    }
+
+    function hideDialog() {
+        $('#dialog').hide(200);
+    }
+
+    function showLoading(msg) {
+        $('#loadingMsg').html(msg || '加载中');
+        $('#loadingToast').show(200);
+    }
+
+    function hideLoading() {
+        $('#loadingToast').hide(200);
+    }
+
+    function showSuccess(msg) {
+        $('#successMsg').html(msg || '成功');
+        $('#successToast').show(200);
+        setTimeout(() => {
+            $('#successToast').hide(200);
+        }, 1500);
+    }
+</script>
+
+</html>