2 Incheckningar 81977af115 ... 72c1cd6b5b

Upphovsman SHA1 Meddelande Datum
  jc 72c1cd6b5b shiro 3 veckor sedan
  jc 67de82097f shiro 3 veckor sedan

+ 18 - 0
src/main/java/com/xet/config/CorsConfig.java

@@ -0,0 +1,18 @@
+package com.xet.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class CorsConfig implements WebMvcConfigurer {
+
+    @Override
+    public void addCorsMappings(CorsRegistry registry) {
+        registry.addMapping("/**")
+            .allowedOrigins("*")
+            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
+            .maxAge(3600);
+    }
+
+}

+ 159 - 0
src/main/java/com/xet/constants/CommonConstant.java

@@ -0,0 +1,159 @@
+
+
+package com.xet.constants;
+
+/**
+ * 公共常量
+ */
+public interface CommonConstant {
+
+    /**
+     * 默认页码为1
+     */
+    Long DEFAULT_PAGE_INDEX = 1L;
+
+    /**
+     * 默认页大小为10
+     */
+    Long DEFAULT_PAGE_SIZE = 10L;
+
+    /**
+     * 分页总行数名称
+     */
+    String PAGE_TOTAL_NAME = "total";
+
+    /**
+     * 分页数据列表名称
+     */
+    String PAGE_RECORDS_NAME = "records";
+
+    /**
+     * 分页当前页码名称
+     */
+    String PAGE_INDEX_NAME = "pageIndex";
+
+    /**
+     * 分页当前页大小名称
+     */
+    String PAGE_SIZE_NAME = "pageSize";
+
+    /**
+     * 分页最后一页
+     */
+    String PAGE_LAST_NAME = "lastPage";
+
+    /**
+     * 登录用户
+     */
+    String LOGIN_SYS_USER = "loginSysUser";
+
+    /**
+     * 登录token
+     */
+    String JWT_DEFAULT_TOKEN_NAME = "token";
+
+    /**
+     * JWT用户名
+     */
+    String JWT_USERNAME = "username";
+
+    /**
+     * JWT刷新新token响应状态码
+     */
+    int JWT_REFRESH_TOKEN_CODE = 460;
+
+    /**
+     * JWT刷新新token响应状态码,
+     * Redis中不存在,但jwt未过期,不生成新的token,返回361状态码
+     */
+    int JWT_INVALID_TOKEN_CODE = 461;
+
+    /**
+     * JWT Token默认密钥
+     */
+    String JWT_DEFAULT_SECRET = "888888";
+
+    /**
+     * JWT 默认过期时间,3600L,单位秒
+     */
+    Long JWT_DEFAULT_EXPIRE_SECOND = 36000L;
+
+    /**
+     * 默认头像
+     */
+    String DEFAULT_HEAD_URL = "";
+
+    /**
+     * 管理员角色名称
+     */
+    String ADMIN_ROLE_NAME = "管理员";
+
+    String ADMIN_LOGIN = "adminLogin";
+
+    /**
+     * 验证码token
+     */
+    String VERIFY_TOKEN = "verifyToken";
+
+    /**
+     * 图片
+     */
+    String IMAGE = "image";
+
+    /**
+     * JPEG
+     */
+    String JPEG = "JPEG";
+
+    /**
+     * base64前缀
+     */
+    String BASE64_PREFIX = "data:image/png;base64,";
+
+    /**
+     * ..
+     */
+    String SPOT_SPOT = "..";
+
+    /**
+     * ../
+     */
+    String SPOT_SPOT_BACKSLASH = "../";
+
+    /**
+     * SpringBootAdmin登录信息
+     */
+    String ADMIN_LOGIN_SESSION = "adminLoginSession";
+
+    /**
+     * 用户浏览器代理
+     */
+    String USER_AGENT = "User-Agent";
+
+    /**
+     * 本机地址IP
+     */
+    String LOCALHOST_IP = "127.0.0.1";
+    /**
+     * 本机地址名称
+     */
+    String LOCALHOST_IP_NAME = "本机地址";
+    /**
+     * 局域网IP
+     */
+    String LAN_IP = "192.168";
+    /**
+     * 局域网名称
+     */
+    String LAN_IP_NAME = "局域网";
+    /**
+     * 忽略appId
+     */
+    String NOT_WITH_App_Id = "notWithAppId";
+
+    String mobile="1688888888";
+
+    String password="boss1688";
+
+    Integer chains_app_id=10001;
+}

+ 73 - 0
src/main/java/com/xet/properties/JwtProperties.java

