diff --git a/mall-security/.gitignore b/mall-security/.gitignore
new file mode 100644
index 0000000..a2a3040
--- /dev/null
+++ b/mall-security/.gitignore
@@ -0,0 +1,31 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**
+!**/src/test/**
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+
+### VS Code ###
+.vscode/
diff --git a/mall-security/pom.xml b/mall-security/pom.xml
new file mode 100644
index 0000000..1ed891e
--- /dev/null
+++ b/mall-security/pom.xml
@@ -0,0 +1,38 @@
+
+
+ 4.0.0
+ com.macro.mall
+ mall-security
+ 1.0-SNAPSHOT
+ jar
+
+ mall-security
+ mall-security project for mall
+
+
+ com.macro.mall
+ mall
+ 1.0-SNAPSHOT
+
+
+
+
+ com.macro.mall
+ mall-common
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ io.jsonwebtoken
+ jjwt
+
+
+
+
diff --git a/mall-security/src/main/java/com/macro/mall/security/component/JwtAuthenticationTokenFilter.java b/mall-security/src/main/java/com/macro/mall/security/component/JwtAuthenticationTokenFilter.java
new file mode 100644
index 0000000..bddd916
--- /dev/null
+++ b/mall-security/src/main/java/com/macro/mall/security/component/JwtAuthenticationTokenFilter.java
@@ -0,0 +1,57 @@
+package com.macro.mall.security.component;
+
+import com.macro.mall.security.util.JwtTokenUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+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.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;
+
+/**
+ * JWT登录授权过滤器
+ * Created by macro on 2018/4/26.
+ */
+public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
+ private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);
+ @Autowired
+ private UserDetailsService userDetailsService;
+ @Autowired
+ private JwtTokenUtil jwtTokenUtil;
+ @Value("${jwt.tokenHeader}")
+ private String tokenHeader;
+ @Value("${jwt.tokenHead}")
+ private String tokenHead;
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request,
+ HttpServletResponse response,
+ FilterChain chain) throws ServletException, IOException {
+ String authHeader = request.getHeader(this.tokenHeader);
+ if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
+ String authToken = authHeader.substring(this.tokenHead.length());// The part after "Bearer "
+ String username = jwtTokenUtil.getUserNameFromToken(authToken);
+ LOGGER.info("checking username:{}", username);
+ if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+ UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
+ if (jwtTokenUtil.validateToken(authToken, userDetails)) {
+ UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
+ authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+ LOGGER.info("authenticated user:{}", username);
+ SecurityContextHolder.getContext().setAuthentication(authentication);
+ }
+ }
+ }
+ chain.doFilter(request, response);
+ }
+}
diff --git a/mall-security/src/main/java/com/macro/mall/security/component/RestAuthenticationEntryPoint.java b/mall-security/src/main/java/com/macro/mall/security/component/RestAuthenticationEntryPoint.java
new file mode 100644
index 0000000..a0b370c
--- /dev/null
+++ b/mall-security/src/main/java/com/macro/mall/security/component/RestAuthenticationEntryPoint.java
@@ -0,0 +1,26 @@
+package com.macro.mall.security.component;
+
+import cn.hutool.json.JSONUtil;
+import com.macro.mall.common.api.CommonResult;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 当未登录或者token失效访问接口时,自定义的返回结果
+ * Created by macro on 2018/5/14.
+ */
+public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
+ @Override
+ public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
+ response.setCharacterEncoding("UTF-8");
+ response.setContentType("application/json");
+ response.getWriter().println(JSONUtil.parse(CommonResult.unauthorized(authException.getMessage())));
+ response.getWriter().flush();
+ }
+}
diff --git a/mall-security/src/main/java/com/macro/mall/security/component/RestfulAccessDeniedHandler.java b/mall-security/src/main/java/com/macro/mall/security/component/RestfulAccessDeniedHandler.java
new file mode 100644
index 0000000..b885449
--- /dev/null
+++ b/mall-security/src/main/java/com/macro/mall/security/component/RestfulAccessDeniedHandler.java
@@ -0,0 +1,28 @@
+package com.macro.mall.security.component;
+
+import cn.hutool.json.JSONUtil;
+import com.macro.mall.common.api.CommonResult;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * 当访问接口没有权限时,自定义的返回结果
+ * Created by macro on 2018/4/26.
+ */
+public class RestfulAccessDeniedHandler implements AccessDeniedHandler{
+ @Override
+ public void handle(HttpServletRequest request,
+ HttpServletResponse response,
+ AccessDeniedException e) throws IOException, ServletException {
+ response.setCharacterEncoding("UTF-8");
+ response.setContentType("application/json");
+ response.getWriter().println(JSONUtil.parse(CommonResult.forbidden(e.getMessage())));
+ response.getWriter().flush();
+ }
+}
diff --git a/mall-security/src/main/java/com/macro/mall/security/config/IgnoreUrlsConfig.java b/mall-security/src/main/java/com/macro/mall/security/config/IgnoreUrlsConfig.java
new file mode 100644
index 0000000..0dc852a
--- /dev/null
+++ b/mall-security/src/main/java/com/macro/mall/security/config/IgnoreUrlsConfig.java
@@ -0,0 +1,22 @@
+package com.macro.mall.security.config;
+
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 用于配置不需要保护的资源路径
+ * Created by macro on 2018/11/5.
+ */
+@Getter
+@Setter
+@ConfigurationProperties(prefix = "ignored")
+public class IgnoreUrlsConfig {
+
+ private List urls = new ArrayList<>();
+
+}
diff --git a/mall-security/src/main/java/com/macro/mall/security/config/SecurityConfig.java b/mall-security/src/main/java/com/macro/mall/security/config/SecurityConfig.java
new file mode 100644
index 0000000..6fea681
--- /dev/null
+++ b/mall-security/src/main/java/com/macro/mall/security/config/SecurityConfig.java
@@ -0,0 +1,99 @@
+package com.macro.mall.security.config;
+
+import com.macro.mall.security.component.JwtAuthenticationTokenFilter;
+import com.macro.mall.security.component.RestAuthenticationEntryPoint;
+import com.macro.mall.security.component.RestfulAccessDeniedHandler;
+import com.macro.mall.security.util.JwtTokenUtil;
+import org.springframework.context.annotation.Bean;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+
+/**
+ * 对SpringSecurity的配置的扩展,支持自定义白名单资源路径和查询用户逻辑
+ * Created by macro on 2019/11/5.
+ */
+public class SecurityConfig extends WebSecurityConfigurerAdapter {
+
+ @Override
+ protected void configure(HttpSecurity httpSecurity) throws Exception {
+ ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry = httpSecurity
+ .authorizeRequests();
+ for (String url : ignoreUrlsConfig().getUrls()) {
+ registry.antMatchers(url).permitAll();
+ }
+ //允许跨域请求的OPTIONS请求
+ registry.antMatchers(HttpMethod.OPTIONS)
+ .permitAll();
+ // 任何请求需要身份认证
+ registry.and()
+ .authorizeRequests()
+ .anyRequest()
+ .authenticated()
+ // 关闭跨站请求防护及不使用session
+ .and()
+ .csrf()
+ .disable()
+ .sessionManagement()
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ // 自定义权限拒绝处理类
+ .and()
+ .exceptionHandling()
+ .accessDeniedHandler(restfulAccessDeniedHandler())
+ .authenticationEntryPoint(restAuthenticationEntryPoint())
+ // 自定义权限拦截器JWT过滤器
+ .and()
+ .addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
+ }
+
+ @Override
+ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
+ auth.userDetailsService(userDetailsService())
+ .passwordEncoder(passwordEncoder());
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+
+ @Bean
+ public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
+ return new JwtAuthenticationTokenFilter();
+ }
+
+ @Bean
+ @Override
+ public AuthenticationManager authenticationManagerBean() throws Exception {
+ return super.authenticationManagerBean();
+ }
+
+ @Bean
+ public RestfulAccessDeniedHandler restfulAccessDeniedHandler() {
+ return new RestfulAccessDeniedHandler();
+ }
+
+ @Bean
+ public RestAuthenticationEntryPoint restAuthenticationEntryPoint() {
+ return new RestAuthenticationEntryPoint();
+ }
+
+ @Bean
+ public IgnoreUrlsConfig ignoreUrlsConfig() {
+ return new IgnoreUrlsConfig();
+ }
+
+ @Bean
+ public JwtTokenUtil jwtTokenUtil() {
+ return new JwtTokenUtil();
+ }
+
+}
diff --git a/mall-security/src/main/java/com/macro/mall/security/util/JwtTokenUtil.java b/mall-security/src/main/java/com/macro/mall/security/util/JwtTokenUtil.java
new file mode 100644
index 0000000..3c4338d
--- /dev/null
+++ b/mall-security/src/main/java/com/macro/mall/security/util/JwtTokenUtil.java
@@ -0,0 +1,144 @@
+package com.macro.mall.security.util;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * JwtToken生成的工具类
+ * JWT token的格式:header.payload.signature
+ * header的格式(算法、token的类型):
+ * {"alg": "HS512","typ": "JWT"}
+ * payload的格式(用户名、创建时间、生成时间):
+ * {"sub":"wang","created":1489079981393,"exp":1489684781}
+ * signature的生成算法:
+ * HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
+ * Created by macro on 2018/4/26.
+ */
+public class JwtTokenUtil {
+ private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
+ private static final String CLAIM_KEY_USERNAME = "sub";
+ private static final String CLAIM_KEY_CREATED = "created";
+ @Value("${jwt.secret}")
+ private String secret;
+ @Value("${jwt.expiration}")
+ private Long expiration;
+ @Value("${jwt.tokenHead}")
+ private String tokenHead;
+
+ /**
+ * 根据负责生成JWT的token
+ */
+ private String generateToken(Map claims) {
+ return Jwts.builder()
+ .setClaims(claims)
+ .setExpiration(generateExpirationDate())
+ .signWith(SignatureAlgorithm.HS512, secret)
+ .compact();
+ }
+
+ /**
+ * 从token中获取JWT中的负载
+ */
+ private Claims getClaimsFromToken(String token) {
+ Claims claims = null;
+ try {
+ claims = Jwts.parser()
+ .setSigningKey(secret)
+ .parseClaimsJws(token)
+ .getBody();
+ } catch (Exception e) {
+ LOGGER.info("JWT格式验证失败:{}", token);
+ }
+ return claims;
+ }
+
+ /**
+ * 生成token的过期时间
+ */
+ private Date generateExpirationDate() {
+ return new Date(System.currentTimeMillis() + expiration * 1000);
+ }
+
+ /**
+ * 从token中获取登录用户名
+ */
+ public String getUserNameFromToken(String token) {
+ String username;
+ try {
+ Claims claims = getClaimsFromToken(token);
+ username = claims.getSubject();
+ } catch (Exception e) {
+ username = null;
+ }
+ return username;
+ }
+
+ /**
+ * 验证token是否还有效
+ *
+ * @param token 客户端传入的token
+ * @param userDetails 从数据库中查询出来的用户信息
+ */
+ public boolean validateToken(String token, UserDetails userDetails) {
+ String username = getUserNameFromToken(token);
+ return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
+ }
+
+ /**
+ * 判断token是否已经失效
+ */
+ private boolean isTokenExpired(String token) {
+ Date expiredDate = getExpiredDateFromToken(token);
+ return expiredDate.before(new Date());
+ }
+
+ /**
+ * 从token中获取过期时间
+ */
+ private Date getExpiredDateFromToken(String token) {
+ Claims claims = getClaimsFromToken(token);
+ return claims.getExpiration();
+ }
+
+ /**
+ * 根据用户信息生成token
+ */
+ public String generateToken(UserDetails userDetails) {
+ Map claims = new HashMap<>();
+ claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
+ claims.put(CLAIM_KEY_CREATED, new Date());
+ return generateToken(claims);
+ }
+
+ /**
+ * 判断token是否可以被刷新
+ */
+ private boolean canRefresh(String token) {
+ return !isTokenExpired(token);
+ }
+
+
+ /**
+ * 当原来的token没过期是可以刷新
+ *
+ * @param oldToken 带tokenHead的token
+ */
+ public String refreshHeadToken(String oldToken) {
+ String token = oldToken.substring(tokenHead.length());
+ if (canRefresh(token)) {
+ Claims claims = getClaimsFromToken(token);
+ claims.put(CLAIM_KEY_CREATED, new Date());
+ return generateToken(claims);
+ }
+ return null;
+ }
+}