x1ongzhu 7 yıl önce
ebeveyn
işleme
a0e3c0b22e
21 değiştirilmiş dosya ile 1064 ekleme ve 40 silme
  1. 61 3
      pom.xml
  2. 42 0
      src/main/java/com/izouma/walkchina/configuration/RedisConfig.java
  3. 33 0
      src/main/java/com/izouma/walkchina/configuration/WechatConfig.java
  4. 268 0
      src/main/java/com/izouma/walkchina/configuration/WxMaRedisConfig.java
  5. 41 0
      src/main/java/com/izouma/walkchina/domain/Result.java
  6. 108 0
      src/main/java/com/izouma/walkchina/domain/UserInfo.java
  7. 19 0
      src/main/java/com/izouma/walkchina/domain/wechat/WechatAccessToken.java
  8. 0 16
      src/main/java/com/izouma/walkchina/entity/User.java
  9. 31 0
      src/main/java/com/izouma/walkchina/exception/GlobalExceptionHandler.java
  10. 9 0
      src/main/java/com/izouma/walkchina/exception/ServiceException.java
  11. 7 0
      src/main/java/com/izouma/walkchina/repo/UserInfoRepository.java
  12. 21 0
      src/main/java/com/izouma/walkchina/security/JwtAuthenticationEntryPoint.java
  13. 65 0
      src/main/java/com/izouma/walkchina/security/JwtRequestFilter.java
  14. 74 0
      src/main/java/com/izouma/walkchina/security/JwtTokenUtil.java
  15. 66 0
      src/main/java/com/izouma/walkchina/security/WebSecurityConfig.java
  16. 43 0
      src/main/java/com/izouma/walkchina/service/UserInfoService.java
  17. 58 0
      src/main/java/com/izouma/walkchina/web/AuthenticationController.java
  18. 0 19
      src/main/java/com/izouma/walkchina/web/UserController.java
  19. 64 0
      src/main/java/com/izouma/walkchina/web/UserInfoController.java
  20. 21 2
      src/main/resources/application.yaml
  21. 33 0
      src/test/java/com/izouma/walkchina/RedisTest.java

+ 61 - 3
pom.xml

@@ -21,32 +21,90 @@
     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-security</artifactId>
+            <artifactId>spring-boot-starter-web</artifactId>
         </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-web</artifactId>
+            <artifactId>spring-boot-starter-security</artifactId>
         </dependency>
-        
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-data-jpa</artifactId>
         </dependency>
+
         <dependency>
             <groupId>mysql</groupId>
             <artifactId>mysql-connector-java</artifactId>
             <scope>runtime</scope>
         </dependency>
+
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
             <optional>true</optional>
         </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-pool2</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-devtools</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-configuration-processor</artifactId>
+            <optional>true</optional>
+        </dependency>
+
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt</artifactId>
+            <version>0.9.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.binarywang</groupId>
+            <artifactId>weixin-java-miniapp</artifactId>
+            <version>3.4.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.binarywang</groupId>
+            <artifactId>weixin-java-mp</artifactId>
+            <version>3.4.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.binarywang</groupId>
+            <artifactId>weixin-java-pay</artifactId>
+            <version>3.4.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.binarywang</groupId>
+            <artifactId>weixin-java-open</artifactId>
+            <version>3.4.0</version>
+        </dependency>
+
+        <!--test dependency-->
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
         </dependency>
+
         <dependency>
             <groupId>org.springframework.security</groupId>
             <artifactId>spring-security-test</artifactId>

+ 42 - 0
src/main/java/com/izouma/walkchina/configuration/RedisConfig.java

@@ -0,0 +1,42 @@
+package com.izouma.walkchina.configuration;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+@Configuration
+@AutoConfigureAfter(RedisAutoConfiguration.class)
+public class RedisConfig {
+
+    @Bean
+    RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+
+        RedisTemplate<String, Object> template = new RedisTemplate<>();
+        template.setConnectionFactory(redisConnectionFactory);
+
+        //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
+        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(Object.class);
+
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
+        serializer.setObjectMapper(mapper);
+
+        template.setValueSerializer(serializer);
+        //使用StringRedisSerializer来序列化和反序列化redis的key值
+        template.setKeySerializer(new StringRedisSerializer());
+        template.setHashKeySerializer(new StringRedisSerializer());
+        template.setHashValueSerializer(serializer);
+        template.afterPropertiesSet();
+        return template;
+    }
+
+}