@@ -0,0 +1,73 @@
+
+
+package com.xet.properties;
+
+import com.xet.constants.CommonConstant;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * JWT属性配置
+ **/
+@Data
+@Component
+@ConfigurationProperties(prefix = "spring-boot-futu.jwt")
+public class JwtProperties {
+
+    /**
+     * token名称,默认名称为:token,可自定义
+     */
+    private String tokenName = CommonConstant.JWT_DEFAULT_TOKEN_NAME;
+
+    /**
+     * 密码
+     */
+    private String secret = CommonConstant.JWT_DEFAULT_SECRET;
+
+    /**
+     * 签发人
+     */
+    private String issuer;
+
+    /**
+     * 主题
+     */
+    private String subject;
+
+    /**
+     * 签发的目标
+     */
+    private String audience;
+
+    /**
+     * token失效时间,默认1小时,60*60=3600
+     */
+    private Long expireSecond = CommonConstant.JWT_DEFAULT_EXPIRE_SECOND;
+
+    /**
+     * 是否刷新token,默认为true
+     */
+    private boolean refreshToken = true;
+
+    /**
+     * 刷新token倒计时,默认10分钟,10*60=600
+     */
+    private Integer refreshTokenCountdown;
+
+    /**
+     * redis校验jwt token是否存在
+     */
+    private boolean redisCheck;
+
+    /**
+     * 单用户登录,一个用户只能又一个有效的token
+     */
+    private boolean singleLogin;
+
+    /**
+     * 是否进行盐值校验
+     */
+    private boolean saltCheck;
+
+}

+ 27 - 0
src/main/java/com/xet/properties/ShiroPermissionProperties.java

@@ -0,0 +1,27 @@
+
+
+package com.xet.properties;
+
+import lombok.Data;
+
+/**
+ * Shiro权限配置映射类
+ **/
+@Data
+public class ShiroPermissionProperties {
+
+    /**
+     * 路径
+     */
+    private String url;
+    /**
+     * 路径数组
+     */
+    private String[] urls;
+
+    /**
+     * 权限
+     */
+    private String permission;
+
+}

+ 39 - 0
src/main/java/com/xet/properties/ShiroProperties.java

@@ -0,0 +1,39 @@
+
+
+package com.xet.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.NestedConfigurationProperty;
+
+import java.util.List;
+
+/**
+ * Shiro配置映射类
+ **/
+@Data
+@ConfigurationProperties(prefix = "spring-boot-futu.shiro")
+public class ShiroProperties {
+
+    /**
+     * 是否启用
+     */
+    private boolean enable;
+
+    /**
+     * 路径权限配置
+     */
+    private String filterChainDefinitions;
+
+    /**
+     * 设置无需权限路径集合
+     */
+    private List<String[]> anon;
+
+    /**
+     * 权限配置集合
+     */
+    @NestedConfigurationProperty
+    private List<ShiroPermissionProperties> permission;
+
+}

+ 29 - 0
src/main/java/com/xet/shiro/config/JwtCredentialsMatcher.java

@@ -0,0 +1,29 @@
+
+
+package com.xet.shiro.config;
+
+import com.xet.shiro.utils.JwtUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.credential.CredentialsMatcher;
+
+/**
+ * JWT证书匹配
+ **/
+@Slf4j
+public class JwtCredentialsMatcher implements CredentialsMatcher {
+
+    @Override
+    public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo authenticationInfo) {
+        String token = authenticationToken.getCredentials().toString();
+        String salt = authenticationInfo.getCredentials().toString();
+        try {
+            return JwtUtil.verifyToken(token, salt);
+        } catch (Exception e) {
+            log.error("JWT Token CredentialsMatch Exception:" + e.getMessage(), e);
+        }
+        return false;
+    }
+
+}

+ 169 - 0
src/main/java/com/xet/shiro/config/JwtFilter.java

