xiongzhu 3 лет назад
Родитель
Сommit
442907a19c
22 измененных файлов с 556 добавлено и 72 удалено
  1. 23 0
      pom.xml
  2. 7 0
      src/main/java/com/example/jpatest/JsonView/UserView.java
  3. 12 0
      src/main/java/com/example/jpatest/annotations/Debounce.java
  4. 14 0
      src/main/java/com/example/jpatest/annotations/OperLog.java
  5. 15 0
      src/main/java/com/example/jpatest/annotations/RedisLock.java
  6. 12 0
      src/main/java/com/example/jpatest/annotations/Searchable.java
  7. 12 0
      src/main/java/com/example/jpatest/annotations/SearchableOne.java
  8. 35 0
      src/main/java/com/example/jpatest/config/GeneralProperties.java
  9. 104 0
      src/main/java/com/example/jpatest/config/WebMvcConfig.java
  10. 9 0
      src/main/java/com/example/jpatest/domain/BaseEntityNoID.java
  11. 40 11
      src/main/java/com/example/jpatest/domain/User.java
  12. 20 0
      src/main/java/com/example/jpatest/domain/UserToken.java
  13. 20 0
      src/main/java/com/example/jpatest/enums/AuthorityName.java
  14. 9 0
      src/main/java/com/example/jpatest/repo/UserTokenRepo.java
  15. 54 0
      src/main/java/com/example/jpatest/security/Authority.java
  16. 88 0
      src/main/java/com/example/jpatest/security/JwtAuthorizationTokenFilter.java
  17. 17 1
      src/main/java/com/example/jpatest/security/JwtTokenUtil.java
  18. 2 2
      src/main/java/com/example/jpatest/security/JwtUser.java
  19. 13 2
      src/main/java/com/example/jpatest/security/JwtUserFactory.java
  20. 7 55
      src/main/java/com/example/jpatest/security/WebSecurityConfig.java
  21. 40 0
      src/main/java/com/example/jpatest/utils/UserAuthoritySerializer.java
  22. 3 1
      src/main/resources/application.yaml

+ 23 - 0
pom.xml

@@ -93,6 +93,29 @@
 			<artifactId>springfox-swagger-ui</artifactId>
 			<version>2.9.1</version>
 		</dependency>
+
+		<dependency>
+			<groupId>org.hibernate</groupId>
+			<artifactId>hibernate-envers</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>com.alibaba</groupId>
+			<artifactId>easyexcel</artifactId>
+			<version>2.2.6</version>
+		</dependency>
+
+		<dependency>
+			<groupId>javax.validation</groupId>
+			<artifactId>validation-api</artifactId>
+		</dependency>
+
+		<dependency>
+			<groupId>com.google.code.findbugs</groupId>
+			<artifactId>annotations</artifactId>
+			<version>3.0.1</version>
+			<scope>provided</scope>
+		</dependency>
 	</dependencies>
 
 	<build>

+ 7 - 0
src/main/java/com/example/jpatest/JsonView/UserView.java

@@ -0,0 +1,7 @@
+package com.example.jpatest.JsonView;
+
+public interface UserView {
+    public static class Redis {
+    }
+
+}

+ 12 - 0
src/main/java/com/example/jpatest/annotations/Debounce.java

@@ -0,0 +1,12 @@
+package com.example.jpatest.annotations;
+
+import java.lang.annotation.*;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface Debounce {
+    String key();
+
+    long delay() default 200L;
+}

+ 14 - 0
src/main/java/com/example/jpatest/annotations/OperLog.java

@@ -0,0 +1,14 @@
+package com.example.jpatest.annotations;
+
+import java.lang.annotation.*;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface OperLog {
+    String value() default "";
+
+    String type() default "";
+
+    String desc() default "";
+}

+ 15 - 0
src/main/java/com/example/jpatest/annotations/RedisLock.java

@@ -0,0 +1,15 @@
+package com.example.jpatest.annotations;
+
+import java.lang.annotation.*;
+import java.util.concurrent.TimeUnit;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface RedisLock {
+    String value();
+
+    long expire() default 10;
+
+    TimeUnit unit() default TimeUnit.SECONDS;
+}