+ 33 - 0
src/main/java/com/izouma/walkchina/configuration/WechatConfig.java

@@ -0,0 +1,33 @@
+package com.izouma.walkchina.configuration;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties(prefix = "wx.miniapp")
+@Data
+public class WechatConfig {
+    private String appId;
+    private String appSecret;
+    private String msgToken;
+    private String msgAesKey;
+    private String msgFormat;
+
+    @Bean
+    public WxMaService wxMaService() {
+        WxMaService service = new WxMaServiceImpl();
+        WxMaRedisConfig config = new WxMaRedisConfig();
+        config.setAppid(appId);
+        config.setSecret(appSecret);
+        config.setToken(msgAesKey);
+        config.setAesKey(msgAesKey);
+        config.setMsgDataFormat(msgFormat);
+        service.setWxMaConfig(config);
+        return service;
+    }
+
+}

+ 268 - 0
src/main/java/com/izouma/walkchina/configuration/WxMaRedisConfig.java

@@ -0,0 +1,268 @@
+package com.izouma.walkchina.configuration;
+
+import cn.binarywang.wx.miniapp.config.WxMaConfig;
+import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import me.chanjar.weixin.common.bean.WxAccessToken;
+import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+@Component
+public class WxMaRedisConfig implements WxMaConfig {
+    private final String REDIS_KEY = "miniAppAccessToken";
+
+    @Autowired
+    private RedisTemplate redisTemplate;
+
+    private volatile String msgDataFormat;
+    private volatile String appid;
+    private volatile String secret;
+    private volatile String token;
+    private volatile String aesKey;
+
+    private volatile String httpProxyHost;
+    private volatile int    httpProxyPort;
+    private volatile String httpProxyUsername;
+    private volatile String httpProxyPassword;
+
+    private volatile String jsapiTicket;
+    private volatile long   jsapiTicketExpiresTime;
+
+    //微信卡券的ticket单独缓存
+    private volatile String cardApiTicket;
+    private volatile long   cardApiTicketExpiresTime;
+
+
+    private Lock accessTokenLock   = new ReentrantLock();
+    private Lock jsapiTicketLock   = new ReentrantLock();
+    private Lock cardApiTicketLock = new ReentrantLock();
+
+    /**
+     * 临时文件目录
+     */
+    protected volatile File tmpDirFile;
+
+    private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
+
+    @Override
+    public String getAccessToken() {
+        try {
+            Map<String, Object> map = (Map<String, Object>) redisTemplate.opsForValue().get(REDIS_KEY);
+            return (String) map.get("accessToken");
+        } catch (Exception ignored) {
+        }
+        return null;
+    }
+
+
+    @Override
+    public Lock getAccessTokenLock() {
+        return this.accessTokenLock;
+    }
+
+    public void setAccessTokenLock(Lock accessTokenLock) {
+        this.accessTokenLock = accessTokenLock;
+    }
+
+    @Override
+    public boolean isAccessTokenExpired() {
+        try {
+            Map<String, Object> map = (Map<String, Object>) redisTemplate.opsForValue().get(REDIS_KEY);
+            long expiresAt = (long) map.get("expiresAt");
+            return System.currentTimeMillis() >= expiresAt;
+        } catch (Exception ignored) {
+        }
+        return true;
+    }
+
+    @Override
+    public synchronized void updateAccessToken(WxAccessToken accessToken) {
+        updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
+    }
+
+    @Override
+    public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) {
+        long expiresAt = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
+        Map<String, Object> map = new HashMap<>();
+        map.put("accessToken", accessToken);
+        map.put("expiresAt", expiresAt);
+        redisTemplate.opsForValue().set(REDIS_KEY, map);
+    }
+
+    @Override
+    public String getJsapiTicket() {
+        return this.jsapiTicket;
+    }
+
+    @Override
+    public Lock getJsapiTicketLock() {
+        return this.jsapiTicketLock;
+    }
+
+    @Override
+    public boolean isJsapiTicketExpired() {
+        return System.currentTimeMillis() > this.jsapiTicketExpiresTime;
+    }
+
+    @Override
+    public void expireJsapiTicket() {
+        this.jsapiTicketExpiresTime = 0;
+    }
+
+    @Override
+    public void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) {
+        this.jsapiTicket = jsapiTicket;
+        // 预留200秒的时间
+        this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
+    }
+
+
+    @Override
+    public String getCardApiTicket() {
+        return this.cardApiTicket;
+    }
+
+    @Override
+    public Lock getCardApiTicketLock() {
+        return this.cardApiTicketLock;
+    }
+
+    @Override
+    public boolean isCardApiTicketExpired() {
+        return System.currentTimeMillis() > this.cardApiTicketExpiresTime;
+    }
+
+    @Override
+    public void expireCardApiTicket() {
+        this.cardApiTicketExpiresTime = 0;
+    }
+
+    @Override
+    public void updateCardApiTicket(String cardApiTicket, int expiresInSeconds) {
+        this.cardApiTicket = cardApiTicket;
+        // 预留200秒的时间
+        this.cardApiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
+    }
+
+    @Override
+    public void expireAccessToken() {
+        redisTemplate.delete(REDIS_KEY);
+    }
+
+    @Override
+    public String getSecret() {
+        return this.secret;
+    }
+
+    public void setSecret(String secret) {
+        this.secret = secret;
+    }
+
+    @Override
+    public String getToken() {
+        return this.token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    @Override
+    public long getExpiresTime() {
+        try {
+            Map<String, Object> map = (Map<String, Object>) redisTemplate.opsForValue().get(REDIS_KEY);
+            return (long) map.get("expiresAt");
+        } catch (Exception ignored) {
+        }
+        return 0;
+    }
+
+    @Override
+    public String getAesKey() {
+        return this.aesKey;
+    }
+
+    public void setAesKey(String aesKey) {
+        this.aesKey = aesKey;
+    }
+
+    @Override
+    public String getMsgDataFormat() {
+        return this.msgDataFormat;
+    }
+
+    public void setMsgDataFormat(String msgDataFormat) {
+        this.msgDataFormat = msgDataFormat;
+    }
+
+    @Override
+    public String getHttpProxyHost() {
+        return this.httpProxyHost;
+    }
+
+    public void setHttpProxyHost(String httpProxyHost) {
+        this.httpProxyHost = httpProxyHost;
+    }
+
+    @Override
+    public int getHttpProxyPort() {
+        return this.httpProxyPort;
+    }
+
+    public void setHttpProxyPort(int httpProxyPort) {
+        this.httpProxyPort = httpProxyPort;
+    }
+
+    @Override
+    public String getHttpProxyUsername() {
+        return this.httpProxyUsername;
+    }
+
+    public void setHttpProxyUsername(String httpProxyUsername) {
+        this.httpProxyUsername = httpProxyUsername;
+    }
+
+    @Override
+    public String getHttpProxyPassword() {
+        return this.httpProxyPassword;
+    }
+
+    public void setHttpProxyPassword(String httpProxyPassword) {
+        this.httpProxyPassword = httpProxyPassword;
+    }
+
+    @Override
+    public String toString() {
+        return WxMaGsonBuilder.create().toJson(this);
+    }
+
+    @Override
+    public ApacheHttpClientBuilder getApacheHttpClientBuilder() {
+        return this.apacheHttpClientBuilder;
+    }
+
+    public void setApacheHttpClientBuilder(ApacheHttpClientBuilder apacheHttpClientBuilder) {
+        this.apacheHttpClientBuilder = apacheHttpClientBuilder;
+    }
+
+    @Override
+    public boolean autoRefreshToken() {
+        return true;
+    }
+
+    @Override
+    public String getAppid() {
+        return appid;
+    }
+
+    public void setAppid(String appid) {
+        this.appid = appid;
+    }
+}