@@ -0,0 +1,169 @@
+package com.xet.shiro.config;
+
+import com.xet.properties.JwtProperties;
+import com.xet.shiro.utils.JwtToken;
+import com.xet.shiro.utils.JwtTokenUtil;
+import com.xet.shiro.utils.JwtUtil;
+import com.xet.shiro.utils.SaltUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
+import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
+import org.apache.shiro.web.util.WebUtils;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Shiro JWT授权过滤器
+ **/
+@Slf4j
+public class JwtFilter extends AuthenticatingFilter {
+    private JwtProperties jwtProperties;
+
+    public JwtFilter(   JwtProperties jwtProperties) {
+         this.jwtProperties = jwtProperties;
+    }
+
+    /**
+     * 将JWT Token包装成AuthenticationToken
+     *
+     * @param servletRequest
+     * @param servletResponse
+     * @return
+     * @throws Exception
+     */
+    @Override
+    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
+        String url = ((ShiroHttpServletRequest) servletRequest).getRequestURI();
+        String model = JwtTokenUtil.getModel(url);
+        String token = JwtTokenUtil.getToken(model);
+        if (StringUtils.isBlank(token)) {
+            throw new AuthenticationException("token不能为空");
+        }
+        if (JwtUtil.isExpired(token)) {
+            throw new AuthenticationException("JWT Token已过期,token:" + token);
+        }
+
+        // 如果开启redis二次校验,或者设置为单个用户token登录,则先在redis中判断token是否存在
+        if (jwtProperties.isRedisCheck() || jwtProperties.isSingleLogin()) {
+            boolean redisExpired = false;
+//            if(url.startsWith("/api/admin/")){
+//                redisExpired = adminLoginRedisService.exists(token);
+//            }else if(url.startsWith("/api/shop/")){
+//                redisExpired = shopLoginRedisService.exists(token);
+//            }else if(url.startsWith("/api/supplier/")){
+//                redisExpired = supplierLoginRedisService.exists(token);
+//            }else if(url.startsWith("/api/service/")){
+//                redisExpired = serviceLoginRedisService.exists(token);
+//            }
+//            if (!redisExpired) {
+//                throw new AuthenticationException("Redis Token不存在,token:" + token);
+//            }
+        }
+
+        String username = JwtUtil.getUsername(token);
+        String salt = SaltUtil.getSalt("futuvip", String.valueOf(jwtProperties));
+        if (jwtProperties.isSaltCheck()) {
+//            if(url.startsWith("/api/admin/")) {
+//                salt = adminLoginRedisService.getSalt(username);
+//            }else if(url.startsWith("/api/shop/")){
+//                salt = shopLoginRedisService.getSalt(username);
+//            }else if(url.startsWith("/api/supplier/")){
+//                salt = supplierLoginRedisService.getSalt(username);
+//            }else if(url.startsWith("/api/service/")){
+//                salt = serviceLoginRedisService.getSalt(username);
+//            }
+        } else {
+            salt = jwtProperties.getSecret();
+        }
+        return JwtToken.build(token, username, salt, jwtProperties.getExpireSecond());
+    }
+
+    /**
+     * 访问失败处理
+     *
+     * @param request
+     * @param response
+     * @return
+     * @throws Exception
+     */
+    @Override
+    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
+        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
+        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
+        // 返回-1未登录
+        String url = httpServletRequest.getRequestURI();
+        log.error("onAccessDenied url:{}", url);
+//        ApiResult<Boolean> apiResult = ApiResult.fail(ApiCode.NOT_LOGIN);
+//        HttpServletResponseUtil.printJson(httpServletResponse, apiResult);
+        return false;
+    }
+
+    /**
+     * 判断是否允许访问
+     *
+     * @param request
+     * @param response
+     * @param mappedValue
+     * @return
+     */
+    @Override
+    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
+        String url = WebUtils.toHttp(request).getRequestURI();
+        log.debug("isAccessAllowed url:{}", url);
+        if (this.isLoginRequest(request, response)) {
+            return true;
+        }
+        boolean allowed = false;
+        try {
+            allowed = executeLogin(request, response);
+        } catch (IllegalStateException e) { //not found any token
+            log.error("Token不能为空", e);
+        } catch (Exception e) {
+            log.error("访问错误", e);
+        }
+        return allowed || super.isPermissive(mappedValue);
+    }
+
+    /**
+     * 登录成功处理
+     *
+     * @param token
+     * @param subject
+     * @param request
+     * @param response
+     * @return
+     * @throws Exception
+     */
+    @Override
+    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
+        String url = WebUtils.toHttp(request).getRequestURI();
+        log.debug("鉴权成功,token:{},url:{}", token, url);
+        // 刷新token
+        JwtToken jwtToken = (JwtToken) token;
+        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
+//        shiroLoginService.refreshToken(jwtToken, url, httpServletResponse);
+        return true;
+    }
+
+    /**
+     * 登录失败处理
+     *
+     * @param token
+     * @param e
+     * @param request
+     * @param response
+     * @return
+     */
+    @Override
+    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
+        log.error("登录失败,token:" + token + ",error:" + e.getMessage(), e);
+        return false;
+    }
+}

+ 99 - 0
src/main/java/com/xet/shiro/config/JwtRealm.java