+ 12 - 0
src/main/java/com/example/jpatest/annotations/Searchable.java

@@ -0,0 +1,12 @@
+package com.example.jpatest.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Searchable {
+    boolean value() default true;
+}

+ 12 - 0
src/main/java/com/example/jpatest/annotations/SearchableOne.java

@@ -0,0 +1,12 @@
+package com.example.jpatest.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SearchableOne {
+    boolean value() default true;
+}

+ 35 - 0
src/main/java/com/example/jpatest/config/GeneralProperties.java

@@ -0,0 +1,35 @@
+package com.example.jpatest.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties(prefix = "general")
+@Data
+public class GeneralProperties {
+    private String  host;
+    private String  contractName;
+    private String  name;
+    private String  org;
+    private String  shortName;
+    private String  createOrderGroup;
+    private String  createOrderTopic;
+    private String  updateStockGroup;
+    private String  updateStockTopic;
+    private String  updateSaleGroup;
+    private String  updateSaleTopic;
+    private String  orderNotifyGroup;
+    private String  orderNotifyTopic;
+    private String  mintGroup;
+    private String  mintTopic;
+    private boolean notifyServer;
+    private String  updateActivityStockGroup;
+    private String  updateActivityStockTopic;
+    private int     dataCenterId;
+    private int     workerId;
+    private String  updateQuotaGroup;
+    private String  updateQuotaTopic;
+    private String  broadcastEventGroup;
+    private String  broadcastEventTopic;
+    private String  registerGroup;
+    private String  registerTopic;
+}

+ 104 - 0
src/main/java/com/example/jpatest/config/WebMvcConfig.java

@@ -0,0 +1,104 @@
+package com.example.jpatest.config;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+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.context.annotation.Configuration;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+
+import java.util.List;
+
+@Configuration
+@EnableConfigurationProperties(GeneralProperties.class)
+public class WebMvcConfig implements WebMvcConfigurer {
+    @Value("${storage.local_path}")
+    private String localPath;
+
+    @Autowired
+    private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;
+
+    @Override
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        // registry.addResourceHandler("/swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
+        // registry.addResourceHandler("webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
+        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
+        registry.addResourceHandler("/admin/**").addResourceLocations("classpath:/static/admin/");
+        registry.addResourceHandler("/files/**").addResourceLocations("file:" + localPath);
+        registry.addResourceHandler("/MP_verify*").addResourceLocations("classpath:/");
+    }
+
+    @Bean
+    public Docket createApi() {
+        return new Docket(DocumentationType.SWAGGER_2)
+                .apiInfo(new ApiInfoBuilder()
+                        .title("接口文档")
+                        .version("1.0.0")
+                        .termsOfServiceUrl("#")
+                        .description("接口文档")
+                        .build())
+                .select()
+                .apis(RequestHandlerSelectors.basePackage("com.example.jpatest.web"))
+                .paths(PathSelectors.any())
+                .build();
+    }
+
+    @Override
+    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
+        converters.stream().filter(converter -> converter instanceof MappingJackson2HttpMessageConverter)
+                .forEach(converter -> {
+                    ObjectMapper objectMapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper();
+                    SimpleModule simpleModule = new SimpleModule();
+                    simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
+                    simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
+                    objectMapper.registerModule(simpleModule);
+                    ((MappingJackson2HttpMessageConverter) converter).setObjectMapper(objectMapper);
+                });
+        System.out.println(converters);
+    }
+
+    // @Bean
+    // public MappingJackson2HttpMessageConverter getMappingJackson2HttpMessageConverter() {
+    //     MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
+    //     //设置日期格式
+    //     ObjectMapper objectMapper = new ObjectMapper();
+    //     objectMapper.setDateFormat(CustomDateFormat.instance);
+    //     objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+    //     mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
+    //     //设置中文编码格式
+    //     List<MediaType> list = new ArrayList<>();
+    //     list.add(MediaType.APPLICATION_JSON_UTF8);
+    //     mappingJackson2HttpMessageConverter.setSupportedMediaTypes(list);
+    //     return mappingJackson2HttpMessageConverter;
+    // }
+
+//    @Override
+//    public void addFormatters(FormatterRegistry registry) {
+//        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
+//        registrar.setUseIsoFormat(true);
+//        registrar.registerFormatters(registry);
+//    }
+
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+                .allowedHeaders("*")
+                .allowedOriginPatterns("*")
+                .allowCredentials(true)
+                .allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH")
+                .exposedHeaders("Content-Disposition");
+    }
+
+}