+ 41 - 0
src/main/java/com/izouma/walkchina/domain/Result.java

@@ -0,0 +1,41 @@
+package com.izouma.walkchina.domain;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class Result {
+    public static final int OK    = 0;
+    public static final int ERROR = -1;
+
+    private Integer code;
+    private Boolean success;
+    private Object  data;
+    private Object  error;
+
+    public static Result ok(Object data) {
+        Result result = new Result();
+        result.setSuccess(true);
+        result.setData(data);
+        return result;
+    }
+
+    public static Result error(int code, Object error) {
+        Result result = new Result();
+        result.setSuccess(false);
+        result.setCode(code);
+        result.setError(error);
+        return result;
+    }
+
+    public static Result error(Object error) {
+        return error(-1, error);
+    }
+}

+ 108 - 0
src/main/java/com/izouma/walkchina/domain/UserInfo.java

@@ -0,0 +1,108 @@
+package com.izouma.walkchina.domain;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.Where;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedDate;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import javax.persistence.*;
+import javax.validation.constraints.Email;
+import javax.validation.constraints.NotNull;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+
+@Data
+@Entity
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@Where(clause = "active = 1")
+public class UserInfo implements UserDetails {
+
+    private static final long serialVersionUID = 6392705780780532944L;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @Column(unique = true)
+    private String username;
+
+    private String nickname;
+
+    @JsonIgnore
+    @NotNull
+    private String password;
+
+    private String openId;
+
+    private String unionId;
+
+    private String avatar;
+
+    private String phone;
+
+    @Email
+    private String email;
+
+    private Date birthday;
+
+    private String country;
+
+    private String province;
+
+    private String city;
+
+    @Column(columnDefinition = "integer default 0")
+    private Integer sex;
+
+    @CreatedDate
+    private Date createdAt;
+
+    @LastModifiedDate
+    private Date modifiedAt;
+
+    @Column(columnDefinition = "bit default 1", nullable = false)
+    @Builder.Default
+    private Boolean active = true;
+
+    @Override
+    @JsonIgnore
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        return Collections.singletonList(new SimpleGrantedAuthority("user"));
+    }
+
+    @Override
+    @JsonIgnore
+    public boolean isAccountNonExpired() {
+        return true;
+    }
+
+    @Override
+    @JsonIgnore
+    public boolean isAccountNonLocked() {
+        return true;
+    }
+
+    @Override
+    @JsonIgnore
+    public boolean isCredentialsNonExpired() {
+        return true;
+    }
+
+    @Override
+    @JsonIgnore
+    public boolean isEnabled() {
+        return active;
+    }
+}