@@ -0,0 +1,99 @@
+
+
+package com.xet.shiro.config;
+
+import com.xet.shiro.utils.JwtToken;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Shiro 授权认证
+ **/
+@Slf4j
+public class JwtRealm extends AuthorizingRealm {
+
+
+
+    @Autowired
+    private HttpServletRequest request;
+
+    @Override
+    public boolean supports(AuthenticationToken token) {
+        return token != null && token instanceof JwtToken;
+    }
+
+    /**
+     * 授权认证,设置角色/权限信息
+     *
+     * @param principalCollection
+     * @return
+     */
+    @Override
+    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
+        log.debug("doGetAuthorizationInfo principalCollection..."+request.getRequestURI());
+        // 设置角色/权限信息
+        JwtToken jwtToken = (JwtToken) principalCollection.getPrimaryPrincipal();
+        // 获取username
+        String username = jwtToken.getUsername();
+        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
+        //这里应是查数据库
+        Map<String,Set<String>> map=new HashMap<>();
+        Set<String> permissions=new HashSet<>();
+        permissions.add("emp:get");
+        permissions.add("emp:add");
+        map.put("admin",permissions);
+        Set<String> pw=new HashSet<>();
+        pw.add("emp:add");
+        map.put("user001",pw);
+
+       Set<String>user_permission= map.get(username);
+       if(!user_permission.isEmpty()) {
+           authorizationInfo.setStringPermissions(user_permission);
+       }
+
+        return authorizationInfo;
+    }
+
+    /**
+     * 登录认证
+     *
+     * @param authenticationToken
+     * @return
+     * @throws AuthenticationException
+     */
+    @Override
+    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
+        log.debug("doGetAuthenticationInfo authenticationToken...");
+        // 校验token
+        JwtToken jwtToken = (JwtToken) authenticationToken;
+        if (jwtToken == null) {
+            throw new AuthenticationException("jwtToken不能为空");
+        }
+        String salt = jwtToken.getSalt();
+        if (StringUtils.isBlank(salt)) {
+            throw new AuthenticationException("salt不能为空");
+        }
+        return new SimpleAuthenticationInfo(
+                jwtToken,
+                salt,
+                getName()
+        );
+
+    }
+
+}

+ 294 - 0
src/main/java/com/xet/shiro/config/ShiroConfig.java