+ 9 - 0
src/main/java/com/example/jpatest/domain/BaseEntityNoID.java

@@ -1,9 +1,12 @@
 package com.example.jpatest.domain;
 
+import com.alibaba.excel.annotation.ExcelIgnore;
+import com.alibaba.excel.annotation.ExcelProperty;
 import com.fasterxml.jackson.annotation.JsonIgnore;
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonProperty;
+import org.hibernate.envers.Audited;
 import org.springframework.data.annotation.CreatedBy;
 import org.springframework.data.annotation.CreatedDate;
 import org.springframework.data.annotation.LastModifiedBy;
@@ -15,26 +18,32 @@ import javax.persistence.MappedSuperclass;
 import java.time.LocalDateTime;
 
 @MappedSuperclass
+@Audited
 @EntityListeners(AuditingEntityListener.class)
 @JsonInclude(JsonInclude.Include.NON_NULL)
 @JsonIgnoreProperties(value = {"hibernateLazyInitializer"}, ignoreUnknown = true)
 public abstract class BaseEntityNoID {
+    @ExcelIgnore
     @JsonIgnore
     @CreatedBy
     private String createdBy;
 
+    @ExcelProperty("创建时间")
     @JsonIgnore
     @CreatedDate
     private LocalDateTime createdAt;
 
+    @ExcelIgnore
     @JsonIgnore
     @LastModifiedBy
     private String modifiedBy;
 
+    @ExcelIgnore
     @JsonIgnore
     @LastModifiedDate
     private LocalDateTime modifiedAt;
 
+    @ExcelIgnore
     private boolean del;
 
     public String getCreatedBy() {

+ 40 - 11
src/main/java/com/example/jpatest/domain/User.java

@@ -1,17 +1,28 @@
 package com.example.jpatest.domain;
 
+import com.alibaba.excel.annotation.ExcelIgnore;
+import com.example.jpatest.JsonView.UserView;
+import com.example.jpatest.annotations.Searchable;
 import com.example.jpatest.enums.AuthStatus;
+import com.example.jpatest.security.Authority;
+import com.example.jpatest.utils.UserAuthoritySerializer;
 import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonView;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
+import org.hibernate.annotations.BatchSize;
 
 import javax.persistence.*;
+import javax.validation.constraints.Size;
 import java.io.Serializable;
 import java.math.BigDecimal;
-import java.time.LocalDateTime;
+import java.util.HashSet;
+import java.util.Set;
 
 @Data
 @Entity
@@ -45,26 +56,32 @@ public class User extends BaseEntityNoID implements Serializable {
 //    @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Long id;
 
-    private boolean del = false;
-
-    private String createdBy;
-
-    private LocalDateTime createdAt;
-
-    private String modifiedBy;
-
-    private LocalDateTime modifiedAt;
-
     //    @Pattern(regexp = Constants.Regex.USERNAME)
+    @Size(min = 1, max = 50)
     @Column(nullable = false, unique = true)
+    @Searchable
     private String username;
 
+    @Searchable
     private String nickname;
 
     private String avatar;
 
+    @JsonView(UserView.Redis.class)
     private String password;
 
+    private boolean del = false;
+
+    @ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.DETACH})
+    @JoinTable(
+            name = "user_authority",
+            joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))},
+            inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "name", foreignKey = @ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT))})
+    @BatchSize(size = 20)
+    @ExcelIgnore
+    @JsonSerialize(using = UserAuthoritySerializer.class, as = HashSet.class)
+    private Set<Authority> authorities = new HashSet<>();
+
     private String openId;
 
     private String sex;