+ 19 - 0
src/main/java/com/izouma/walkchina/domain/wechat/WechatAccessToken.java

@@ -0,0 +1,19 @@
+package com.izouma.walkchina.domain.wechat;
+
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class WechatAccessToken {
+    public static final String MINI_PROGRAM     = "mini_program_access_token";
+    public static final String OFFICIAL_ACCOUNT = "official_account_access_token";
+
+    private String type;
+
+    private String accessToken;
+
+    private Date createAt;
+
+    private Date expireAt;
+}

+ 0 - 16
src/main/java/com/izouma/walkchina/entity/User.java

@@ -1,16 +0,0 @@
-package com.izouma.walkchina.entity;
-
-import lombok.Data;
-import lombok.Generated;
-
-import javax.persistence.Entity;
-import javax.persistence.Id;
-
-@Data
-@Entity
-public class User {
-
-    @Id
-    @Generated
-    private Long id;
-}

+ 31 - 0
src/main/java/com/izouma/walkchina/exception/GlobalExceptionHandler.java

@@ -0,0 +1,31 @@
+package com.izouma.walkchina.exception;
+
+import com.izouma.walkchina.domain.Result;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.Map;
+
+@ControllerAdvice
+public class GlobalExceptionHandler {
+
+    @ExceptionHandler(value = ServiceException.class)
+    @ResponseBody
+    public Result serviceExceptionHandler(HttpServletRequest req, ServiceException e) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("url", req.getRequestURL().toString());
+
+        return Result.builder()
+                .success(false)
+                .error(e.getMessage())
+                .data(map)
+                .code(-1)
+                .build();
+    }
+
+
+
+}

+ 9 - 0
src/main/java/com/izouma/walkchina/exception/ServiceException.java

@@ -0,0 +1,9 @@
+package com.izouma.walkchina.exception;
+
+public class ServiceException extends Exception {
+    private static final long serialVersionUID = 3779880207424189309L;
+
+    public ServiceException(String message) {
+        super(message);
+    }
+}

+ 7 - 0
src/main/java/com/izouma/walkchina/repo/UserInfoRepository.java

@@ -0,0 +1,7 @@
+package com.izouma.walkchina.repo;
+
+import com.izouma.walkchina.domain.UserInfo;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface UserInfoRepository extends JpaRepository<UserInfo, Long> {
+}

+ 21 - 0
src/main/java/com/izouma/walkchina/security/JwtAuthenticationEntryPoint.java

@@ -0,0 +1,21 @@
+package com.izouma.walkchina.security;
+
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.Serializable;
+
+@Component
+public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
+    private static final long serialVersionUID = -7858869558953243875L;
+
+    @Override
+    public void commence(HttpServletRequest request, HttpServletResponse response,
+                         AuthenticationException authException) throws IOException {
+        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
+    }
+}