@@ -0,0 +1,294 @@
+
+
+package com.xet.shiro.config;
+
+import com.alibaba.fastjson.JSON;
+import com.xet.properties.JwtProperties;
+import com.xet.properties.ShiroPermissionProperties;
+import com.xet.properties.ShiroProperties;
+import com.xet.util.IniUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.Authenticator;
+import org.apache.shiro.authc.credential.CredentialsMatcher;
+import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
+import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
+import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
+import org.apache.shiro.mgt.DefaultSubjectDAO;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.mgt.SessionStorageEvaluator;
+import org.apache.shiro.spring.LifecycleBeanPostProcessor;
+import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
+import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
+import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
+import org.apache.shiro.web.mgt.DefaultWebSessionStorageEvaluator;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.filter.DelegatingFilterProxy;
+
+import javax.servlet.DispatcherType;
+import javax.servlet.Filter;
+import java.util.*;
+
+/**
+ * Shiro配置
+ * https://shiro.apache.org/spring.html
+ * https://shiro.apache.org/spring-boot.html
+ **/
+@Slf4j
+@Configuration
+@EnableConfigurationProperties({ShiroProperties.class})
+@ConditionalOnProperty(value = {"spring-boot-jjj.shiro.enable"}, matchIfMissing = true)
+public class ShiroConfig {
+
+    /**
+     * JWT过滤器名称
+     */
+    private static final String JWT_FILTER_NAME = "jwtFilter";
+
+    /**
+     * Shiro过滤器名称
+     */
+    private static final String SHIRO_FILTER_NAME = "shiroFilter";
+
+    /**
+     * anon
+     */
+    private static final String ANON = "anon";
+
+
+    @Bean
+    public CredentialsMatcher credentialsMatcher() {
+        return new JwtCredentialsMatcher();
+    }
+
+    /**
+     * JWT数据源验证
+     *
+     * @return
+     */
+    @Bean
+    public JwtRealm jwtRealm() {
+        JwtRealm jwtRealm = new JwtRealm();
+//        jwtRealm.setCachingEnabled(false);
+        jwtRealm.setCredentialsMatcher(credentialsMatcher());
+        return jwtRealm;
+    }
+
+
+    @Bean
+    public SessionStorageEvaluator sessionStorageEvaluator() {
+        DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultWebSessionStorageEvaluator();
+        sessionStorageEvaluator.setSessionStorageEnabled(false);
+        return sessionStorageEvaluator;
+    }
+
+    @Bean
+    public DefaultSubjectDAO subjectDAO() {
+        DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO();
+        defaultSubjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator());
+        return defaultSubjectDAO;
+    }
+
+    /**
+     * 安全管理器配置
+     *
+     * @return
+     */
+    @Bean
+    public SecurityManager securityManager() {
+        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
+        securityManager.setRealm(jwtRealm());
+        securityManager.setSubjectDAO(subjectDAO());
+        SecurityUtils.setSecurityManager(securityManager);
+        return securityManager;
+    }
+
+    /**
+     * ShiroFilterFactoryBean配置
+     *
+     * @param securityManager
+     * @param shiroProperties
+     * @param jwtProperties
+     * @return
+     */
+    @Bean(SHIRO_FILTER_NAME)
+    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,
+
+                                                         ShiroProperties shiroProperties,
+                                                         JwtProperties jwtProperties) {
+        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
+        shiroFilterFactoryBean.setSecurityManager(securityManager);
+        Map<String, Filter> filterMap = getFilterMap( jwtProperties);
+        shiroFilterFactoryBean.setFilters(filterMap);
+        Map<String, String> filterChainMap = null;
+        try {
+            filterChainMap = getFilterChainDefinitionMap(shiroProperties);
+
+        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return shiroFilterFactoryBean;
+    }
+
+
+    /**
+     * 获取filter map
+     *
+     * @return
+     */
+    private Map<String, Filter> getFilterMap(
+                                             JwtProperties jwtProperties) {
+        Map<String, Filter> filterMap = new LinkedHashMap<>();
+        filterMap.put(JWT_FILTER_NAME, new JwtFilter(jwtProperties));
+        return filterMap;
+    }
+
+
+    /**
+     * Shiro路径权限配置
+     *
+     * @return
+     */
+    private Map<String, String> getFilterChainDefinitionMap(ShiroProperties shiroProperties) throws Exception {
+        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
+        // 获取排除的路径
+        List<String[]> anonList = shiroProperties.getAnon();
+        log.debug("anonList:{}", JSON.toJSONString(anonList));
+        if (CollectionUtils.isNotEmpty(anonList)) {
+            anonList.forEach(anonArray -> {
+                if (ArrayUtils.isNotEmpty(anonArray)) {
+                    for (String anonPath : anonArray) {
+                        filterChainDefinitionMap.put(anonPath, ANON);
+                    }
+                }
+            });
+        }
+
+        // 获取ini格式配置
+        String definitions = shiroProperties.getFilterChainDefinitions();
+        if (StringUtils.isNotBlank(definitions)) {
+            Map<String, String> section = IniUtil.parseIni(definitions);
+            log.debug("definitions:{}", JSON.toJSONString(section));
+            for (Map.Entry<String, String> entry : section.entrySet()) {
+                filterChainDefinitionMap.put(entry.getKey(), entry.getValue());
+            }
+        }
+
+        // 获取自定义权限路径配置集合
+        List<ShiroPermissionProperties> permissionConfigs = shiroProperties.getPermission();
+        log.debug("permissionConfigs:{}", JSON.toJSONString(permissionConfigs));
+        if (CollectionUtils.isNotEmpty(permissionConfigs)) {
+            for (ShiroPermissionProperties permissionConfig : permissionConfigs) {
+                String url = permissionConfig.getUrl();
+                String[] urls = permissionConfig.getUrls();
+                String permission = permissionConfig.getPermission();
+                if (StringUtils.isBlank(url) && ArrayUtils.isEmpty(urls)) {
+                    throw new Exception("shiro permission config 路径配置不能为空");
+                }
+                if (StringUtils.isBlank(permission)) {
+                    throw new Exception("shiro permission config permission不能为空");
+                }
+
+                if (StringUtils.isNotBlank(url)) {
+                    filterChainDefinitionMap.put(url, permission);
+                }
+                if (ArrayUtils.isNotEmpty(urls)) {
+                    for (String string : urls) {
+                        filterChainDefinitionMap.put(string, permission);
+                    }
+                }
+            }
+        }
+
+        // 如果启用shiro,则设置最后一个设置为JWTFilter,否则全部路径放行
+        if (shiroProperties.isEnable()) {
+            filterChainDefinitionMap.put("/**", JWT_FILTER_NAME);
+        } else {
+            filterChainDefinitionMap.put("/**", ANON);
+        }
+
+        log.debug("filterChainMap:{}", JSON.toJSONString(filterChainDefinitionMap));
+
+        // 添加默认的filter
+        Map<String, String> newFilterChainDefinitionMap = addDefaultFilterDefinition(filterChainDefinitionMap);
+        return newFilterChainDefinitionMap;
+    }
+
+    /**
+     * 添加默认的filter权限过滤
+     *
+     * @param filterChainDefinitionMap
+     * @return
+     */
+    private Map<String, String> addDefaultFilterDefinition(Map<String, String> filterChainDefinitionMap) {
+        if (MapUtils.isEmpty(filterChainDefinitionMap)) {
+            return filterChainDefinitionMap;
+        }
+        final Map<String, String> map = new LinkedHashMap<>();
+        for (Map.Entry<String, String> entry : filterChainDefinitionMap.entrySet()) {
+            String key = entry.getKey();
+            String value = entry.getValue();
+            String definition;
+            String[] strings = value.split(",");
+            List<String> list = new ArrayList<>();
+            list.addAll(Arrays.asList(strings));
+            definition = String.join(",", list);
+            map.put(key, definition);
+        }
+        return map;
+    }
+
+    /**
+     * ShiroFilter配置
+     *
+     * @return
+     */
+    @Bean
+    public FilterRegistrationBean delegatingFilterProxy() {
+        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
+        DelegatingFilterProxy proxy = new DelegatingFilterProxy();
+        proxy.setTargetFilterLifecycle(true);
+        proxy.setTargetBeanName(SHIRO_FILTER_NAME);
+        filterRegistrationBean.setFilter(proxy);
+        filterRegistrationBean.setAsyncSupported(true);
+        filterRegistrationBean.setEnabled(true);
+        filterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
+        return filterRegistrationBean;
+    }
+
+    @Bean
+    public Authenticator authenticator() {
+        ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
+        authenticator.setRealms(Arrays.asList(jwtRealm()));
+        authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategy());
+        return authenticator;
+    }
+
+
+    /**
+     * Enabling Shiro Annotations
+     *
+     * @return
+     */
+    @Bean
+    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
+        return new LifecycleBeanPostProcessor();
+    }
+
+    @Bean
+    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
+        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
+        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
+        return authorizationAttributeSourceAdvisor;
+    }
+
+}