@@ -77,12 +94,15 @@ public class User extends BaseEntityNoID implements Serializable {
 
     private String country;
 
+    @Searchable
     private String phone;
 
     private String email;
 
+    @ApiModelProperty("关注数量")
     private int follows;
 
+    @ApiModelProperty("粉丝数量")
     private int followers;
 
     private int sales;
@@ -94,6 +114,7 @@ public class User extends BaseEntityNoID implements Serializable {
 
     private Long authId;
 
+    @ApiModelProperty("实名审核状态")
     @Enumerated(EnumType.STRING)
     private AuthStatus authStatus;
 
@@ -110,6 +131,7 @@ public class User extends BaseEntityNoID implements Serializable {
 
     private boolean admin;
 
+    @ApiModelProperty("分成比例")
     @Column(precision = 10, scale = 2)
     private BigDecimal shareRatio;
 
@@ -123,21 +145,28 @@ public class User extends BaseEntityNoID implements Serializable {
 
     private String inviteCode;
 
+    @ApiModelProperty("分享藏品邀请者")
     private Long collectionInvitor;
 
+    @ApiModelProperty("藏品Id")
     private Long collectionId;
 
+    @ApiModelProperty("勋章等级")
     private int level;
 
+    @ApiModelProperty("优先购买")
     private int vipPurchase;
 
     private boolean minter;
 
     @Column(columnDefinition = "bit default false")
+    @ApiModelProperty("使用藏品图片")
     private boolean useCollectionPic;
 
     @Column(columnDefinition = "int(11) default 0")
+    @ApiModelProperty("白名单积分")
     private int vipPoint = 0;
 
+    @ApiModelProperty(value = "风险提示")
     private Boolean riskWarning;
 }

+ 20 - 0
src/main/java/com/example/jpatest/domain/UserToken.java

@@ -0,0 +1,20 @@
+package com.example.jpatest.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.redis.core.RedisHash;
+
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@RedisHash("UserToken")
+public class UserToken {
+
+    @Id
+    private String username;
+
+    private String token;
+}

+ 20 - 0
src/main/java/com/example/jpatest/enums/AuthorityName.java

@@ -0,0 +1,20 @@
+package com.example.jpatest.enums;
+
+public enum AuthorityName {
+    ROLE_USER("普通用户"),
+    ROLE_MINTER("铸造者"),
+    ROLE_DEV("开发者"),
+    ROLE_ADMIN("高级管理员"),
+    ROLE_OPERATOR("普通管理员"),
+    ROLE_NEWS("新闻管理员")
+    ;
+    private final String description;
+
+    AuthorityName(String description) {
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}

+ 9 - 0
src/main/java/com/example/jpatest/repo/UserTokenRepo.java

@@ -0,0 +1,9 @@
+package com.example.jpatest.repo;
+
+import com.example.jpatest.domain.UserToken;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface UserTokenRepo extends CrudRepository<UserToken, String> {
+}

+ 54 - 0
src/main/java/com/example/jpatest/security/Authority.java

@@ -0,0 +1,54 @@
+package com.example.jpatest.security;
+
+import com.example.jpatest.enums.AuthorityName;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import lombok.*;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.io.Serializable;
+import java.util.Objects;
+
+@Entity
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@EqualsAndHashCode
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonIgnoreProperties(value = {"hibernateLazyInitializer"}, ignoreUnknown = true)
+public class Authority implements Serializable {
+
+    public static Authority get(AuthorityName name) {
+        return new Authority(name.name(), name.getDescription());
+    }
+
+    @Id
+    @Size(max = 50)
+    @NotNull
+    @Column(length = 50)
+    private String name;
+
+    @Column(length = 50)
+    @Size(max = 50)
+    @NotNull
+    private String description;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        Authority authority = (Authority) o;
+
+        return Objects.equals(name, authority.name);
+    }
+}

+ 88 - 0
src/main/java/com/example/jpatest/security/JwtAuthorizationTokenFilter.java

@@ -0,0 +1,88 @@
+package com.example.jpatest.security;
+
+import io.jsonwebtoken.ExpiredJwtException;
+import io.jsonwebtoken.SignatureException;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+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
+@Slf4j
+@Order(1)
+public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
+
+    private final UserDetailsService userDetailsService;
+    private final JwtTokenUtil       jwtTokenUtil;
+    private final String             tokenHeader;
+
+    public JwtAuthorizationTokenFilter(@Qualifier("jwtUserDetailsService") UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, @Value("${jwt.header}") String tokenHeader) {
+        this.userDetailsService = userDetailsService;
+        this.jwtTokenUtil = jwtTokenUtil;
+        this.tokenHeader = tokenHeader;
+    }
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
+        log.debug("processing authentication for '{}'", request.getRequestURL());
+
+        final String requestHeader = request.getHeader(this.tokenHeader);
+
+        String username = null;
+        String authToken = null;
+        if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
+            authToken = requestHeader.substring(7);
+            try {
+                username = jwtTokenUtil.getUsernameFromToken(authToken);
+            } catch (IllegalArgumentException e) {
+                log.error("an error occurred during getting username from token", e);
+            } catch (ExpiredJwtException e) {
+                log.warn("the token is expired and not valid anymore", e);
+            } catch (SignatureException e) {
+                log.error(e.getMessage());
+            }
+        } else {
+            // log.warn("couldn't find bearer string, will ignore the header");
+        }
+
+        log.debug("checking authentication for user '{}'", username);
+        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+            log.debug("security context was null, so authorizing user");
+
+            // It is not compelling necessary to load the use details from the database. You could also store the information
+            // in the token and read it from it. It's up to you ;)            
+            UserDetails userDetails;
+            try {
+                userDetails = userDetailsService.loadUserByUsername(username);
+            } catch (Exception e) {
+                //response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
+                chain.doFilter(request, response);
+                return;
+            }
+
+
+            // For simple validation it is completely sufficient to just check the token integrity. You don't have to call
+            // the database compellingly. Again it's up to you ;)
+            if (jwtTokenUtil.validateToken(authToken, userDetails)) {
+                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
+                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+                SecurityContextHolder.getContext().setAuthentication(authentication);
+            }
+        }
+
+        chain.doFilter(request, response);
+    }
+}