+ 65 - 0
src/main/java/com/izouma/walkchina/security/JwtRequestFilter.java

@@ -0,0 +1,65 @@
+package com.izouma.walkchina.security;
+
+import com.izouma.walkchina.service.UserInfoService;
+import io.jsonwebtoken.ExpiredJwtException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+@Component
+public class JwtRequestFilter extends OncePerRequestFilter {
+    @Autowired
+    private UserInfoService userInfoService;
+    @Autowired
+    private JwtTokenUtil    jwtTokenUtil;
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
+            throws ServletException, IOException {
+        final String requestTokenHeader = request.getHeader("Authorization");
+        String username = null;
+        String jwtToken = null;
+        // JWT Token is in the form "Bearer token". Remove Bearer word and get
+        // only the Token
+        if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
+            jwtToken = requestTokenHeader.substring(7);
+            try {
+                username = jwtTokenUtil.getUsernameFromToken(jwtToken);
+            } catch (IllegalArgumentException e) {
+                System.out.println("Unable to get JWT Token");
+            } catch (ExpiredJwtException e) {
+                System.out.println("JWT Token has expired");
+            }
+        } else {
+            logger.warn("JWT Token does not begin with Bearer String");
+        }
+        // Once we get the token validate it.
+        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+            UserDetails userDetails = userInfoService.loadUserByUsername(username);
+            // if token is valid configure Spring Security to manually set
+            // authentication
+            if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
+                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
+                        userDetails, null, userDetails.getAuthorities());
+                usernamePasswordAuthenticationToken
+                        .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+                // After setting the Authentication in the context, we specify
+                // that the current user is authenticated. So it passes the
+                // Spring Security Configurations successfully.
+                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
+            }
+        }
+        chain.doFilter(request, response);
+    }
+
+}

+ 74 - 0
src/main/java/com/izouma/walkchina/security/JwtTokenUtil.java

@@ -0,0 +1,74 @@
+package com.izouma.walkchina.security;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Component;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+@Component
+public class JwtTokenUtil implements Serializable {
+
+    private static final long serialVersionUID = -3722940977538012340L;
+
+    public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;
+
+    @Value("${jwt.secret}")
+    private String secret;
+
+    //retrieve username from jwt token
+    public String getUsernameFromToken(String token) {
+        return getClaimFromToken(token, Claims::getSubject);
+    }
+
+    //retrieve expiration date from jwt token
+    public Date getExpirationDateFromToken(String token) {
+        return getClaimFromToken(token, Claims::getExpiration);
+    }
+
+    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
+        final Claims claims = getAllClaimsFromToken(token);
+        return claimsResolver.apply(claims);
+    }
+
+    //for retrieveing any information from token we will need the secret key
+    private Claims getAllClaimsFromToken(String token) {
+        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
+    }
+
+    //check if the token has expired
+    private Boolean isTokenExpired(String token) {
+        final Date expiration = getExpirationDateFromToken(token);
+        return expiration.before(new Date());
+    }
+
+    //generate token for user
+    public String generateToken(UserDetails userDetails) {
+        Map<String, Object> claims = new HashMap<>();
+        return doGenerateToken(claims, userDetails.getUsername());
+    }
+
+    //while creating the token -
+    //1. Define  claims of the token, like Issuer, Expiration, Subject, and the ID
+    //2. Sign the JWT using the HS512 algorithm and secret key.
+    //3. According to JWS Compact Serialization(https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-3.1)
+    //   compaction of the JWT to a URL-safe string
+    private String doGenerateToken(Map<String, Object> claims, String subject) {
+        return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
+                .setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
+                .signWith(SignatureAlgorithm.HS512, secret).compact();
+    }
+
+    //validate token
+    public Boolean validateToken(String token, UserDetails userDetails) {
+        final String username = getUsernameFromToken(token);
+        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
+    }
+}

+ 66 - 0
src/main/java/com/izouma/walkchina/security/WebSecurityConfig.java