+ 79 - 0
src/main/java/com/xet/shiro/utils/JwtToken.java

@@ -0,0 +1,79 @@
+
+
+package com.xet.shiro.utils;
+
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.xet.util.IpUtil;
+import lombok.Data;
+import lombok.experimental.Accessors;
+import org.apache.shiro.authc.HostAuthenticationToken;
+
+import java.util.Date;
+
+/**
+ * Shiro JwtToken对象
+ **/
+@Data
+@Accessors(chain = true)
+public class JwtToken implements HostAuthenticationToken {
+	private static final long serialVersionUID = 5101247566043093405L;
+
+	/**
+     * 登录ip
+     */
+    private String host;
+    /**
+     * 登录用户名称
+     */
+    private String username;
+    /**
+     * 登录盐值
+     */
+    private String salt;
+    /**
+     * 登录token
+     */
+    private String token;
+    /**
+     * 创建时间
+     */
+    private Date createDate;
+    /**
+     * 多长时间过期,默认一小时
+     */
+    private long expireSecond;
+    /**
+     * 过期日期
+     */
+    private Date expireDate;
+
+    private String principal;
+
+    private String credentials;
+
+    @Override
+    public Object getPrincipal() {
+        return token;
+    }
+
+    @Override
+    public Object getCredentials() {
+        return token;
+    }
+
+    public static JwtToken build(String token, String username, String salt, long expireSecond) {
+        DecodedJWT decodedJwt = JwtUtil.getJwtInfo(token);
+        Date createDate = decodedJwt.getIssuedAt();
+        Date expireDate = decodedJwt.getExpiresAt();
+        return new JwtToken()
+                .setUsername(username)
+                .setToken(token)
+                .setHost(IpUtil.getRequestIp())
+                .setSalt(salt)
+                .setCreateDate(createDate)
+                .setExpireSecond(expireSecond)
+                .setExpireDate(expireDate);
+
+    }
+
+}

+ 87 - 0
src/main/java/com/xet/shiro/utils/JwtTokenUtil.java

@@ -0,0 +1,87 @@
+
+
+package com.xet.shiro.utils;
+
+import com.xet.properties.JwtProperties;
+import com.xet.util.HttpServletRequestUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * JwtToken工具类
+ **/
+@Slf4j
+@Component
+public class  JwtTokenUtil {
+
+    private static String tokenName;
+
+    public JwtTokenUtil(JwtProperties jwtProperties) {
+        tokenName = jwtProperties.getTokenName();
+        log.debug("tokenName:{}", tokenName);
+    }
+
+    /**
+     * 获取token名称
+     *
+     * @return
+     */
+    public static String getTokenName(String model) {
+        if(model.equals("")){
+            return tokenName;
+        }else{
+            return tokenName + "" + model;
+        }
+    }
+
+    /**
+     * 从请求头或者请求参数中
+     *
+     * @return
+     */
+    public static String getToken(String model) {
+        return getToken(HttpServletRequestUtil.getRequest(), model);
+    }
+
+    public static String getModel(String path){
+        String model = "";
+        if(path.startsWith("/api/admin/")){
+            model = "admin";
+        }else if(path.startsWith("/api/shop/")){
+            model = "shop";
+        }else if(path.startsWith("/api/supplier/")) {
+            model = "supplier";
+        }else if(path.startsWith("/api/service/")){
+            model = "service";
+        }
+        return model;
+    }
+
+    /**
+     * 从请求头或者请求参数中
+     *
+     * @param request
+     * @return
+     */
+    public static String getToken(HttpServletRequest request, String model) {
+        if (request == null) {
+            throw new IllegalArgumentException("request不能为空");
+        }
+        String realTokenName = "";
+        if(model.equals("")){
+            realTokenName = tokenName;
+        }else{
+            realTokenName = tokenName + "" + model;
+        }
+        // 从请求头中获取token
+        String token = request.getHeader(realTokenName);
+        if (StringUtils.isBlank(token)) {
+            // 从请求参数中获取token
+            token = request.getParameter(realTokenName);
+        }
+        return token;
+    }
+}