+ 17 - 1
src/main/java/com/example/jpatest/security/JwtTokenUtil.java

@@ -1,5 +1,8 @@
 package com.example.jpatest.security;
 
+import com.example.jpatest.domain.UserToken;
+import com.example.jpatest.repo.UserTokenRepo;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import io.jsonwebtoken.Claims;
 import io.jsonwebtoken.Clock;
 import io.jsonwebtoken.Jwts;
@@ -23,11 +26,15 @@ public class JwtTokenUtil implements Serializable {
     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;
 
-    public JwtTokenUtil(JwtConfig jwtConfig) {
+    private final UserTokenRepo userTokenRepo;
+
+    public JwtTokenUtil(JwtConfig jwtConfig, UserTokenRepo userTokenRepo) {
         this.jwtConfig = jwtConfig;
+        this.userTokenRepo = userTokenRepo;
     }
 
     public String getUsernameFromToken(String token) {
@@ -56,6 +63,13 @@ public class JwtTokenUtil implements Serializable {
 
     private Boolean isTokenExpired(String token) {
         final Date expiration = getExpirationDateFromToken(token);
+        Optional<UserToken> userToken = userTokenRepo.findById(getUsernameFromToken(token));
+        if (!userToken.isPresent()) {
+            return true;
+        }
+        if (!token.equals(userToken.get().getToken())) {
+            return true;
+        }
         return expiration.before(clock.now());
     }
 
@@ -72,6 +86,8 @@ public class JwtTokenUtil implements Serializable {
         JwtUser jwtUser = (JwtUser) userDetails;
         Map<String, Object> claims = new HashMap<>();
         String token = doGenerateToken(claims, userDetails.getUsername());
+        userTokenRepo.deleteById(jwtUser.getUser().getUsername());
+        userTokenRepo.save(new UserToken(jwtUser.getUser().getUsername(), token));
         return token;
     }
 

+ 2 - 2
src/main/java/com/example/jpatest/security/JwtUser.java

@@ -17,8 +17,8 @@ public class JwtUser implements UserDetails {
     private static final long serialVersionUID = 5803985158027956021L;
 
     private final Collection<? extends GrantedAuthority> authorities;
-    private final Date                                   lastPasswordResetDate;
-    private       User                                   user;
+    private final Date lastPasswordResetDate;
+    private       User user;
 
     public JwtUser(User user, Collection<? extends GrantedAuthority> authorities) {
         this.authorities = authorities;

+ 13 - 2
src/main/java/com/example/jpatest/security/JwtUserFactory.java

@@ -1,9 +1,12 @@
 package com.example.jpatest.security;
 
 import com.example.jpatest.domain.User;
+import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
 
-import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 public final class JwtUserFactory {
 
@@ -11,7 +14,15 @@ public final class JwtUserFactory {
     }
 
     public static JwtUser create(User user) {
-        return new JwtUser(user, Collections.singleton(new SimpleGrantedAuthority("user")));
+        return new JwtUser(user, mapToGrantedAuthorities(user.getAuthorities()));
     }
 
+    private static List<GrantedAuthority> mapToGrantedAuthorities(Set<Authority> authorities) {
+        if (authorities != null) {
+            return authorities.stream()
+                    .map(authority -> new SimpleGrantedAuthority(authority.getName()))
+                    .collect(Collectors.toList());
+        }
+        return null;
+    }
 }

+ 7 - 55
src/main/java/com/example/jpatest/security/WebSecurityConfig.java

@@ -16,6 +16,7 @@ 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;
 
 @EnableWebSecurity
 @EnableGlobalMethodSecurity(prePostEnabled = true)
@@ -24,13 +25,16 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
     private final JwtAuthenticationEntryPoint unauthorizedHandler;
     private final UserDetailsService          userDetailsService;
+    private final JwtAuthorizationTokenFilter authenticationTokenFilter;
     private final String                      tokenHeader;
 
     public WebSecurityConfig(JwtAuthenticationEntryPoint unauthorizedHandler,
                              @Qualifier("jwtUserDetailsService") UserDetailsService userDetailsService,
+                             JwtAuthorizationTokenFilter authenticationTokenFilter,
                              @Value("${jwt.header}") String tokenHeader) {
         this.unauthorizedHandler = unauthorizedHandler;
         this.userDetailsService = userDetailsService;
+        this.authenticationTokenFilter = authenticationTokenFilter;
         this.tokenHeader = tokenHeader;
     }
 
@@ -59,60 +63,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
                 // dont authenticate this particular request
                 .authorizeRequests()
                 //swagger-ui放行路径
-                .antMatchers("/v2/api-docs", "/swagger-ui.html", "/swagger-resources/**", "/webjars/**").permitAll()
-                .antMatchers("/user/register").permitAll()
-                .antMatchers("/upload/**").permitAll()
-                .antMatchers("/files/**").permitAll()
-                .antMatchers("/static/**").permitAll()
-                .antMatchers("/auth/**").permitAll()
-                .antMatchers("/captcha/**").permitAll()
-                .antMatchers("/admin/**").permitAll()
-                .antMatchers("/systemVariable/all").permitAll()
-                .antMatchers("/**/excel").permitAll()
-                .antMatchers("/wx/**").permitAll()
-                .antMatchers("/sms/sendVerify").permitAll()
-                .antMatchers("/sms/verify").permitAll()
-                .antMatchers("/error").permitAll()
-                .antMatchers("/401").permitAll()
-                .antMatchers("/404").permitAll()
-                .antMatchers("/500").permitAll()
-                .antMatchers("/MP_verify*").permitAll()
-                .antMatchers("/payOrder/**").permitAll()
-                .antMatchers("/notify/**").permitAll()
-                .antMatchers("/banner/all").permitAll()
-                .antMatchers("/collection/all").permitAll()
-                .antMatchers("/collection/get/**").permitAll()
-                .antMatchers("/asset/get/**").permitAll()
-                .antMatchers("/asset/tokenHistory").permitAll()
-                .antMatchers("/user/all").permitAll()
-                .antMatchers("/user/get/*").permitAll()
-                .antMatchers("/news/all").permitAll()
-                .antMatchers("/news/get/*").permitAll()
-                .antMatchers("/user/forgotPassword").permitAll()
-                .antMatchers("/sysConfig/get/*").permitAll()
-                .antMatchers("/sysConfig/getDecimal/*").permitAll()
-                .antMatchers("/user/code2openId").permitAll()
-                .antMatchers("/blindBoxItem/all").permitAll()
-                .antMatchers("/collection/recommend").permitAll()
-                .antMatchers("/order/**/status").permitAll()
-                .antMatchers("/order/checkLimit").permitAll()
-                .antMatchers("/mintOrder/**/status").permitAll()
-                .antMatchers("/activity/all").permitAll()
-                .antMatchers("/activity/get/*").permitAll()
-                .antMatchers("/mintActivity/all").permitAll()
-                .antMatchers("/mintActivity/get/**").permitAll()
-                .antMatchers("/purchaseLevel/all").permitAll()
-                .antMatchers("/purchaseLevel/get/**").permitAll()
-                .antMatchers("/appVersion/checkIosReview").permitAll()
-                .antMatchers("/appVersion/checkAndroidReview").permitAll()
-                .antMatchers("/news/all").permitAll()
-                .antMatchers("/news/get/**").permitAll()
-                .antMatchers("/druid/**").permitAll()
-                .antMatchers("/identityAuth/autoAuth").permitAll()
-                .antMatchers("/statistic/weekTop").permitAll()
-                .antMatchers("/showroom/all").permitAll()
-                .antMatchers("/showroom/get/**").permitAll()
-                .antMatchers("/testClass/**").permitAll()
+                .antMatchers("/**/**").permitAll()
                 // all other requests need to be authenticated
                 .anyRequest().authenticated().and()
                 // make sure we use stateless session; session won't be used to
@@ -121,6 +72,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
                 .and().sessionManagement()
                 .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
         // Add a filter to validate the tokens with every request
+        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
     }
 
     @Override
@@ -134,7 +86,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
                 .ignoring()
                 .antMatchers(
                         HttpMethod.GET,
-                        "/**",
+                        "/",
                         "/*.html",
                         "/**/favicon.ico",
                         "/**/*.html",

+ 40 - 0
src/main/java/com/example/jpatest/utils/UserAuthoritySerializer.java

@@ -0,0 +1,40 @@
+package com.example.jpatest.utils;
+
+import com.example.jpatest.security.Authority;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.core.type.WritableTypeId;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+public class UserAuthoritySerializer extends JsonSerializer<Set<Authority>> {
+    @Override
+    public void serialize(Set<Authority> value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+        if (value == null) {
+            gen.writeNull();
+        } else {
+            gen.writeStartArray();
+            for (Authority authority : value) {
+                gen.writeObject(authority);
+            }
+            gen.writeEndArray();
+        }
+    }
+
+    @Override
+    public void serializeWithType(Set<Authority> value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException {
+        HashSet<Authority> set = new HashSet<>(value);
+        WritableTypeId typeId = typeSer.writeTypePrefix(gen, typeSer.typeId(set, JsonToken.VALUE_STRING));
+        gen.writeStartArray();
+        for (Authority authority : set) {
+            gen.writeObject(authority);
+        }
+        gen.writeEndArray();
+        typeSer.writeTypeSuffix(gen, typeId);
+    }
+}

+ 3 - 1
src/main/resources/application.yaml

@@ -100,4 +100,6 @@ spring:
 jwt:
   secret: XvAD0kboD76Dpebm
   header: Authorization
-  expiration: 2592000 #30days
+  expiration: 2592000 #30days
+storage:
+  local_path: /var