@@ -0,0 +1,66 @@
+package com.izouma.walkchina.security;
+
+import com.izouma.walkchina.service.UserInfoService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+@Configuration
+@EnableWebSecurity
+@EnableGlobalMethodSecurity(prePostEnabled = true)
+public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
+    @Autowired
+    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
+    @Autowired
+    private UserInfoService             userInfoService;
+    @Autowired
+    private JwtRequestFilter            jwtRequestFilter;
+
+    @Autowired
+    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
+        // configure AuthenticationManager so that it knows from where to load
+        // user for matching credentials
+        // Use BCryptPasswordEncoder
+        auth.userDetailsService(userInfoService).passwordEncoder(passwordEncoder());
+    }
+
+    @Bean
+    public PasswordEncoder passwordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+
+    @Bean
+    @Override
+    public AuthenticationManager authenticationManagerBean() throws Exception {
+        return super.authenticationManagerBean();
+    }
+
+    @Override
+    protected void configure(HttpSecurity httpSecurity) throws Exception {
+        // We don't need CSRF for this example
+        httpSecurity.csrf().disable()
+                // dont authenticate this particular request
+                .authorizeRequests()
+                .antMatchers("/**/**").permitAll()
+                // all other requests need to be authenticated
+                .anyRequest().authenticated().and()
+                // make sure we use stateless session; session won't be used to
+                // store user's state.
+                .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
+                .and().sessionManagement()
+                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
+        // Add a filter to validate the tokens with every request
+        httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
+    }
+}

+ 43 - 0
src/main/java/com/izouma/walkchina/service/UserInfoService.java

@@ -0,0 +1,43 @@
+package com.izouma.walkchina.service;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import com.izouma.walkchina.domain.UserInfo;
+import com.izouma.walkchina.exception.ServiceException;
+import com.izouma.walkchina.repo.UserInfoRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Example;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Service;
+
+
+@Service
+@Slf4j
+public class UserInfoService implements UserDetailsService {
+    @Autowired
+    private UserInfoRepository userInfoRepository;
+    @Autowired
+    private WxMaService        wxMaService;
+
+    @Override
+    public UserInfo loadUserByUsername(String username) {
+        log.info("loadUserByUsername {}", username);
+        UserInfo userInfo = new UserInfo();
+        userInfo.setUsername(username);
+        return userInfoRepository.findOne(Example.of(userInfo)).orElse(null);
+    }
+
+    public UserInfo registerByUserPwd(String username, String password) throws ServiceException {
+        UserInfo userInfo = userInfoRepository.findOne(Example.of(UserInfo.builder().username(username).build())).orElse(null);
+        if (userInfo != null) {
+            throw new ServiceException("该用户已存在");
+        }
+        userInfo = UserInfo.builder()
+                .username(username)
+                .password(new BCryptPasswordEncoder().encode(password))
+                .build();
+        userInfo = userInfoRepository.save(userInfo);
+        return userInfo;
+    }
+}

+ 58 - 0
src/main/java/com/izouma/walkchina/web/AuthenticationController.java

@@ -0,0 +1,58 @@
+package com.izouma.walkchina.web;
+
+import com.izouma.walkchina.domain.Result;
+import com.izouma.walkchina.domain.UserInfo;
+import com.izouma.walkchina.exception.ServiceException;
+import com.izouma.walkchina.security.JwtTokenUtil;
+import com.izouma.walkchina.service.UserInfoService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.DisabledException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@CrossOrigin
+@Slf4j
+@RequestMapping("/auth")
+public class AuthenticationController {
+    @Autowired
+    private AuthenticationManager authenticationManager;
+    @Autowired
+    private JwtTokenUtil          jwtTokenUtil;
+    @Autowired
+    private UserInfoService       userInfoService;
+
+    @PostMapping("/loginByUserPwd")
+    public ResponseEntity<?> loginByUserPwd(String username, String password) {
+        try {
+            authenticate(username, password);
+            final UserDetails userDetails = userInfoService.loadUserByUsername(username);
+            final String token = jwtTokenUtil.generateToken(userDetails);
+            return ResponseEntity.ok(token);
+        } catch (Exception e) {
+            log.error("loginByUserPwd", e);
+            return ResponseEntity.ok(Result.error(10001, "用户名或密码错误"));
+        }
+    }
+
+    private void authenticate(String username, String password) throws Exception {
+        try {
+            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
+        } catch (DisabledException e) {
+            throw new Exception("USER_DISABLED", e);
+        } catch (BadCredentialsException e) {
+            throw new Exception("INVALID_CREDENTIALS", e);
+        }
+    }
+
+    @PostMapping("/registerByUserPwd")
+    public ResponseEntity<?> registerByUserPwd(String username, String password) throws ServiceException {
+        UserInfo userInfo = userInfoService.registerByUserPwd(username, password);
+        return ResponseEntity.ok(Result.ok(userInfo));
+    }
+}