+ 186 - 0
src/main/java/com/xet/shiro/utils/JwtUtil.java

@@ -0,0 +1,186 @@
+
+
+package com.xet.shiro.utils;
+
+import com.alibaba.fastjson.JSON;
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.xet.constants.CommonConstant;
+import com.xet.properties.JwtProperties;
+import com.xet.util.UUIDUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.time.DateUtils;
+import org.springframework.stereotype.Component;
+
+import java.time.Duration;
+import java.util.Date;
+
+/**
+ * JWT工具类
+ * https://github.com/auth0/java-jwt
+ **/
+@Slf4j
+@Component
+public class JwtUtil {
+
+    private static JwtProperties jwtProperties;
+
+    public JwtUtil(JwtProperties jwtProperties) {
+        JwtUtil.jwtProperties = jwtProperties;
+        log.info(JSON.toJSONString(JwtUtil.jwtProperties));
+    }
+
+    /**
+     * 生成JWT Token
+     *
+     * @param username       用户名
+     * @param salt           盐值
+     * @param expireDuration 过期时间和单位
+     * @return token
+     */
+    public static String generateToken(String username, String salt, Duration expireDuration) {
+        try {
+            if (StringUtils.isBlank(username)) {
+                log.error("username不能为空");
+                return null;
+            }
+            log.debug("username:{}", username);
+
+            // 如果盐值为空,则使用默认值:888888
+            if (StringUtils.isBlank(salt)) {
+                salt = jwtProperties.getSecret();
+            }
+            log.debug("salt:{}", salt);
+
+            // 过期时间,单位:秒
+            Long expireSecond;
+            // 默认过期时间为1小时
+            if (expireDuration == null) {
+                expireSecond = jwtProperties.getExpireSecond();
+            } else {
+                expireSecond = expireDuration.getSeconds();
+            }
+            log.debug("expireSecond:{}", expireSecond);
+            Date expireDate = DateUtils.addSeconds(new Date(), expireSecond.intValue());
+            log.debug("expireDate:{}", expireDate);
+
+            // 生成token
+            Algorithm algorithm = Algorithm.HMAC256(salt);
+            String token = JWT.create()
+                    .withClaim(CommonConstant.JWT_USERNAME, username)
+                    // jwt唯一id
+                    .withJWTId(UUIDUtil.getUuid())
+                    // 签发人
+                    .withIssuer(jwtProperties.getIssuer())
+                    // 主题
+                    .withSubject(jwtProperties.getSubject())
+                    // 签发的目标
+                    .withAudience(jwtProperties.getAudience())
+                    // 签名时间
+                    .withIssuedAt(new Date())
+                    // token过期时间
+                    .withExpiresAt(expireDate)
+                    // 签名
+                    .sign(algorithm);
+            return token;
+        } catch (Exception e) {
+            log.error("generateToken exception", e);
+        }
+        return null;
+    }
+
+    public static boolean verifyToken(String token, String salt) {
+        try {
+            Algorithm algorithm = Algorithm.HMAC256(salt);
+            JWTVerifier verifier = JWT.require(algorithm)
+                    // 签发人
+                    .withIssuer(jwtProperties.getIssuer())
+                    // 主题
+                    .withSubject(jwtProperties.getSubject())
+                    // 签发的目标
+                    .withAudience(jwtProperties.getAudience())
+                    .build();
+            DecodedJWT jwt = verifier.verify(token);
+            if (jwt != null) {
+                return true;
+            }
+        } catch (Exception e) {
+            log.error("Verify Token Exception", e);
+        }
+        return false;
+    }
+
+    /**
+     * 解析token,获取token数据
+     *
+     * @param token
+     * @return
+     */
+    public static DecodedJWT getJwtInfo(String token) {
+        return JWT.decode(token);
+    }
+
+    /**
+     * 获取用户名
+     *
+     * @param token
+     * @return
+     */
+    public static String getUsername(String token) {
+        if (StringUtils.isBlank(token)){
+            return null;
+        }
+        DecodedJWT decodedJwt = getJwtInfo(token);
+        if (decodedJwt == null) {
+            return null;
+        }
+        String username = decodedJwt.getClaim(CommonConstant.JWT_USERNAME).asString();
+        return username;
+    }
+
+    /**
+     * 获取创建时间
+     *
+     * @param token
+     * @return
+     */
+    public static Date getIssuedAt(String token) {
+        DecodedJWT decodedJwt = getJwtInfo(token);
+        if (decodedJwt == null) {
+            return null;
+        }
+        return decodedJwt.getIssuedAt();
+    }
+
+    /**
+     * 获取过期时间
+     *
+     * @param token
+     * @return
+     */
+    public static Date getExpireDate(String token) {
+        DecodedJWT decodedJwt = getJwtInfo(token);
+        if (decodedJwt == null) {
+            return null;
+        }
+        return decodedJwt.getExpiresAt();
+    }
+
+    /**
+     * 判断token是否已过期
+     *
+     * @param token
+     * @return
+     */
+    public static boolean isExpired(String token) {
+        Date expireDate = getExpireDate(token);
+        if (expireDate == null) {
+            return true;
+        }
+        return expireDate.before(new Date());
+    }
+
+}

+ 59 - 0
src/main/java/com/xet/shiro/utils/SaltUtil.java

@@ -0,0 +1,59 @@
+
+
+package com.xet.shiro.utils;
+
+import com.xet.properties.JwtProperties;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shiro.crypto.SecureRandomNumberGenerator;
+import org.springframework.util.DigestUtils;
+
+/**
+ * 盐值包装工具类
+ **/
+public class SaltUtil {
+
+    /**
+     * 盐值包装
+     *
+     * @param secret 配置文件中配置的附加盐值
+     * @param salt   数据库中保存的盐值
+     * @return
+     */
+    public static String getSalt(String secret, String salt) {
+        if (StringUtils.isBlank(secret) && StringUtils.isBlank(salt)) {
+            return null;
+        }
+        // 加密方法
+        String newSalt = DigestUtils.md5DigestAsHex((secret + salt).getBytes());
+        return newSalt;
+    }
+
+    /**
+     * 生成6位随机盐
+     *
+     * @return
+     */
+    public static String generateSalt() {
+        return new SecureRandomNumberGenerator().nextBytes(3).toHex();
+    }
+
+    /**
+     * 加工盐值
+     *
+     * @param salt
+     * @param jwtProperties
+     * @return
+     */
+    public static String getSalt(String salt, JwtProperties jwtProperties) {
+        String newSalt;
+        if (jwtProperties.isSaltCheck()) {
+            // 包装盐值
+            newSalt = SaltUtil.getSalt(jwtProperties.getSecret(), salt);
+        } else {
+            newSalt = jwtProperties.getSecret();
+        }
+        return newSalt;
+    }
+
+}
+

+ 18 - 0
src/main/java/com/xet/util/HttpServletRequestUtil.java

@@ -0,0 +1,18 @@
+
+
+package com.xet.util;
+
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * 获取当前请求的HttpServletRequest对象
+ */
+public class HttpServletRequestUtil {
+
+    public static HttpServletRequest getRequest() {
+        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
+    }
+}

+ 42 - 0
src/main/java/com/xet/util/IniUtil.java

@@ -0,0 +1,42 @@
+
+
+package com.xet.util;
+
+import org.ini4j.Config;
+import org.ini4j.Ini;
+import org.ini4j.Profile;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Map;
+
+public class IniUtil {
+    public static Map<String,String> parseIni(String string) {
+        Config config = new Config();
+        config.setGlobalSection(true);
+        config.setGlobalSectionName("");
+        Ini ini = new Ini();
+        ini.setConfig(config);
+        try {
+            ini.load(new StringReader(string));
+            Profile.Section section = ini.get("");
+            return section;
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public static Map<String,String> parseIni(String sectionName,String string) {
+        Ini ini = new Ini();
+        try {
+            ini.load(new StringReader(string));
+            Profile.Section section = ini.get(sectionName);
+            return section;
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+}

+ 65 - 0
src/main/java/com/xet/util/IpUtil.java

@@ -0,0 +1,65 @@
+
+
+package com.xet.util;
+
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * 获取IP地址工具类
+ */
+public final class IpUtil {
+
+    private static final String UNKNOWN = "unknown";
+    private static final String IPV6_LOCAL = "0:0:0:0:0:0:0:1";
+
+    private IpUtil(){
+        throw new AssertionError();
+    }
+
+    /**
+     * 获取请求用户的IP地址
+     * @return
+     */
+    public static String getRequestIp() {
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        HttpServletRequest request = attributes.getRequest();
+        return getRequestIp(request);
+    }
+
+    /**
+     * 获取请求用户的IP地址
+     * @param request
+     * @return
+     */
+    public static String getRequestIp(HttpServletRequest request) {
+        String ip = request.getHeader("x-forwarded-for");
+        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
+            ip = request.getRemoteAddr();
+        }
+
+        if (IPV6_LOCAL.equals(ip)){
+            ip = getLocalhostIp();
+        }
+        return ip;
+    }
+
+    public static String getLocalhostIp(){
+        try {
+            return InetAddress.getLocalHost().getHostAddress();
+        } catch (UnknownHostException e) {
+        }
+        return null;
+    }
+
+}

+ 14 - 0
src/main/java/com/xet/util/UUIDUtil.java

@@ -0,0 +1,14 @@
+
+
+package com.xet.util;
+
+import java.util.UUID;
+
+public class UUIDUtil {
+
+    public static String getUuid(){
+        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
+        return uuid;
+    }
+
+}