+ 0 - 19
src/main/java/com/izouma/walkchina/web/UserController.java

@@ -1,19 +0,0 @@
-package com.izouma.walkchina.web;
-
-import com.izouma.walkchina.entity.User;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
-
-@Controller
-@RequestMapping("/user")
-public class UserController {
-
-    @RequestMapping("/get/{id}")
-    @ResponseBody
-    public String getOne( @PathVariable String id){
-        User  user=new User();
-
-    }
-}

+ 64 - 0
src/main/java/com/izouma/walkchina/web/UserInfoController.java

@@ -0,0 +1,64 @@
+package com.izouma.walkchina.web;
+
+import com.izouma.walkchina.domain.Result;
+import com.izouma.walkchina.domain.UserInfo;
+import com.izouma.walkchina.repo.UserInfoRepository;
+import org.springframework.data.domain.Example;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/user")
+public class UserInfoController {
+    private final UserInfoRepository userInfoRepository;
+
+    public UserInfoController(UserInfoRepository userInfoRepository) {
+        this.userInfoRepository = userInfoRepository;
+    }
+
+    @GetMapping("/my")
+    public Result my() {
+        try {
+            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+            UserInfo userInfo = (UserInfo) authentication.getPrincipal();
+            userInfo = userInfoRepository.findById(userInfo.getId()).orElse(null);
+            if (userInfo != null) {
+                return Result.ok(userInfo);
+            }
+        } catch (Exception ignored) {
+        }
+        return Result.error("无记录");
+    }
+
+    @GetMapping("/get")
+    public Result get(UserInfo probe) {
+        UserInfo userInfo = userInfoRepository.findOne(Example.of(probe)).orElse(null);
+        if (userInfo != null) {
+            return Result.ok(userInfo);
+        } else {
+            return Result.error("无结果");
+        }
+    }
+
+    @GetMapping("/get/{id}")
+    public Result getOne(@PathVariable Long id) {
+        UserInfo userInfo = userInfoRepository.findById(id).orElse(null);
+        if (userInfo != null) {
+            return Result.ok(userInfo);
+        }
+        return Result.error("无记录");
+    }
+
+    @GetMapping("/all")
+    public Result all(UserInfo probe) {
+        return Result.ok(userInfoRepository.findAll(Example.of(probe)));
+    }
+
+    @GetMapping("/page")
+    public Result page(@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "20") int size, UserInfo probe) {
+        return Result.ok(userInfoRepository.findAll(Example.of(probe), PageRequest.of(page, size)));
+    }
+}

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

@@ -1,5 +1,5 @@
 server:
-  port: 8083
+  port: 8080
   servlet:
     context-path: /
 spring:
@@ -15,4 +15,23 @@ spring:
       ddl-auto: update
     properties:
       hibernate:
-        enable_lazy_load_no_trans: true
+        enable_lazy_load_no_trans: true
+  redis:
+    host: 127.0.0.1
+    port: 6379
+    lettuce:
+      pool:
+        max-active: 8
+        max-idle: 8
+        min-idle: 0
+jwt:
+  secret: javainuse
+wx:
+  miniapp:
+    app_id: wx47bde0e3d49633b4
+    app_secret: 152f70e353fb0b42a656678b34d41252
+    msg_token: msgToken
+    msg_aes_key: aesKey
+    msg_format: JSON
+
+

+ 33 - 0
src/test/java/com/izouma/walkchina/RedisTest.java

@@ -0,0 +1,33 @@
+package com.izouma.walkchina;
+
+import com.izouma.walkchina.domain.UserInfo;
+import me.chanjar.weixin.common.bean.WxAccessToken;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class RedisTest {
+    @Autowired
+    private RedisTemplate redisTemplate;
+
+    @Test
+    public void redisTest() {
+        Map<String, Object> map = new HashMap<>();
+        map.put("accessToken", "123");
+        map.put("expiresAt", System.currentTimeMillis() + 7200 * 1000);
+        redisTemplate.opsForValue().set("miniAppAccessToken", map);
+
+        Map<String, Object> newMap = (Map<String, Object>) redisTemplate.opsForValue().get("miniAppAccessToken1");
+        System.out.println(newMap);
+
+
+    }
+}